nexus site path corrected
[portal.git] / ecomp-portal-FE / client / bower_components / angular-bootstrap / ui-bootstrap.js
1 /*
2  * angular-ui-bootstrap
3  * http://angular-ui.github.io/bootstrap/
4
5  * Version: 2.4.0 - 2016-12-29
6  * License: MIT
7  */angular.module("ui.bootstrap", ["ui.bootstrap.collapse","ui.bootstrap.tabindex","ui.bootstrap.accordion","ui.bootstrap.alert","ui.bootstrap.buttons","ui.bootstrap.carousel","ui.bootstrap.dateparser","ui.bootstrap.isClass","ui.bootstrap.datepicker","ui.bootstrap.position","ui.bootstrap.datepickerPopup","ui.bootstrap.debounce","ui.bootstrap.multiMap","ui.bootstrap.dropdown","ui.bootstrap.stackedMap","ui.bootstrap.modal","ui.bootstrap.paging","ui.bootstrap.pager","ui.bootstrap.pagination","ui.bootstrap.tooltip","ui.bootstrap.popover","ui.bootstrap.progressbar","ui.bootstrap.rating","ui.bootstrap.tabs","ui.bootstrap.timepicker","ui.bootstrap.typeahead"]);
8 angular.module('ui.bootstrap.collapse', [])
9
10   .directive('uibCollapse', ['$animate', '$q', '$parse', '$injector', function($animate, $q, $parse, $injector) {
11     var $animateCss = $injector.has('$animateCss') ? $injector.get('$animateCss') : null;
12     return {
13       link: function(scope, element, attrs) {
14         var expandingExpr = $parse(attrs.expanding),
15           expandedExpr = $parse(attrs.expanded),
16           collapsingExpr = $parse(attrs.collapsing),
17           collapsedExpr = $parse(attrs.collapsed),
18           horizontal = false,
19           css = {},
20           cssTo = {};
21
22         init();
23
24         function init() {
25           horizontal = !!('horizontal' in attrs);
26           if (horizontal) {
27             css = {
28               width: ''
29             };
30             cssTo = {width: '0'};
31           } else {
32             css = {
33               height: ''
34             };
35             cssTo = {height: '0'};
36           }
37           if (!scope.$eval(attrs.uibCollapse)) {
38             element.addClass('in')
39               .addClass('collapse')
40               .attr('aria-expanded', true)
41               .attr('aria-hidden', false)
42               .css(css);
43           }
44         }
45
46         function getScrollFromElement(element) {
47           if (horizontal) {
48             return {width: element.scrollWidth + 'px'};
49           }
50           return {height: element.scrollHeight + 'px'};
51         }
52
53         function expand() {
54           if (element.hasClass('collapse') && element.hasClass('in')) {
55             return;
56           }
57
58           $q.resolve(expandingExpr(scope))
59             .then(function() {
60               element.removeClass('collapse')
61                 .addClass('collapsing')
62                 .attr('aria-expanded', true)
63                 .attr('aria-hidden', false);
64
65               if ($animateCss) {
66                 $animateCss(element, {
67                   addClass: 'in',
68                   easing: 'ease',
69                   css: {
70                     overflow: 'hidden'
71                   },
72                   to: getScrollFromElement(element[0])
73                 }).start()['finally'](expandDone);
74               } else {
75                 $animate.addClass(element, 'in', {
76                   css: {
77                     overflow: 'hidden'
78                   },
79                   to: getScrollFromElement(element[0])
80                 }).then(expandDone);
81               }
82             });
83         }
84
85         function expandDone() {
86           element.removeClass('collapsing')
87             .addClass('collapse')
88             .css(css);
89           expandedExpr(scope);
90         }
91
92         function collapse() {
93           if (!element.hasClass('collapse') && !element.hasClass('in')) {
94             return collapseDone();
95           }
96
97           $q.resolve(collapsingExpr(scope))
98             .then(function() {
99               element
100               // IMPORTANT: The width must be set before adding "collapsing" class.
101               // Otherwise, the browser attempts to animate from width 0 (in
102               // collapsing class) to the given width here.
103                 .css(getScrollFromElement(element[0]))
104                 // initially all panel collapse have the collapse class, this removal
105                 // prevents the animation from jumping to collapsed state
106                 .removeClass('collapse')
107                 .addClass('collapsing')
108                 .attr('aria-expanded', false)
109                 .attr('aria-hidden', true);
110
111               if ($animateCss) {
112                 $animateCss(element, {
113                   removeClass: 'in',
114                   to: cssTo
115                 }).start()['finally'](collapseDone);
116               } else {
117                 $animate.removeClass(element, 'in', {
118                   to: cssTo
119                 }).then(collapseDone);
120               }
121             });
122         }
123
124         function collapseDone() {
125           element.css(cssTo); // Required so that collapse works when animation is disabled
126           element.removeClass('collapsing')
127             .addClass('collapse');
128           collapsedExpr(scope);
129         }
130
131         scope.$watch(attrs.uibCollapse, function(shouldCollapse) {
132           if (shouldCollapse) {
133             collapse();
134           } else {
135             expand();
136           }
137         });
138       }
139     };
140   }]);
141
142 angular.module('ui.bootstrap.tabindex', [])
143
144 .directive('uibTabindexToggle', function() {
145   return {
146     restrict: 'A',
147     link: function(scope, elem, attrs) {
148       attrs.$observe('disabled', function(disabled) {
149         attrs.$set('tabindex', disabled ? -1 : null);
150       });
151     }
152   };
153 });
154
155 angular.module('ui.bootstrap.accordion', ['ui.bootstrap.collapse', 'ui.bootstrap.tabindex'])
156
157 .constant('uibAccordionConfig', {
158   closeOthers: true
159 })
160
161 .controller('UibAccordionController', ['$scope', '$attrs', 'uibAccordionConfig', function($scope, $attrs, accordionConfig) {
162   // This array keeps track of the accordion groups
163   this.groups = [];
164
165   // Ensure that all the groups in this accordion are closed, unless close-others explicitly says not to
166   this.closeOthers = function(openGroup) {
167     var closeOthers = angular.isDefined($attrs.closeOthers) ?
168       $scope.$eval($attrs.closeOthers) : accordionConfig.closeOthers;
169     if (closeOthers) {
170       angular.forEach(this.groups, function(group) {
171         if (group !== openGroup) {
172           group.isOpen = false;
173         }
174       });
175     }
176   };
177
178   // This is called from the accordion-group directive to add itself to the accordion
179   this.addGroup = function(groupScope) {
180     var that = this;
181     this.groups.push(groupScope);
182
183     groupScope.$on('$destroy', function(event) {
184       that.removeGroup(groupScope);
185     });
186   };
187
188   // This is called from the accordion-group directive when to remove itself
189   this.removeGroup = function(group) {
190     var index = this.groups.indexOf(group);
191     if (index !== -1) {
192       this.groups.splice(index, 1);
193     }
194   };
195 }])
196
197 // The accordion directive simply sets up the directive controller
198 // and adds an accordion CSS class to itself element.
199 .directive('uibAccordion', function() {
200   return {
201     controller: 'UibAccordionController',
202     controllerAs: 'accordion',
203     transclude: true,
204     templateUrl: function(element, attrs) {
205       return attrs.templateUrl || 'uib/template/accordion/accordion.html';
206     }
207   };
208 })
209
210 // The accordion-group directive indicates a block of html that will expand and collapse in an accordion
211 .directive('uibAccordionGroup', function() {
212   return {
213     require: '^uibAccordion',         // We need this directive to be inside an accordion
214     transclude: true,              // It transcludes the contents of the directive into the template
215     restrict: 'A',
216     templateUrl: function(element, attrs) {
217       return attrs.templateUrl || 'uib/template/accordion/accordion-group.html';
218     },
219     scope: {
220       heading: '@',               // Interpolate the heading attribute onto this scope
221       panelClass: '@?',           // Ditto with panelClass
222       isOpen: '=?',
223       isDisabled: '=?'
224     },
225     controller: function() {
226       this.setHeading = function(element) {
227         this.heading = element;
228       };
229     },
230     link: function(scope, element, attrs, accordionCtrl) {
231       element.addClass('panel');
232       accordionCtrl.addGroup(scope);
233
234       scope.openClass = attrs.openClass || 'panel-open';
235       scope.panelClass = attrs.panelClass || 'panel-default';
236       scope.$watch('isOpen', function(value) {
237         element.toggleClass(scope.openClass, !!value);
238         if (value) {
239           accordionCtrl.closeOthers(scope);
240         }
241       });
242
243       scope.toggleOpen = function($event) {
244         if (!scope.isDisabled) {
245           if (!$event || $event.which === 32) {
246             scope.isOpen = !scope.isOpen;
247           }
248         }
249       };
250
251       var id = 'accordiongroup-' + scope.$id + '-' + Math.floor(Math.random() * 10000);
252       scope.headingId = id + '-tab';
253       scope.panelId = id + '-panel';
254     }
255   };
256 })
257
258 // Use accordion-heading below an accordion-group to provide a heading containing HTML
259 .directive('uibAccordionHeading', function() {
260   return {
261     transclude: true,   // Grab the contents to be used as the heading
262     template: '',       // In effect remove this element!
263     replace: true,
264     require: '^uibAccordionGroup',
265     link: function(scope, element, attrs, accordionGroupCtrl, transclude) {
266       // Pass the heading to the accordion-group controller
267       // so that it can be transcluded into the right place in the template
268       // [The second parameter to transclude causes the elements to be cloned so that they work in ng-repeat]
269       accordionGroupCtrl.setHeading(transclude(scope, angular.noop));
270     }
271   };
272 })
273
274 // Use in the accordion-group template to indicate where you want the heading to be transcluded
275 // You must provide the property on the accordion-group controller that will hold the transcluded element
276 .directive('uibAccordionTransclude', function() {
277   return {
278     require: '^uibAccordionGroup',
279     link: function(scope, element, attrs, controller) {
280       scope.$watch(function() { return controller[attrs.uibAccordionTransclude]; }, function(heading) {
281         if (heading) {
282           var elem = angular.element(element[0].querySelector(getHeaderSelectors()));
283           elem.html('');
284           elem.append(heading);
285         }
286       });
287     }
288   };
289
290   function getHeaderSelectors() {
291       return 'uib-accordion-header,' +
292           'data-uib-accordion-header,' +
293           'x-uib-accordion-header,' +
294           'uib\\:accordion-header,' +
295           '[uib-accordion-header],' +
296           '[data-uib-accordion-header],' +
297           '[x-uib-accordion-header]';
298   }
299 });
300
301 angular.module('ui.bootstrap.alert', [])
302
303 .controller('UibAlertController', ['$scope', '$element', '$attrs', '$interpolate', '$timeout', function($scope, $element, $attrs, $interpolate, $timeout) {
304   $scope.closeable = !!$attrs.close;
305   $element.addClass('alert');
306   $attrs.$set('role', 'alert');
307   if ($scope.closeable) {
308     $element.addClass('alert-dismissible');
309   }
310
311   var dismissOnTimeout = angular.isDefined($attrs.dismissOnTimeout) ?
312     $interpolate($attrs.dismissOnTimeout)($scope.$parent) : null;
313
314   if (dismissOnTimeout) {
315     $timeout(function() {
316       $scope.close();
317     }, parseInt(dismissOnTimeout, 10));
318   }
319 }])
320
321 .directive('uibAlert', function() {
322   return {
323     controller: 'UibAlertController',
324     controllerAs: 'alert',
325     restrict: 'A',
326     templateUrl: function(element, attrs) {
327       return attrs.templateUrl || 'uib/template/alert/alert.html';
328     },
329     transclude: true,
330     scope: {
331       close: '&'
332     }
333   };
334 });
335
336 angular.module('ui.bootstrap.buttons', [])
337
338 .constant('uibButtonConfig', {
339   activeClass: 'active',
340   toggleEvent: 'click'
341 })
342
343 .controller('UibButtonsController', ['uibButtonConfig', function(buttonConfig) {
344   this.activeClass = buttonConfig.activeClass || 'active';
345   this.toggleEvent = buttonConfig.toggleEvent || 'click';
346 }])
347
348 .directive('uibBtnRadio', ['$parse', function($parse) {
349   return {
350     require: ['uibBtnRadio', 'ngModel'],
351     controller: 'UibButtonsController',
352     controllerAs: 'buttons',
353     link: function(scope, element, attrs, ctrls) {
354       var buttonsCtrl = ctrls[0], ngModelCtrl = ctrls[1];
355       var uncheckableExpr = $parse(attrs.uibUncheckable);
356
357       element.find('input').css({display: 'none'});
358
359       //model -> UI
360       ngModelCtrl.$render = function() {
361         element.toggleClass(buttonsCtrl.activeClass, angular.equals(ngModelCtrl.$modelValue, scope.$eval(attrs.uibBtnRadio)));
362       };
363
364       //ui->model
365       element.on(buttonsCtrl.toggleEvent, function() {
366         if (attrs.disabled) {
367           return;
368         }
369
370         var isActive = element.hasClass(buttonsCtrl.activeClass);
371
372         if (!isActive || angular.isDefined(attrs.uncheckable)) {
373           scope.$apply(function() {
374             ngModelCtrl.$setViewValue(isActive ? null : scope.$eval(attrs.uibBtnRadio));
375             ngModelCtrl.$render();
376           });
377         }
378       });
379
380       if (attrs.uibUncheckable) {
381         scope.$watch(uncheckableExpr, function(uncheckable) {
382           attrs.$set('uncheckable', uncheckable ? '' : undefined);
383         });
384       }
385     }
386   };
387 }])
388
389 .directive('uibBtnCheckbox', function() {
390   return {
391     require: ['uibBtnCheckbox', 'ngModel'],
392     controller: 'UibButtonsController',
393     controllerAs: 'button',
394     link: function(scope, element, attrs, ctrls) {
395       var buttonsCtrl = ctrls[0], ngModelCtrl = ctrls[1];
396
397       element.find('input').css({display: 'none'});
398
399       function getTrueValue() {
400         return getCheckboxValue(attrs.btnCheckboxTrue, true);
401       }
402
403       function getFalseValue() {
404         return getCheckboxValue(attrs.btnCheckboxFalse, false);
405       }
406
407       function getCheckboxValue(attribute, defaultValue) {
408         return angular.isDefined(attribute) ? scope.$eval(attribute) : defaultValue;
409       }
410
411       //model -> UI
412       ngModelCtrl.$render = function() {
413         element.toggleClass(buttonsCtrl.activeClass, angular.equals(ngModelCtrl.$modelValue, getTrueValue()));
414       };
415
416       //ui->model
417       element.on(buttonsCtrl.toggleEvent, function() {
418         if (attrs.disabled) {
419           return;
420         }
421
422         scope.$apply(function() {
423           ngModelCtrl.$setViewValue(element.hasClass(buttonsCtrl.activeClass) ? getFalseValue() : getTrueValue());
424           ngModelCtrl.$render();
425         });
426       });
427     }
428   };
429 });
430
431 angular.module('ui.bootstrap.carousel', [])
432
433 .controller('UibCarouselController', ['$scope', '$element', '$interval', '$timeout', '$animate', function($scope, $element, $interval, $timeout, $animate) {
434   var self = this,
435     slides = self.slides = $scope.slides = [],
436     SLIDE_DIRECTION = 'uib-slideDirection',
437     currentIndex = $scope.active,
438     currentInterval, isPlaying, bufferedTransitions = [];
439
440   var destroyed = false;
441   $element.addClass('carousel');
442
443   self.addSlide = function(slide, element) {
444     slides.push({
445       slide: slide,
446       element: element
447     });
448     slides.sort(function(a, b) {
449       return +a.slide.index - +b.slide.index;
450     });
451     //if this is the first slide or the slide is set to active, select it
452     if (slide.index === $scope.active || slides.length === 1 && !angular.isNumber($scope.active)) {
453       if ($scope.$currentTransition) {
454         $scope.$currentTransition = null;
455       }
456
457       currentIndex = slide.index;
458       $scope.active = slide.index;
459       setActive(currentIndex);
460       self.select(slides[findSlideIndex(slide)]);
461       if (slides.length === 1) {
462         $scope.play();
463       }
464     }
465   };
466
467   self.getCurrentIndex = function() {
468     for (var i = 0; i < slides.length; i++) {
469       if (slides[i].slide.index === currentIndex) {
470         return i;
471       }
472     }
473   };
474
475   self.next = $scope.next = function() {
476     var newIndex = (self.getCurrentIndex() + 1) % slides.length;
477
478     if (newIndex === 0 && $scope.noWrap()) {
479       $scope.pause();
480       return;
481     }
482
483     return self.select(slides[newIndex], 'next');
484   };
485
486   self.prev = $scope.prev = function() {
487     var newIndex = self.getCurrentIndex() - 1 < 0 ? slides.length - 1 : self.getCurrentIndex() - 1;
488
489     if ($scope.noWrap() && newIndex === slides.length - 1) {
490       $scope.pause();
491       return;
492     }
493
494     return self.select(slides[newIndex], 'prev');
495   };
496
497   self.removeSlide = function(slide) {
498     var index = findSlideIndex(slide);
499
500     var bufferedIndex = bufferedTransitions.indexOf(slides[index]);
501     if (bufferedIndex !== -1) {
502       bufferedTransitions.splice(bufferedIndex, 1);
503     }
504
505     //get the index of the slide inside the carousel
506     slides.splice(index, 1);
507     if (slides.length > 0 && currentIndex === index) {
508       if (index >= slides.length) {
509         currentIndex = slides.length - 1;
510         $scope.active = currentIndex;
511         setActive(currentIndex);
512         self.select(slides[slides.length - 1]);
513       } else {
514         currentIndex = index;
515         $scope.active = currentIndex;
516         setActive(currentIndex);
517         self.select(slides[index]);
518       }
519     } else if (currentIndex > index) {
520       currentIndex--;
521       $scope.active = currentIndex;
522     }
523
524     //clean the active value when no more slide
525     if (slides.length === 0) {
526       currentIndex = null;
527       $scope.active = null;
528       clearBufferedTransitions();
529     }
530   };
531
532   /* direction: "prev" or "next" */
533   self.select = $scope.select = function(nextSlide, direction) {
534     var nextIndex = findSlideIndex(nextSlide.slide);
535     //Decide direction if it's not given
536     if (direction === undefined) {
537       direction = nextIndex > self.getCurrentIndex() ? 'next' : 'prev';
538     }
539     //Prevent this user-triggered transition from occurring if there is already one in progress
540     if (nextSlide.slide.index !== currentIndex &&
541       !$scope.$currentTransition) {
542       goNext(nextSlide.slide, nextIndex, direction);
543     } else if (nextSlide && nextSlide.slide.index !== currentIndex && $scope.$currentTransition) {
544       bufferedTransitions.push(slides[nextIndex]);
545     }
546   };
547
548   /* Allow outside people to call indexOf on slides array */
549   $scope.indexOfSlide = function(slide) {
550     return +slide.slide.index;
551   };
552
553   $scope.isActive = function(slide) {
554     return $scope.active === slide.slide.index;
555   };
556
557   $scope.isPrevDisabled = function() {
558     return $scope.active === 0 && $scope.noWrap();
559   };
560
561   $scope.isNextDisabled = function() {
562     return $scope.active === slides.length - 1 && $scope.noWrap();
563   };
564
565   $scope.pause = function() {
566     if (!$scope.noPause) {
567       isPlaying = false;
568       resetTimer();
569     }
570   };
571
572   $scope.play = function() {
573     if (!isPlaying) {
574       isPlaying = true;
575       restartTimer();
576     }
577   };
578
579   $element.on('mouseenter', $scope.pause);
580   $element.on('mouseleave', $scope.play);
581
582   $scope.$on('$destroy', function() {
583     destroyed = true;
584     resetTimer();
585   });
586
587   $scope.$watch('noTransition', function(noTransition) {
588     $animate.enabled($element, !noTransition);
589   });
590
591   $scope.$watch('interval', restartTimer);
592
593   $scope.$watchCollection('slides', resetTransition);
594
595   $scope.$watch('active', function(index) {
596     if (angular.isNumber(index) && currentIndex !== index) {
597       for (var i = 0; i < slides.length; i++) {
598         if (slides[i].slide.index === index) {
599           index = i;
600           break;
601         }
602       }
603
604       var slide = slides[index];
605       if (slide) {
606         setActive(index);
607         self.select(slides[index]);
608         currentIndex = index;
609       }
610     }
611   });
612
613   function clearBufferedTransitions() {
614     while (bufferedTransitions.length) {
615       bufferedTransitions.shift();
616     }
617   }
618
619   function getSlideByIndex(index) {
620     for (var i = 0, l = slides.length; i < l; ++i) {
621       if (slides[i].index === index) {
622         return slides[i];
623       }
624     }
625   }
626
627   function setActive(index) {
628     for (var i = 0; i < slides.length; i++) {
629       slides[i].slide.active = i === index;
630     }
631   }
632
633   function goNext(slide, index, direction) {
634     if (destroyed) {
635       return;
636     }
637
638     angular.extend(slide, {direction: direction});
639     angular.extend(slides[currentIndex].slide || {}, {direction: direction});
640     if ($animate.enabled($element) && !$scope.$currentTransition &&
641       slides[index].element && self.slides.length > 1) {
642       slides[index].element.data(SLIDE_DIRECTION, slide.direction);
643       var currentIdx = self.getCurrentIndex();
644
645       if (angular.isNumber(currentIdx) && slides[currentIdx].element) {
646         slides[currentIdx].element.data(SLIDE_DIRECTION, slide.direction);
647       }
648
649       $scope.$currentTransition = true;
650       $animate.on('addClass', slides[index].element, function(element, phase) {
651         if (phase === 'close') {
652           $scope.$currentTransition = null;
653           $animate.off('addClass', element);
654           if (bufferedTransitions.length) {
655             var nextSlide = bufferedTransitions.pop().slide;
656             var nextIndex = nextSlide.index;
657             var nextDirection = nextIndex > self.getCurrentIndex() ? 'next' : 'prev';
658             clearBufferedTransitions();
659
660             goNext(nextSlide, nextIndex, nextDirection);
661           }
662         }
663       });
664     }
665
666     $scope.active = slide.index;
667     currentIndex = slide.index;
668     setActive(index);
669
670     //every time you change slides, reset the timer
671     restartTimer();
672   }
673
674   function findSlideIndex(slide) {
675     for (var i = 0; i < slides.length; i++) {
676       if (slides[i].slide === slide) {
677         return i;
678       }
679     }
680   }
681
682   function resetTimer() {
683     if (currentInterval) {
684       $interval.cancel(currentInterval);
685       currentInterval = null;
686     }
687   }
688
689   function resetTransition(slides) {
690     if (!slides.length) {
691       $scope.$currentTransition = null;
692       clearBufferedTransitions();
693     }
694   }
695
696   function restartTimer() {
697     resetTimer();
698     var interval = +$scope.interval;
699     if (!isNaN(interval) && interval > 0) {
700       currentInterval = $interval(timerFn, interval);
701     }
702   }
703
704   function timerFn() {
705     var interval = +$scope.interval;
706     if (isPlaying && !isNaN(interval) && interval > 0 && slides.length) {
707       $scope.next();
708     } else {
709       $scope.pause();
710     }
711   }
712 }])
713
714 .directive('uibCarousel', function() {
715   return {
716     transclude: true,
717     controller: 'UibCarouselController',
718     controllerAs: 'carousel',
719     restrict: 'A',
720     templateUrl: function(element, attrs) {
721       return attrs.templateUrl || 'uib/template/carousel/carousel.html';
722     },
723     scope: {
724       active: '=',
725       interval: '=',
726       noTransition: '=',
727       noPause: '=',
728       noWrap: '&'
729     }
730   };
731 })
732
733 .directive('uibSlide', ['$animate', function($animate) {
734   return {
735     require: '^uibCarousel',
736     restrict: 'A',
737     transclude: true,
738     templateUrl: function(element, attrs) {
739       return attrs.templateUrl || 'uib/template/carousel/slide.html';
740     },
741     scope: {
742       actual: '=?',
743       index: '=?'
744     },
745     link: function (scope, element, attrs, carouselCtrl) {
746       element.addClass('item');
747       carouselCtrl.addSlide(scope, element);
748       //when the scope is destroyed then remove the slide from the current slides array
749       scope.$on('$destroy', function() {
750         carouselCtrl.removeSlide(scope);
751       });
752
753       scope.$watch('active', function(active) {
754         $animate[active ? 'addClass' : 'removeClass'](element, 'active');
755       });
756     }
757   };
758 }])
759
760 .animation('.item', ['$animateCss',
761 function($animateCss) {
762   var SLIDE_DIRECTION = 'uib-slideDirection';
763
764   function removeClass(element, className, callback) {
765     element.removeClass(className);
766     if (callback) {
767       callback();
768     }
769   }
770
771   return {
772     beforeAddClass: function(element, className, done) {
773       if (className === 'active') {
774         var stopped = false;
775         var direction = element.data(SLIDE_DIRECTION);
776         var directionClass = direction === 'next' ? 'left' : 'right';
777         var removeClassFn = removeClass.bind(this, element,
778           directionClass + ' ' + direction, done);
779         element.addClass(direction);
780
781         $animateCss(element, {addClass: directionClass})
782           .start()
783           .done(removeClassFn);
784
785         return function() {
786           stopped = true;
787         };
788       }
789       done();
790     },
791     beforeRemoveClass: function (element, className, done) {
792       if (className === 'active') {
793         var stopped = false;
794         var direction = element.data(SLIDE_DIRECTION);
795         var directionClass = direction === 'next' ? 'left' : 'right';
796         var removeClassFn = removeClass.bind(this, element, directionClass, done);
797
798         $animateCss(element, {addClass: directionClass})
799           .start()
800           .done(removeClassFn);
801
802         return function() {
803           stopped = true;
804         };
805       }
806       done();
807     }
808   };
809 }]);
810
811 angular.module('ui.bootstrap.dateparser', [])
812
813 .service('uibDateParser', ['$log', '$locale', 'dateFilter', 'orderByFilter', 'filterFilter', function($log, $locale, dateFilter, orderByFilter, filterFilter) {
814   // Pulled from https://github.com/mbostock/d3/blob/master/src/format/requote.js
815   var SPECIAL_CHARACTERS_REGEXP = /[\\\^\$\*\+\?\|\[\]\(\)\.\{\}]/g;
816
817   var localeId;
818   var formatCodeToRegex;
819
820   this.init = function() {
821     localeId = $locale.id;
822
823     this.parsers = {};
824     this.formatters = {};
825
826     formatCodeToRegex = [
827       {
828         key: 'yyyy',
829         regex: '\\d{4}',
830         apply: function(value) { this.year = +value; },
831         formatter: function(date) {
832           var _date = new Date();
833           _date.setFullYear(Math.abs(date.getFullYear()));
834           return dateFilter(_date, 'yyyy');
835         }
836       },
837       {
838         key: 'yy',
839         regex: '\\d{2}',
840         apply: function(value) { value = +value; this.year = value < 69 ? value + 2000 : value + 1900; },
841         formatter: function(date) {
842           var _date = new Date();
843           _date.setFullYear(Math.abs(date.getFullYear()));
844           return dateFilter(_date, 'yy');
845         }
846       },
847       {
848         key: 'y',
849         regex: '\\d{1,4}',
850         apply: function(value) { this.year = +value; },
851         formatter: function(date) {
852           var _date = new Date();
853           _date.setFullYear(Math.abs(date.getFullYear()));
854           return dateFilter(_date, 'y');
855         }
856       },
857       {
858         key: 'M!',
859         regex: '0?[1-9]|1[0-2]',
860         apply: function(value) { this.month = value - 1; },
861         formatter: function(date) {
862           var value = date.getMonth();
863           if (/^[0-9]$/.test(value)) {
864             return dateFilter(date, 'MM');
865           }
866
867           return dateFilter(date, 'M');
868         }
869       },
870       {
871         key: 'MMMM',
872         regex: $locale.DATETIME_FORMATS.MONTH.join('|'),
873         apply: function(value) { this.month = $locale.DATETIME_FORMATS.MONTH.indexOf(value); },
874         formatter: function(date) { return dateFilter(date, 'MMMM'); }
875       },
876       {
877         key: 'MMM',
878         regex: $locale.DATETIME_FORMATS.SHORTMONTH.join('|'),
879         apply: function(value) { this.month = $locale.DATETIME_FORMATS.SHORTMONTH.indexOf(value); },
880         formatter: function(date) { return dateFilter(date, 'MMM'); }
881       },
882       {
883         key: 'MM',
884         regex: '0[1-9]|1[0-2]',
885         apply: function(value) { this.month = value - 1; },
886         formatter: function(date) { return dateFilter(date, 'MM'); }
887       },
888       {
889         key: 'M',
890         regex: '[1-9]|1[0-2]',
891         apply: function(value) { this.month = value - 1; },
892         formatter: function(date) { return dateFilter(date, 'M'); }
893       },
894       {
895         key: 'd!',
896         regex: '[0-2]?[0-9]{1}|3[0-1]{1}',
897         apply: function(value) { this.date = +value; },
898         formatter: function(date) {
899           var value = date.getDate();
900           if (/^[1-9]$/.test(value)) {
901             return dateFilter(date, 'dd');
902           }
903
904           return dateFilter(date, 'd');
905         }
906       },
907       {
908         key: 'dd',
909         regex: '[0-2][0-9]{1}|3[0-1]{1}',
910         apply: function(value) { this.date = +value; },
911         formatter: function(date) { return dateFilter(date, 'dd'); }
912       },
913       {
914         key: 'd',
915         regex: '[1-2]?[0-9]{1}|3[0-1]{1}',
916         apply: function(value) { this.date = +value; },
917         formatter: function(date) { return dateFilter(date, 'd'); }
918       },
919       {
920         key: 'EEEE',
921         regex: $locale.DATETIME_FORMATS.DAY.join('|'),
922         formatter: function(date) { return dateFilter(date, 'EEEE'); }
923       },
924       {
925         key: 'EEE',
926         regex: $locale.DATETIME_FORMATS.SHORTDAY.join('|'),
927         formatter: function(date) { return dateFilter(date, 'EEE'); }
928       },
929       {
930         key: 'HH',
931         regex: '(?:0|1)[0-9]|2[0-3]',
932         apply: function(value) { this.hours = +value; },
933         formatter: function(date) { return dateFilter(date, 'HH'); }
934       },
935       {
936         key: 'hh',
937         regex: '0[0-9]|1[0-2]',
938         apply: function(value) { this.hours = +value; },
939         formatter: function(date) { return dateFilter(date, 'hh'); }
940       },
941       {
942         key: 'H',
943         regex: '1?[0-9]|2[0-3]',
944         apply: function(value) { this.hours = +value; },
945         formatter: function(date) { return dateFilter(date, 'H'); }
946       },
947       {
948         key: 'h',
949         regex: '[0-9]|1[0-2]',
950         apply: function(value) { this.hours = +value; },
951         formatter: function(date) { return dateFilter(date, 'h'); }
952       },
953       {
954         key: 'mm',
955         regex: '[0-5][0-9]',
956         apply: function(value) { this.minutes = +value; },
957         formatter: function(date) { return dateFilter(date, 'mm'); }
958       },
959       {
960         key: 'm',
961         regex: '[0-9]|[1-5][0-9]',
962         apply: function(value) { this.minutes = +value; },
963         formatter: function(date) { return dateFilter(date, 'm'); }
964       },
965       {
966         key: 'sss',
967         regex: '[0-9][0-9][0-9]',
968         apply: function(value) { this.milliseconds = +value; },
969         formatter: function(date) { return dateFilter(date, 'sss'); }
970       },
971       {
972         key: 'ss',
973         regex: '[0-5][0-9]',
974         apply: function(value) { this.seconds = +value; },
975         formatter: function(date) { return dateFilter(date, 'ss'); }
976       },
977       {
978         key: 's',
979         regex: '[0-9]|[1-5][0-9]',
980         apply: function(value) { this.seconds = +value; },
981         formatter: function(date) { return dateFilter(date, 's'); }
982       },
983       {
984         key: 'a',
985         regex: $locale.DATETIME_FORMATS.AMPMS.join('|'),
986         apply: function(value) {
987           if (this.hours === 12) {
988             this.hours = 0;
989           }
990
991           if (value === 'PM') {
992             this.hours += 12;
993           }
994         },
995         formatter: function(date) { return dateFilter(date, 'a'); }
996       },
997       {
998         key: 'Z',
999         regex: '[+-]\\d{4}',
1000         apply: function(value) {
1001           var matches = value.match(/([+-])(\d{2})(\d{2})/),
1002             sign = matches[1],
1003             hours = matches[2],
1004             minutes = matches[3];
1005           this.hours += toInt(sign + hours);
1006           this.minutes += toInt(sign + minutes);
1007         },
1008         formatter: function(date) {
1009           return dateFilter(date, 'Z');
1010         }
1011       },
1012       {
1013         key: 'ww',
1014         regex: '[0-4][0-9]|5[0-3]',
1015         formatter: function(date) { return dateFilter(date, 'ww'); }
1016       },
1017       {
1018         key: 'w',
1019         regex: '[0-9]|[1-4][0-9]|5[0-3]',
1020         formatter: function(date) { return dateFilter(date, 'w'); }
1021       },
1022       {
1023         key: 'GGGG',
1024         regex: $locale.DATETIME_FORMATS.ERANAMES.join('|').replace(/\s/g, '\\s'),
1025         formatter: function(date) { return dateFilter(date, 'GGGG'); }
1026       },
1027       {
1028         key: 'GGG',
1029         regex: $locale.DATETIME_FORMATS.ERAS.join('|'),
1030         formatter: function(date) { return dateFilter(date, 'GGG'); }
1031       },
1032       {
1033         key: 'GG',
1034         regex: $locale.DATETIME_FORMATS.ERAS.join('|'),
1035         formatter: function(date) { return dateFilter(date, 'GG'); }
1036       },
1037       {
1038         key: 'G',
1039         regex: $locale.DATETIME_FORMATS.ERAS.join('|'),
1040         formatter: function(date) { return dateFilter(date, 'G'); }
1041       }
1042     ];
1043
1044     if (angular.version.major >= 1 && angular.version.minor > 4) {
1045       formatCodeToRegex.push({
1046         key: 'LLLL',
1047         regex: $locale.DATETIME_FORMATS.STANDALONEMONTH.join('|'),
1048         apply: function(value) { this.month = $locale.DATETIME_FORMATS.STANDALONEMONTH.indexOf(value); },
1049         formatter: function(date) { return dateFilter(date, 'LLLL'); }
1050       });
1051     }
1052   };
1053
1054   this.init();
1055
1056   function getFormatCodeToRegex(key) {
1057     return filterFilter(formatCodeToRegex, {key: key}, true)[0];
1058   }
1059
1060   this.getParser = function (key) {
1061     var f = getFormatCodeToRegex(key);
1062     return f && f.apply || null;
1063   };
1064
1065   this.overrideParser = function (key, parser) {
1066     var f = getFormatCodeToRegex(key);
1067     if (f && angular.isFunction(parser)) {
1068       this.parsers = {};
1069       f.apply = parser;
1070     }
1071   }.bind(this);
1072
1073   function createParser(format) {
1074     var map = [], regex = format.split('');
1075
1076     // check for literal values
1077     var quoteIndex = format.indexOf('\'');
1078     if (quoteIndex > -1) {
1079       var inLiteral = false;
1080       format = format.split('');
1081       for (var i = quoteIndex; i < format.length; i++) {
1082         if (inLiteral) {
1083           if (format[i] === '\'') {
1084             if (i + 1 < format.length && format[i+1] === '\'') { // escaped single quote
1085               format[i+1] = '$';
1086               regex[i+1] = '';
1087             } else { // end of literal
1088               regex[i] = '';
1089               inLiteral = false;
1090             }
1091           }
1092           format[i] = '$';
1093         } else {
1094           if (format[i] === '\'') { // start of literal
1095             format[i] = '$';
1096             regex[i] = '';
1097             inLiteral = true;
1098           }
1099         }
1100       }
1101
1102       format = format.join('');
1103     }
1104
1105     angular.forEach(formatCodeToRegex, function(data) {
1106       var index = format.indexOf(data.key);
1107
1108       if (index > -1) {
1109         format = format.split('');
1110
1111         regex[index] = '(' + data.regex + ')';
1112         format[index] = '$'; // Custom symbol to define consumed part of format
1113         for (var i = index + 1, n = index + data.key.length; i < n; i++) {
1114           regex[i] = '';
1115           format[i] = '$';
1116         }
1117         format = format.join('');
1118
1119         map.push({
1120           index: index,
1121           key: data.key,
1122           apply: data.apply,
1123           matcher: data.regex
1124         });
1125       }
1126     });
1127
1128     return {
1129       regex: new RegExp('^' + regex.join('') + '$'),
1130       map: orderByFilter(map, 'index')
1131     };
1132   }
1133
1134   function createFormatter(format) {
1135     var formatters = [];
1136     var i = 0;
1137     var formatter, literalIdx;
1138     while (i < format.length) {
1139       if (angular.isNumber(literalIdx)) {
1140         if (format.charAt(i) === '\'') {
1141           if (i + 1 >= format.length || format.charAt(i + 1) !== '\'') {
1142             formatters.push(constructLiteralFormatter(format, literalIdx, i));
1143             literalIdx = null;
1144           }
1145         } else if (i === format.length) {
1146           while (literalIdx < format.length) {
1147             formatter = constructFormatterFromIdx(format, literalIdx);
1148             formatters.push(formatter);
1149             literalIdx = formatter.endIdx;
1150           }
1151         }
1152
1153         i++;
1154         continue;
1155       }
1156
1157       if (format.charAt(i) === '\'') {
1158         literalIdx = i;
1159         i++;
1160         continue;
1161       }
1162
1163       formatter = constructFormatterFromIdx(format, i);
1164
1165       formatters.push(formatter.parser);
1166       i = formatter.endIdx;
1167     }
1168
1169     return formatters;
1170   }
1171
1172   function constructLiteralFormatter(format, literalIdx, endIdx) {
1173     return function() {
1174       return format.substr(literalIdx + 1, endIdx - literalIdx - 1);
1175     };
1176   }
1177
1178   function constructFormatterFromIdx(format, i) {
1179     var currentPosStr = format.substr(i);
1180     for (var j = 0; j < formatCodeToRegex.length; j++) {
1181       if (new RegExp('^' + formatCodeToRegex[j].key).test(currentPosStr)) {
1182         var data = formatCodeToRegex[j];
1183         return {
1184           endIdx: i + data.key.length,
1185           parser: data.formatter
1186         };
1187       }
1188     }
1189
1190     return {
1191       endIdx: i + 1,
1192       parser: function() {
1193         return currentPosStr.charAt(0);
1194       }
1195     };
1196   }
1197
1198   this.filter = function(date, format) {
1199     if (!angular.isDate(date) || isNaN(date) || !format) {
1200       return '';
1201     }
1202
1203     format = $locale.DATETIME_FORMATS[format] || format;
1204
1205     if ($locale.id !== localeId) {
1206       this.init();
1207     }
1208
1209     if (!this.formatters[format]) {
1210       this.formatters[format] = createFormatter(format);
1211     }
1212
1213     var formatters = this.formatters[format];
1214
1215     return formatters.reduce(function(str, formatter) {
1216       return str + formatter(date);
1217     }, '');
1218   };
1219
1220   this.parse = function(input, format, baseDate) {
1221     if (!angular.isString(input) || !format) {
1222       return input;
1223     }
1224
1225     format = $locale.DATETIME_FORMATS[format] || format;
1226     format = format.replace(SPECIAL_CHARACTERS_REGEXP, '\\$&');
1227
1228     if ($locale.id !== localeId) {
1229       this.init();
1230     }
1231
1232     if (!this.parsers[format]) {
1233       this.parsers[format] = createParser(format, 'apply');
1234     }
1235
1236     var parser = this.parsers[format],
1237         regex = parser.regex,
1238         map = parser.map,
1239         results = input.match(regex),
1240         tzOffset = false;
1241     if (results && results.length) {
1242       var fields, dt;
1243       if (angular.isDate(baseDate) && !isNaN(baseDate.getTime())) {
1244         fields = {
1245           year: baseDate.getFullYear(),
1246           month: baseDate.getMonth(),
1247           date: baseDate.getDate(),
1248           hours: baseDate.getHours(),
1249           minutes: baseDate.getMinutes(),
1250           seconds: baseDate.getSeconds(),
1251           milliseconds: baseDate.getMilliseconds()
1252         };
1253       } else {
1254         if (baseDate) {
1255           $log.warn('dateparser:', 'baseDate is not a valid date');
1256         }
1257         fields = { year: 1900, month: 0, date: 1, hours: 0, minutes: 0, seconds: 0, milliseconds: 0 };
1258       }
1259
1260       for (var i = 1, n = results.length; i < n; i++) {
1261         var mapper = map[i - 1];
1262         if (mapper.matcher === 'Z') {
1263           tzOffset = true;
1264         }
1265
1266         if (mapper.apply) {
1267           mapper.apply.call(fields, results[i]);
1268         }
1269       }
1270
1271       var datesetter = tzOffset ? Date.prototype.setUTCFullYear :
1272         Date.prototype.setFullYear;
1273       var timesetter = tzOffset ? Date.prototype.setUTCHours :
1274         Date.prototype.setHours;
1275
1276       if (isValid(fields.year, fields.month, fields.date)) {
1277         if (angular.isDate(baseDate) && !isNaN(baseDate.getTime()) && !tzOffset) {
1278           dt = new Date(baseDate);
1279           datesetter.call(dt, fields.year, fields.month, fields.date);
1280           timesetter.call(dt, fields.hours, fields.minutes,
1281             fields.seconds, fields.milliseconds);
1282         } else {
1283           dt = new Date(0);
1284           datesetter.call(dt, fields.year, fields.month, fields.date);
1285           timesetter.call(dt, fields.hours || 0, fields.minutes || 0,
1286             fields.seconds || 0, fields.milliseconds || 0);
1287         }
1288       }
1289
1290       return dt;
1291     }
1292   };
1293
1294   // Check if date is valid for specific month (and year for February).
1295   // Month: 0 = Jan, 1 = Feb, etc
1296   function isValid(year, month, date) {
1297     if (date < 1) {
1298       return false;
1299     }
1300
1301     if (month === 1 && date > 28) {
1302       return date === 29 && (year % 4 === 0 && year % 100 !== 0 || year % 400 === 0);
1303     }
1304
1305     if (month === 3 || month === 5 || month === 8 || month === 10) {
1306       return date < 31;
1307     }
1308
1309     return true;
1310   }
1311
1312   function toInt(str) {
1313     return parseInt(str, 10);
1314   }
1315
1316   this.toTimezone = toTimezone;
1317   this.fromTimezone = fromTimezone;
1318   this.timezoneToOffset = timezoneToOffset;
1319   this.addDateMinutes = addDateMinutes;
1320   this.convertTimezoneToLocal = convertTimezoneToLocal;
1321
1322   function toTimezone(date, timezone) {
1323     return date && timezone ? convertTimezoneToLocal(date, timezone) : date;
1324   }
1325
1326   function fromTimezone(date, timezone) {
1327     return date && timezone ? convertTimezoneToLocal(date, timezone, true) : date;
1328   }
1329
1330   //https://github.com/angular/angular.js/blob/622c42169699ec07fc6daaa19fe6d224e5d2f70e/src/Angular.js#L1207
1331   function timezoneToOffset(timezone, fallback) {
1332     timezone = timezone.replace(/:/g, '');
1333     var requestedTimezoneOffset = Date.parse('Jan 01, 1970 00:00:00 ' + timezone) / 60000;
1334     return isNaN(requestedTimezoneOffset) ? fallback : requestedTimezoneOffset;
1335   }
1336
1337   function addDateMinutes(date, minutes) {
1338     date = new Date(date.getTime());
1339     date.setMinutes(date.getMinutes() + minutes);
1340     return date;
1341   }
1342
1343   function convertTimezoneToLocal(date, timezone, reverse) {
1344     reverse = reverse ? -1 : 1;
1345     var dateTimezoneOffset = date.getTimezoneOffset();
1346     var timezoneOffset = timezoneToOffset(timezone, dateTimezoneOffset);
1347     return addDateMinutes(date, reverse * (timezoneOffset - dateTimezoneOffset));
1348   }
1349 }]);
1350
1351 // Avoiding use of ng-class as it creates a lot of watchers when a class is to be applied to
1352 // at most one element.
1353 angular.module('ui.bootstrap.isClass', [])
1354 .directive('uibIsClass', [
1355          '$animate',
1356 function ($animate) {
1357   //                    11111111          22222222
1358   var ON_REGEXP = /^\s*([\s\S]+?)\s+on\s+([\s\S]+?)\s*$/;
1359   //                    11111111           22222222
1360   var IS_REGEXP = /^\s*([\s\S]+?)\s+for\s+([\s\S]+?)\s*$/;
1361
1362   var dataPerTracked = {};
1363
1364   return {
1365     restrict: 'A',
1366     compile: function(tElement, tAttrs) {
1367       var linkedScopes = [];
1368       var instances = [];
1369       var expToData = {};
1370       var lastActivated = null;
1371       var onExpMatches = tAttrs.uibIsClass.match(ON_REGEXP);
1372       var onExp = onExpMatches[2];
1373       var expsStr = onExpMatches[1];
1374       var exps = expsStr.split(',');
1375
1376       return linkFn;
1377
1378       function linkFn(scope, element, attrs) {
1379         linkedScopes.push(scope);
1380         instances.push({
1381           scope: scope,
1382           element: element
1383         });
1384
1385         exps.forEach(function(exp, k) {
1386           addForExp(exp, scope);
1387         });
1388
1389         scope.$on('$destroy', removeScope);
1390       }
1391
1392       function addForExp(exp, scope) {
1393         var matches = exp.match(IS_REGEXP);
1394         var clazz = scope.$eval(matches[1]);
1395         var compareWithExp = matches[2];
1396         var data = expToData[exp];
1397         if (!data) {
1398           var watchFn = function(compareWithVal) {
1399             var newActivated = null;
1400             instances.some(function(instance) {
1401               var thisVal = instance.scope.$eval(onExp);
1402               if (thisVal === compareWithVal) {
1403                 newActivated = instance;
1404                 return true;
1405               }
1406             });
1407             if (data.lastActivated !== newActivated) {
1408               if (data.lastActivated) {
1409                 $animate.removeClass(data.lastActivated.element, clazz);
1410               }
1411               if (newActivated) {
1412                 $animate.addClass(newActivated.element, clazz);
1413               }
1414               data.lastActivated = newActivated;
1415             }
1416           };
1417           expToData[exp] = data = {
1418             lastActivated: null,
1419             scope: scope,
1420             watchFn: watchFn,
1421             compareWithExp: compareWithExp,
1422             watcher: scope.$watch(compareWithExp, watchFn)
1423           };
1424         }
1425         data.watchFn(scope.$eval(compareWithExp));
1426       }
1427
1428       function removeScope(e) {
1429         var removedScope = e.targetScope;
1430         var index = linkedScopes.indexOf(removedScope);
1431         linkedScopes.splice(index, 1);
1432         instances.splice(index, 1);
1433         if (linkedScopes.length) {
1434           var newWatchScope = linkedScopes[0];
1435           angular.forEach(expToData, function(data) {
1436             if (data.scope === removedScope) {
1437               data.watcher = newWatchScope.$watch(data.compareWithExp, data.watchFn);
1438               data.scope = newWatchScope;
1439             }
1440           });
1441         } else {
1442           expToData = {};
1443         }
1444       }
1445     }
1446   };
1447 }]);
1448 angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootstrap.isClass'])
1449
1450 .value('$datepickerSuppressError', false)
1451
1452 .value('$datepickerLiteralWarning', true)
1453
1454 .constant('uibDatepickerConfig', {
1455   datepickerMode: 'day',
1456   formatDay: 'dd',
1457   formatMonth: 'MMMM',
1458   formatYear: 'yyyy',
1459   formatDayHeader: 'EEE',
1460   formatDayTitle: 'MMMM yyyy',
1461   formatMonthTitle: 'yyyy',
1462   maxDate: null,
1463   maxMode: 'year',
1464   minDate: null,
1465   minMode: 'day',
1466   monthColumns: 3,
1467   ngModelOptions: {},
1468   shortcutPropagation: false,
1469   showWeeks: true,
1470   yearColumns: 5,
1471   yearRows: 4
1472 })
1473
1474 .controller('UibDatepickerController', ['$scope', '$element', '$attrs', '$parse', '$interpolate', '$locale', '$log', 'dateFilter', 'uibDatepickerConfig', '$datepickerLiteralWarning', '$datepickerSuppressError', 'uibDateParser',
1475   function($scope, $element, $attrs, $parse, $interpolate, $locale, $log, dateFilter, datepickerConfig, $datepickerLiteralWarning, $datepickerSuppressError, dateParser) {
1476   var self = this,
1477       ngModelCtrl = { $setViewValue: angular.noop }, // nullModelCtrl;
1478       ngModelOptions = {},
1479       watchListeners = [];
1480
1481   $element.addClass('uib-datepicker');
1482   $attrs.$set('role', 'application');
1483
1484   if (!$scope.datepickerOptions) {
1485     $scope.datepickerOptions = {};
1486   }
1487
1488   // Modes chain
1489   this.modes = ['day', 'month', 'year'];
1490
1491   [
1492     'customClass',
1493     'dateDisabled',
1494     'datepickerMode',
1495     'formatDay',
1496     'formatDayHeader',
1497     'formatDayTitle',
1498     'formatMonth',
1499     'formatMonthTitle',
1500     'formatYear',
1501     'maxDate',
1502     'maxMode',
1503     'minDate',
1504     'minMode',
1505     'monthColumns',
1506     'showWeeks',
1507     'shortcutPropagation',
1508     'startingDay',
1509     'yearColumns',
1510     'yearRows'
1511   ].forEach(function(key) {
1512     switch (key) {
1513       case 'customClass':
1514       case 'dateDisabled':
1515         $scope[key] = $scope.datepickerOptions[key] || angular.noop;
1516         break;
1517       case 'datepickerMode':
1518         $scope.datepickerMode = angular.isDefined($scope.datepickerOptions.datepickerMode) ?
1519           $scope.datepickerOptions.datepickerMode : datepickerConfig.datepickerMode;
1520         break;
1521       case 'formatDay':
1522       case 'formatDayHeader':
1523       case 'formatDayTitle':
1524       case 'formatMonth':
1525       case 'formatMonthTitle':
1526       case 'formatYear':
1527         self[key] = angular.isDefined($scope.datepickerOptions[key]) ?
1528           $interpolate($scope.datepickerOptions[key])($scope.$parent) :
1529           datepickerConfig[key];
1530         break;
1531       case 'monthColumns':
1532       case 'showWeeks':
1533       case 'shortcutPropagation':
1534       case 'yearColumns':
1535       case 'yearRows':
1536         self[key] = angular.isDefined($scope.datepickerOptions[key]) ?
1537           $scope.datepickerOptions[key] : datepickerConfig[key];
1538         break;
1539       case 'startingDay':
1540         if (angular.isDefined($scope.datepickerOptions.startingDay)) {
1541           self.startingDay = $scope.datepickerOptions.startingDay;
1542         } else if (angular.isNumber(datepickerConfig.startingDay)) {
1543           self.startingDay = datepickerConfig.startingDay;
1544         } else {
1545           self.startingDay = ($locale.DATETIME_FORMATS.FIRSTDAYOFWEEK + 8) % 7;
1546         }
1547
1548         break;
1549       case 'maxDate':
1550       case 'minDate':
1551         $scope.$watch('datepickerOptions.' + key, function(value) {
1552           if (value) {
1553             if (angular.isDate(value)) {
1554               self[key] = dateParser.fromTimezone(new Date(value), ngModelOptions.timezone);
1555             } else {
1556               if ($datepickerLiteralWarning) {
1557                 $log.warn('Literal date support has been deprecated, please switch to date object usage');
1558               }
1559
1560               self[key] = new Date(dateFilter(value, 'medium'));
1561             }
1562           } else {
1563             self[key] = datepickerConfig[key] ?
1564               dateParser.fromTimezone(new Date(datepickerConfig[key]), ngModelOptions.timezone) :
1565               null;
1566           }
1567
1568           self.refreshView();
1569         });
1570
1571         break;
1572       case 'maxMode':
1573       case 'minMode':
1574         if ($scope.datepickerOptions[key]) {
1575           $scope.$watch(function() { return $scope.datepickerOptions[key]; }, function(value) {
1576             self[key] = $scope[key] = angular.isDefined(value) ? value : $scope.datepickerOptions[key];
1577             if (key === 'minMode' && self.modes.indexOf($scope.datepickerOptions.datepickerMode) < self.modes.indexOf(self[key]) ||
1578               key === 'maxMode' && self.modes.indexOf($scope.datepickerOptions.datepickerMode) > self.modes.indexOf(self[key])) {
1579               $scope.datepickerMode = self[key];
1580               $scope.datepickerOptions.datepickerMode = self[key];
1581             }
1582           });
1583         } else {
1584           self[key] = $scope[key] = datepickerConfig[key] || null;
1585         }
1586
1587         break;
1588     }
1589   });
1590
1591   $scope.uniqueId = 'datepicker-' + $scope.$id + '-' + Math.floor(Math.random() * 10000);
1592
1593   $scope.disabled = angular.isDefined($attrs.disabled) || false;
1594   if (angular.isDefined($attrs.ngDisabled)) {
1595     watchListeners.push($scope.$parent.$watch($attrs.ngDisabled, function(disabled) {
1596       $scope.disabled = disabled;
1597       self.refreshView();
1598     }));
1599   }
1600
1601   $scope.isActive = function(dateObject) {
1602     if (self.compare(dateObject.date, self.activeDate) === 0) {
1603       $scope.activeDateId = dateObject.uid;
1604       return true;
1605     }
1606     return false;
1607   };
1608
1609   this.init = function(ngModelCtrl_) {
1610     ngModelCtrl = ngModelCtrl_;
1611     ngModelOptions = ngModelCtrl_.$options ||
1612       $scope.datepickerOptions.ngModelOptions ||
1613       datepickerConfig.ngModelOptions;
1614     if ($scope.datepickerOptions.initDate) {
1615       self.activeDate = dateParser.fromTimezone($scope.datepickerOptions.initDate, ngModelOptions.timezone) || new Date();
1616       $scope.$watch('datepickerOptions.initDate', function(initDate) {
1617         if (initDate && (ngModelCtrl.$isEmpty(ngModelCtrl.$modelValue) || ngModelCtrl.$invalid)) {
1618           self.activeDate = dateParser.fromTimezone(initDate, ngModelOptions.timezone);
1619           self.refreshView();
1620         }
1621       });
1622     } else {
1623       self.activeDate = new Date();
1624     }
1625
1626     var date = ngModelCtrl.$modelValue ? new Date(ngModelCtrl.$modelValue) : new Date();
1627     this.activeDate = !isNaN(date) ?
1628       dateParser.fromTimezone(date, ngModelOptions.timezone) :
1629       dateParser.fromTimezone(new Date(), ngModelOptions.timezone);
1630
1631     ngModelCtrl.$render = function() {
1632       self.render();
1633     };
1634   };
1635
1636   this.render = function() {
1637     if (ngModelCtrl.$viewValue) {
1638       var date = new Date(ngModelCtrl.$viewValue),
1639           isValid = !isNaN(date);
1640
1641       if (isValid) {
1642         this.activeDate = dateParser.fromTimezone(date, ngModelOptions.timezone);
1643       } else if (!$datepickerSuppressError) {
1644         $log.error('Datepicker directive: "ng-model" value must be a Date object');
1645       }
1646     }
1647     this.refreshView();
1648   };
1649
1650   this.refreshView = function() {
1651     if (this.element) {
1652       $scope.selectedDt = null;
1653       this._refreshView();
1654       if ($scope.activeDt) {
1655         $scope.activeDateId = $scope.activeDt.uid;
1656       }
1657
1658       var date = ngModelCtrl.$viewValue ? new Date(ngModelCtrl.$viewValue) : null;
1659       date = dateParser.fromTimezone(date, ngModelOptions.timezone);
1660       ngModelCtrl.$setValidity('dateDisabled', !date ||
1661         this.element && !this.isDisabled(date));
1662     }
1663   };
1664
1665   this.createDateObject = function(date, format) {
1666     var model = ngModelCtrl.$viewValue ? new Date(ngModelCtrl.$viewValue) : null;
1667     model = dateParser.fromTimezone(model, ngModelOptions.timezone);
1668     var today = new Date();
1669     today = dateParser.fromTimezone(today, ngModelOptions.timezone);
1670     var time = this.compare(date, today);
1671     var dt = {
1672       date: date,
1673       label: dateParser.filter(date, format),
1674       selected: model && this.compare(date, model) === 0,
1675       disabled: this.isDisabled(date),
1676       past: time < 0,
1677       current: time === 0,
1678       future: time > 0,
1679       customClass: this.customClass(date) || null
1680     };
1681
1682     if (model && this.compare(date, model) === 0) {
1683       $scope.selectedDt = dt;
1684     }
1685
1686     if (self.activeDate && this.compare(dt.date, self.activeDate) === 0) {
1687       $scope.activeDt = dt;
1688     }
1689
1690     return dt;
1691   };
1692
1693   this.isDisabled = function(date) {
1694     return $scope.disabled ||
1695       this.minDate && this.compare(date, this.minDate) < 0 ||
1696       this.maxDate && this.compare(date, this.maxDate) > 0 ||
1697       $scope.dateDisabled && $scope.dateDisabled({date: date, mode: $scope.datepickerMode});
1698   };
1699
1700   this.customClass = function(date) {
1701     return $scope.customClass({date: date, mode: $scope.datepickerMode});
1702   };
1703
1704   // Split array into smaller arrays
1705   this.split = function(arr, size) {
1706     var arrays = [];
1707     while (arr.length > 0) {
1708       arrays.push(arr.splice(0, size));
1709     }
1710     return arrays;
1711   };
1712
1713   $scope.select = function(date) {
1714     if ($scope.datepickerMode === self.minMode) {
1715       var dt = ngModelCtrl.$viewValue ? dateParser.fromTimezone(new Date(ngModelCtrl.$viewValue), ngModelOptions.timezone) : new Date(0, 0, 0, 0, 0, 0, 0);
1716       dt.setFullYear(date.getFullYear(), date.getMonth(), date.getDate());
1717       dt = dateParser.toTimezone(dt, ngModelOptions.timezone);
1718       ngModelCtrl.$setViewValue(dt);
1719       ngModelCtrl.$render();
1720     } else {
1721       self.activeDate = date;
1722       setMode(self.modes[self.modes.indexOf($scope.datepickerMode) - 1]);
1723
1724       $scope.$emit('uib:datepicker.mode');
1725     }
1726
1727     $scope.$broadcast('uib:datepicker.focus');
1728   };
1729
1730   $scope.move = function(direction) {
1731     var year = self.activeDate.getFullYear() + direction * (self.step.years || 0),
1732         month = self.activeDate.getMonth() + direction * (self.step.months || 0);
1733     self.activeDate.setFullYear(year, month, 1);
1734     self.refreshView();
1735   };
1736
1737   $scope.toggleMode = function(direction) {
1738     direction = direction || 1;
1739
1740     if ($scope.datepickerMode === self.maxMode && direction === 1 ||
1741       $scope.datepickerMode === self.minMode && direction === -1) {
1742       return;
1743     }
1744
1745     setMode(self.modes[self.modes.indexOf($scope.datepickerMode) + direction]);
1746
1747     $scope.$emit('uib:datepicker.mode');
1748   };
1749
1750   // Key event mapper
1751   $scope.keys = { 13: 'enter', 32: 'space', 33: 'pageup', 34: 'pagedown', 35: 'end', 36: 'home', 37: 'left', 38: 'up', 39: 'right', 40: 'down' };
1752
1753   var focusElement = function() {
1754     self.element[0].focus();
1755   };
1756
1757   // Listen for focus requests from popup directive
1758   $scope.$on('uib:datepicker.focus', focusElement);
1759
1760   $scope.keydown = function(evt) {
1761     var key = $scope.keys[evt.which];
1762
1763     if (!key || evt.shiftKey || evt.altKey || $scope.disabled) {
1764       return;
1765     }
1766
1767     evt.preventDefault();
1768     if (!self.shortcutPropagation) {
1769       evt.stopPropagation();
1770     }
1771
1772     if (key === 'enter' || key === 'space') {
1773       if (self.isDisabled(self.activeDate)) {
1774         return; // do nothing
1775       }
1776       $scope.select(self.activeDate);
1777     } else if (evt.ctrlKey && (key === 'up' || key === 'down')) {
1778       $scope.toggleMode(key === 'up' ? 1 : -1);
1779     } else {
1780       self.handleKeyDown(key, evt);
1781       self.refreshView();
1782     }
1783   };
1784
1785   $element.on('keydown', function(evt) {
1786     $scope.$apply(function() {
1787       $scope.keydown(evt);
1788     });
1789   });
1790
1791   $scope.$on('$destroy', function() {
1792     //Clear all watch listeners on destroy
1793     while (watchListeners.length) {
1794       watchListeners.shift()();
1795     }
1796   });
1797
1798   function setMode(mode) {
1799     $scope.datepickerMode = mode;
1800     $scope.datepickerOptions.datepickerMode = mode;
1801   }
1802 }])
1803
1804 .controller('UibDaypickerController', ['$scope', '$element', 'dateFilter', function(scope, $element, dateFilter) {
1805   var DAYS_IN_MONTH = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
1806
1807   this.step = { months: 1 };
1808   this.element = $element;
1809   function getDaysInMonth(year, month) {
1810     return month === 1 && year % 4 === 0 &&
1811       (year % 100 !== 0 || year % 400 === 0) ? 29 : DAYS_IN_MONTH[month];
1812   }
1813
1814   this.init = function(ctrl) {
1815     angular.extend(ctrl, this);
1816     scope.showWeeks = ctrl.showWeeks;
1817     ctrl.refreshView();
1818   };
1819
1820   this.getDates = function(startDate, n) {
1821     var dates = new Array(n), current = new Date(startDate), i = 0, date;
1822     while (i < n) {
1823       date = new Date(current);
1824       dates[i++] = date;
1825       current.setDate(current.getDate() + 1);
1826     }
1827     return dates;
1828   };
1829
1830   this._refreshView = function() {
1831     var year = this.activeDate.getFullYear(),
1832       month = this.activeDate.getMonth(),
1833       firstDayOfMonth = new Date(this.activeDate);
1834
1835     firstDayOfMonth.setFullYear(year, month, 1);
1836
1837     var difference = this.startingDay - firstDayOfMonth.getDay(),
1838       numDisplayedFromPreviousMonth = difference > 0 ?
1839         7 - difference : - difference,
1840       firstDate = new Date(firstDayOfMonth);
1841
1842     if (numDisplayedFromPreviousMonth > 0) {
1843       firstDate.setDate(-numDisplayedFromPreviousMonth + 1);
1844     }
1845
1846     // 42 is the number of days on a six-week calendar
1847     var days = this.getDates(firstDate, 42);
1848     for (var i = 0; i < 42; i ++) {
1849       days[i] = angular.extend(this.createDateObject(days[i], this.formatDay), {
1850         secondary: days[i].getMonth() !== month,
1851         uid: scope.uniqueId + '-' + i
1852       });
1853     }
1854
1855     scope.labels = new Array(7);
1856     for (var j = 0; j < 7; j++) {
1857       scope.labels[j] = {
1858         abbr: dateFilter(days[j].date, this.formatDayHeader),
1859         full: dateFilter(days[j].date, 'EEEE')
1860       };
1861     }
1862
1863     scope.title = dateFilter(this.activeDate, this.formatDayTitle);
1864     scope.rows = this.split(days, 7);
1865
1866     if (scope.showWeeks) {
1867       scope.weekNumbers = [];
1868       var thursdayIndex = (4 + 7 - this.startingDay) % 7,
1869           numWeeks = scope.rows.length;
1870       for (var curWeek = 0; curWeek < numWeeks; curWeek++) {
1871         scope.weekNumbers.push(
1872           getISO8601WeekNumber(scope.rows[curWeek][thursdayIndex].date));
1873       }
1874     }
1875   };
1876
1877   this.compare = function(date1, date2) {
1878     var _date1 = new Date(date1.getFullYear(), date1.getMonth(), date1.getDate());
1879     var _date2 = new Date(date2.getFullYear(), date2.getMonth(), date2.getDate());
1880     _date1.setFullYear(date1.getFullYear());
1881     _date2.setFullYear(date2.getFullYear());
1882     return _date1 - _date2;
1883   };
1884
1885   function getISO8601WeekNumber(date) {
1886     var checkDate = new Date(date);
1887     checkDate.setDate(checkDate.getDate() + 4 - (checkDate.getDay() || 7)); // Thursday
1888     var time = checkDate.getTime();
1889     checkDate.setMonth(0); // Compare with Jan 1
1890     checkDate.setDate(1);
1891     return Math.floor(Math.round((time - checkDate) / 86400000) / 7) + 1;
1892   }
1893
1894   this.handleKeyDown = function(key, evt) {
1895     var date = this.activeDate.getDate();
1896
1897     if (key === 'left') {
1898       date = date - 1;
1899     } else if (key === 'up') {
1900       date = date - 7;
1901     } else if (key === 'right') {
1902       date = date + 1;
1903     } else if (key === 'down') {
1904       date = date + 7;
1905     } else if (key === 'pageup' || key === 'pagedown') {
1906       var month = this.activeDate.getMonth() + (key === 'pageup' ? - 1 : 1);
1907       this.activeDate.setMonth(month, 1);
1908       date = Math.min(getDaysInMonth(this.activeDate.getFullYear(), this.activeDate.getMonth()), date);
1909     } else if (key === 'home') {
1910       date = 1;
1911     } else if (key === 'end') {
1912       date = getDaysInMonth(this.activeDate.getFullYear(), this.activeDate.getMonth());
1913     }
1914     this.activeDate.setDate(date);
1915   };
1916 }])
1917
1918 .controller('UibMonthpickerController', ['$scope', '$element', 'dateFilter', function(scope, $element, dateFilter) {
1919   this.step = { years: 1 };
1920   this.element = $element;
1921
1922   this.init = function(ctrl) {
1923     angular.extend(ctrl, this);
1924     ctrl.refreshView();
1925   };
1926
1927   this._refreshView = function() {
1928     var months = new Array(12),
1929         year = this.activeDate.getFullYear(),
1930         date;
1931
1932     for (var i = 0; i < 12; i++) {
1933       date = new Date(this.activeDate);
1934       date.setFullYear(year, i, 1);
1935       months[i] = angular.extend(this.createDateObject(date, this.formatMonth), {
1936         uid: scope.uniqueId + '-' + i
1937       });
1938     }
1939
1940     scope.title = dateFilter(this.activeDate, this.formatMonthTitle);
1941     scope.rows = this.split(months, this.monthColumns);
1942     scope.yearHeaderColspan = this.monthColumns > 3 ? this.monthColumns - 2 : 1;
1943   };
1944
1945   this.compare = function(date1, date2) {
1946     var _date1 = new Date(date1.getFullYear(), date1.getMonth());
1947     var _date2 = new Date(date2.getFullYear(), date2.getMonth());
1948     _date1.setFullYear(date1.getFullYear());
1949     _date2.setFullYear(date2.getFullYear());
1950     return _date1 - _date2;
1951   };
1952
1953   this.handleKeyDown = function(key, evt) {
1954     var date = this.activeDate.getMonth();
1955
1956     if (key === 'left') {
1957       date = date - 1;
1958     } else if (key === 'up') {
1959       date = date - this.monthColumns;
1960     } else if (key === 'right') {
1961       date = date + 1;
1962     } else if (key === 'down') {
1963       date = date + this.monthColumns;
1964     } else if (key === 'pageup' || key === 'pagedown') {
1965       var year = this.activeDate.getFullYear() + (key === 'pageup' ? - 1 : 1);
1966       this.activeDate.setFullYear(year);
1967     } else if (key === 'home') {
1968       date = 0;
1969     } else if (key === 'end') {
1970       date = 11;
1971     }
1972     this.activeDate.setMonth(date);
1973   };
1974 }])
1975
1976 .controller('UibYearpickerController', ['$scope', '$element', 'dateFilter', function(scope, $element, dateFilter) {
1977   var columns, range;
1978   this.element = $element;
1979
1980   function getStartingYear(year) {
1981     return parseInt((year - 1) / range, 10) * range + 1;
1982   }
1983
1984   this.yearpickerInit = function() {
1985     columns = this.yearColumns;
1986     range = this.yearRows * columns;
1987     this.step = { years: range };
1988   };
1989
1990   this._refreshView = function() {
1991     var years = new Array(range), date;
1992
1993     for (var i = 0, start = getStartingYear(this.activeDate.getFullYear()); i < range; i++) {
1994       date = new Date(this.activeDate);
1995       date.setFullYear(start + i, 0, 1);
1996       years[i] = angular.extend(this.createDateObject(date, this.formatYear), {
1997         uid: scope.uniqueId + '-' + i
1998       });
1999     }
2000
2001     scope.title = [years[0].label, years[range - 1].label].join(' - ');
2002     scope.rows = this.split(years, columns);
2003     scope.columns = columns;
2004   };
2005
2006   this.compare = function(date1, date2) {
2007     return date1.getFullYear() - date2.getFullYear();
2008   };
2009
2010   this.handleKeyDown = function(key, evt) {
2011     var date = this.activeDate.getFullYear();
2012
2013     if (key === 'left') {
2014       date = date - 1;
2015     } else if (key === 'up') {
2016       date = date - columns;
2017     } else if (key === 'right') {
2018       date = date + 1;
2019     } else if (key === 'down') {
2020       date = date + columns;
2021     } else if (key === 'pageup' || key === 'pagedown') {
2022       date += (key === 'pageup' ? - 1 : 1) * range;
2023     } else if (key === 'home') {
2024       date = getStartingYear(this.activeDate.getFullYear());
2025     } else if (key === 'end') {
2026       date = getStartingYear(this.activeDate.getFullYear()) + range - 1;
2027     }
2028     this.activeDate.setFullYear(date);
2029   };
2030 }])
2031
2032 .directive('uibDatepicker', function() {
2033   return {
2034     templateUrl: function(element, attrs) {
2035       return attrs.templateUrl || 'uib/template/datepicker/datepicker.html';
2036     },
2037     scope: {
2038       datepickerOptions: '=?'
2039     },
2040     require: ['uibDatepicker', '^ngModel'],
2041     restrict: 'A',
2042     controller: 'UibDatepickerController',
2043     controllerAs: 'datepicker',
2044     link: function(scope, element, attrs, ctrls) {
2045       var datepickerCtrl = ctrls[0], ngModelCtrl = ctrls[1];
2046
2047       datepickerCtrl.init(ngModelCtrl);
2048     }
2049   };
2050 })
2051
2052 .directive('uibDaypicker', function() {
2053   return {
2054     templateUrl: function(element, attrs) {
2055       return attrs.templateUrl || 'uib/template/datepicker/day.html';
2056     },
2057     require: ['^uibDatepicker', 'uibDaypicker'],
2058     restrict: 'A',
2059     controller: 'UibDaypickerController',
2060     link: function(scope, element, attrs, ctrls) {
2061       var datepickerCtrl = ctrls[0],
2062         daypickerCtrl = ctrls[1];
2063
2064       daypickerCtrl.init(datepickerCtrl);
2065     }
2066   };
2067 })
2068
2069 .directive('uibMonthpicker', function() {
2070   return {
2071     templateUrl: function(element, attrs) {
2072       return attrs.templateUrl || 'uib/template/datepicker/month.html';
2073     },
2074     require: ['^uibDatepicker', 'uibMonthpicker'],
2075     restrict: 'A',
2076     controller: 'UibMonthpickerController',
2077     link: function(scope, element, attrs, ctrls) {
2078       var datepickerCtrl = ctrls[0],
2079         monthpickerCtrl = ctrls[1];
2080
2081       monthpickerCtrl.init(datepickerCtrl);
2082     }
2083   };
2084 })
2085
2086 .directive('uibYearpicker', function() {
2087   return {
2088     templateUrl: function(element, attrs) {
2089       return attrs.templateUrl || 'uib/template/datepicker/year.html';
2090     },
2091     require: ['^uibDatepicker', 'uibYearpicker'],
2092     restrict: 'A',
2093     controller: 'UibYearpickerController',
2094     link: function(scope, element, attrs, ctrls) {
2095       var ctrl = ctrls[0];
2096       angular.extend(ctrl, ctrls[1]);
2097       ctrl.yearpickerInit();
2098
2099       ctrl.refreshView();
2100     }
2101   };
2102 });
2103
2104 angular.module('ui.bootstrap.position', [])
2105
2106 /**
2107  * A set of utility methods for working with the DOM.
2108  * It is meant to be used where we need to absolute-position elements in
2109  * relation to another element (this is the case for tooltips, popovers,
2110  * typeahead suggestions etc.).
2111  */
2112   .factory('$uibPosition', ['$document', '$window', function($document, $window) {
2113     /**
2114      * Used by scrollbarWidth() function to cache scrollbar's width.
2115      * Do not access this variable directly, use scrollbarWidth() instead.
2116      */
2117     var SCROLLBAR_WIDTH;
2118     /**
2119      * scrollbar on body and html element in IE and Edge overlay
2120      * content and should be considered 0 width.
2121      */
2122     var BODY_SCROLLBAR_WIDTH;
2123     var OVERFLOW_REGEX = {
2124       normal: /(auto|scroll)/,
2125       hidden: /(auto|scroll|hidden)/
2126     };
2127     var PLACEMENT_REGEX = {
2128       auto: /\s?auto?\s?/i,
2129       primary: /^(top|bottom|left|right)$/,
2130       secondary: /^(top|bottom|left|right|center)$/,
2131       vertical: /^(top|bottom)$/
2132     };
2133     var BODY_REGEX = /(HTML|BODY)/;
2134
2135     return {
2136
2137       /**
2138        * Provides a raw DOM element from a jQuery/jQLite element.
2139        *
2140        * @param {element} elem - The element to convert.
2141        *
2142        * @returns {element} A HTML element.
2143        */
2144       getRawNode: function(elem) {
2145         return elem.nodeName ? elem : elem[0] || elem;
2146       },
2147
2148       /**
2149        * Provides a parsed number for a style property.  Strips
2150        * units and casts invalid numbers to 0.
2151        *
2152        * @param {string} value - The style value to parse.
2153        *
2154        * @returns {number} A valid number.
2155        */
2156       parseStyle: function(value) {
2157         value = parseFloat(value);
2158         return isFinite(value) ? value : 0;
2159       },
2160
2161       /**
2162        * Provides the closest positioned ancestor.
2163        *
2164        * @param {element} element - The element to get the offest parent for.
2165        *
2166        * @returns {element} The closest positioned ancestor.
2167        */
2168       offsetParent: function(elem) {
2169         elem = this.getRawNode(elem);
2170
2171         var offsetParent = elem.offsetParent || $document[0].documentElement;
2172
2173         function isStaticPositioned(el) {
2174           return ($window.getComputedStyle(el).position || 'static') === 'static';
2175         }
2176
2177         while (offsetParent && offsetParent !== $document[0].documentElement && isStaticPositioned(offsetParent)) {
2178           offsetParent = offsetParent.offsetParent;
2179         }
2180
2181         return offsetParent || $document[0].documentElement;
2182       },
2183
2184       /**
2185        * Provides the scrollbar width, concept from TWBS measureScrollbar()
2186        * function in https://github.com/twbs/bootstrap/blob/master/js/modal.js
2187        * In IE and Edge, scollbar on body and html element overlay and should
2188        * return a width of 0.
2189        *
2190        * @returns {number} The width of the browser scollbar.
2191        */
2192       scrollbarWidth: function(isBody) {
2193         if (isBody) {
2194           if (angular.isUndefined(BODY_SCROLLBAR_WIDTH)) {
2195             var bodyElem = $document.find('body');
2196             bodyElem.addClass('uib-position-body-scrollbar-measure');
2197             BODY_SCROLLBAR_WIDTH = $window.innerWidth - bodyElem[0].clientWidth;
2198             BODY_SCROLLBAR_WIDTH = isFinite(BODY_SCROLLBAR_WIDTH) ? BODY_SCROLLBAR_WIDTH : 0;
2199             bodyElem.removeClass('uib-position-body-scrollbar-measure');
2200           }
2201           return BODY_SCROLLBAR_WIDTH;
2202         }
2203
2204         if (angular.isUndefined(SCROLLBAR_WIDTH)) {
2205           var scrollElem = angular.element('<div class="uib-position-scrollbar-measure"></div>');
2206           $document.find('body').append(scrollElem);
2207           SCROLLBAR_WIDTH = scrollElem[0].offsetWidth - scrollElem[0].clientWidth;
2208           SCROLLBAR_WIDTH = isFinite(SCROLLBAR_WIDTH) ? SCROLLBAR_WIDTH : 0;
2209           scrollElem.remove();
2210         }
2211
2212         return SCROLLBAR_WIDTH;
2213       },
2214
2215       /**
2216        * Provides the padding required on an element to replace the scrollbar.
2217        *
2218        * @returns {object} An object with the following properties:
2219        *   <ul>
2220        *     <li>**scrollbarWidth**: the width of the scrollbar</li>
2221        *     <li>**widthOverflow**: whether the the width is overflowing</li>
2222        *     <li>**right**: the amount of right padding on the element needed to replace the scrollbar</li>
2223        *     <li>**rightOriginal**: the amount of right padding currently on the element</li>
2224        *     <li>**heightOverflow**: whether the the height is overflowing</li>
2225        *     <li>**bottom**: the amount of bottom padding on the element needed to replace the scrollbar</li>
2226        *     <li>**bottomOriginal**: the amount of bottom padding currently on the element</li>
2227        *   </ul>
2228        */
2229       scrollbarPadding: function(elem) {
2230         elem = this.getRawNode(elem);
2231
2232         var elemStyle = $window.getComputedStyle(elem);
2233         var paddingRight = this.parseStyle(elemStyle.paddingRight);
2234         var paddingBottom = this.parseStyle(elemStyle.paddingBottom);
2235         var scrollParent = this.scrollParent(elem, false, true);
2236         var scrollbarWidth = this.scrollbarWidth(BODY_REGEX.test(scrollParent.tagName));
2237
2238         return {
2239           scrollbarWidth: scrollbarWidth,
2240           widthOverflow: scrollParent.scrollWidth > scrollParent.clientWidth,
2241           right: paddingRight + scrollbarWidth,
2242           originalRight: paddingRight,
2243           heightOverflow: scrollParent.scrollHeight > scrollParent.clientHeight,
2244           bottom: paddingBottom + scrollbarWidth,
2245           originalBottom: paddingBottom
2246          };
2247       },
2248
2249       /**
2250        * Checks to see if the element is scrollable.
2251        *
2252        * @param {element} elem - The element to check.
2253        * @param {boolean=} [includeHidden=false] - Should scroll style of 'hidden' be considered,
2254        *   default is false.
2255        *
2256        * @returns {boolean} Whether the element is scrollable.
2257        */
2258       isScrollable: function(elem, includeHidden) {
2259         elem = this.getRawNode(elem);
2260
2261         var overflowRegex = includeHidden ? OVERFLOW_REGEX.hidden : OVERFLOW_REGEX.normal;
2262         var elemStyle = $window.getComputedStyle(elem);
2263         return overflowRegex.test(elemStyle.overflow + elemStyle.overflowY + elemStyle.overflowX);
2264       },
2265
2266       /**
2267        * Provides the closest scrollable ancestor.
2268        * A port of the jQuery UI scrollParent method:
2269        * https://github.com/jquery/jquery-ui/blob/master/ui/scroll-parent.js
2270        *
2271        * @param {element} elem - The element to find the scroll parent of.
2272        * @param {boolean=} [includeHidden=false] - Should scroll style of 'hidden' be considered,
2273        *   default is false.
2274        * @param {boolean=} [includeSelf=false] - Should the element being passed be
2275        * included in the scrollable llokup.
2276        *
2277        * @returns {element} A HTML element.
2278        */
2279       scrollParent: function(elem, includeHidden, includeSelf) {
2280         elem = this.getRawNode(elem);
2281
2282         var overflowRegex = includeHidden ? OVERFLOW_REGEX.hidden : OVERFLOW_REGEX.normal;
2283         var documentEl = $document[0].documentElement;
2284         var elemStyle = $window.getComputedStyle(elem);
2285         if (includeSelf && overflowRegex.test(elemStyle.overflow + elemStyle.overflowY + elemStyle.overflowX)) {
2286           return elem;
2287         }
2288         var excludeStatic = elemStyle.position === 'absolute';
2289         var scrollParent = elem.parentElement || documentEl;
2290
2291         if (scrollParent === documentEl || elemStyle.position === 'fixed') {
2292           return documentEl;
2293         }
2294
2295         while (scrollParent.parentElement && scrollParent !== documentEl) {
2296           var spStyle = $window.getComputedStyle(scrollParent);
2297           if (excludeStatic && spStyle.position !== 'static') {
2298             excludeStatic = false;
2299           }
2300
2301           if (!excludeStatic && overflowRegex.test(spStyle.overflow + spStyle.overflowY + spStyle.overflowX)) {
2302             break;
2303           }
2304           scrollParent = scrollParent.parentElement;
2305         }
2306
2307         return scrollParent;
2308       },
2309
2310       /**
2311        * Provides read-only equivalent of jQuery's position function:
2312        * http://api.jquery.com/position/ - distance to closest positioned
2313        * ancestor.  Does not account for margins by default like jQuery position.
2314        *
2315        * @param {element} elem - The element to caclulate the position on.
2316        * @param {boolean=} [includeMargins=false] - Should margins be accounted
2317        * for, default is false.
2318        *
2319        * @returns {object} An object with the following properties:
2320        *   <ul>
2321        *     <li>**width**: the width of the element</li>
2322        *     <li>**height**: the height of the element</li>
2323        *     <li>**top**: distance to top edge of offset parent</li>
2324        *     <li>**left**: distance to left edge of offset parent</li>
2325        *   </ul>
2326        */
2327       position: function(elem, includeMagins) {
2328         elem = this.getRawNode(elem);
2329
2330         var elemOffset = this.offset(elem);
2331         if (includeMagins) {
2332           var elemStyle = $window.getComputedStyle(elem);
2333           elemOffset.top -= this.parseStyle(elemStyle.marginTop);
2334           elemOffset.left -= this.parseStyle(elemStyle.marginLeft);
2335         }
2336         var parent = this.offsetParent(elem);
2337         var parentOffset = {top: 0, left: 0};
2338
2339         if (parent !== $document[0].documentElement) {
2340           parentOffset = this.offset(parent);
2341           parentOffset.top += parent.clientTop - parent.scrollTop;
2342           parentOffset.left += parent.clientLeft - parent.scrollLeft;
2343         }
2344
2345         return {
2346           width: Math.round(angular.isNumber(elemOffset.width) ? elemOffset.width : elem.offsetWidth),
2347           height: Math.round(angular.isNumber(elemOffset.height) ? elemOffset.height : elem.offsetHeight),
2348           top: Math.round(elemOffset.top - parentOffset.top),
2349           left: Math.round(elemOffset.left - parentOffset.left)
2350         };
2351       },
2352
2353       /**
2354        * Provides read-only equivalent of jQuery's offset function:
2355        * http://api.jquery.com/offset/ - distance to viewport.  Does
2356        * not account for borders, margins, or padding on the body
2357        * element.
2358        *
2359        * @param {element} elem - The element to calculate the offset on.
2360        *
2361        * @returns {object} An object with the following properties:
2362        *   <ul>
2363        *     <li>**width**: the width of the element</li>
2364        *     <li>**height**: the height of the element</li>
2365        *     <li>**top**: distance to top edge of viewport</li>
2366        *     <li>**right**: distance to bottom edge of viewport</li>
2367        *   </ul>
2368        */
2369       offset: function(elem) {
2370         elem = this.getRawNode(elem);
2371
2372         var elemBCR = elem.getBoundingClientRect();
2373         return {
2374           width: Math.round(angular.isNumber(elemBCR.width) ? elemBCR.width : elem.offsetWidth),
2375           height: Math.round(angular.isNumber(elemBCR.height) ? elemBCR.height : elem.offsetHeight),
2376           top: Math.round(elemBCR.top + ($window.pageYOffset || $document[0].documentElement.scrollTop)),
2377           left: Math.round(elemBCR.left + ($window.pageXOffset || $document[0].documentElement.scrollLeft))
2378         };
2379       },
2380
2381       /**
2382        * Provides offset distance to the closest scrollable ancestor
2383        * or viewport.  Accounts for border and scrollbar width.
2384        *
2385        * Right and bottom dimensions represent the distance to the
2386        * respective edge of the viewport element.  If the element
2387        * edge extends beyond the viewport, a negative value will be
2388        * reported.
2389        *
2390        * @param {element} elem - The element to get the viewport offset for.
2391        * @param {boolean=} [useDocument=false] - Should the viewport be the document element instead
2392        * of the first scrollable element, default is false.
2393        * @param {boolean=} [includePadding=true] - Should the padding on the offset parent element
2394        * be accounted for, default is true.
2395        *
2396        * @returns {object} An object with the following properties:
2397        *   <ul>
2398        *     <li>**top**: distance to the top content edge of viewport element</li>
2399        *     <li>**bottom**: distance to the bottom content edge of viewport element</li>
2400        *     <li>**left**: distance to the left content edge of viewport element</li>
2401        *     <li>**right**: distance to the right content edge of viewport element</li>
2402        *   </ul>
2403        */
2404       viewportOffset: function(elem, useDocument, includePadding) {
2405         elem = this.getRawNode(elem);
2406         includePadding = includePadding !== false ? true : false;
2407
2408         var elemBCR = elem.getBoundingClientRect();
2409         var offsetBCR = {top: 0, left: 0, bottom: 0, right: 0};
2410
2411         var offsetParent = useDocument ? $document[0].documentElement : this.scrollParent(elem);
2412         var offsetParentBCR = offsetParent.getBoundingClientRect();
2413
2414         offsetBCR.top = offsetParentBCR.top + offsetParent.clientTop;
2415         offsetBCR.left = offsetParentBCR.left + offsetParent.clientLeft;
2416         if (offsetParent === $document[0].documentElement) {
2417           offsetBCR.top += $window.pageYOffset;
2418           offsetBCR.left += $window.pageXOffset;
2419         }
2420         offsetBCR.bottom = offsetBCR.top + offsetParent.clientHeight;
2421         offsetBCR.right = offsetBCR.left + offsetParent.clientWidth;
2422
2423         if (includePadding) {
2424           var offsetParentStyle = $window.getComputedStyle(offsetParent);
2425           offsetBCR.top += this.parseStyle(offsetParentStyle.paddingTop);
2426           offsetBCR.bottom -= this.parseStyle(offsetParentStyle.paddingBottom);
2427           offsetBCR.left += this.parseStyle(offsetParentStyle.paddingLeft);
2428           offsetBCR.right -= this.parseStyle(offsetParentStyle.paddingRight);
2429         }
2430
2431         return {
2432           top: Math.round(elemBCR.top - offsetBCR.top),
2433           bottom: Math.round(offsetBCR.bottom - elemBCR.bottom),
2434           left: Math.round(elemBCR.left - offsetBCR.left),
2435           right: Math.round(offsetBCR.right - elemBCR.right)
2436         };
2437       },
2438
2439       /**
2440        * Provides an array of placement values parsed from a placement string.
2441        * Along with the 'auto' indicator, supported placement strings are:
2442        *   <ul>
2443        *     <li>top: element on top, horizontally centered on host element.</li>
2444        *     <li>top-left: element on top, left edge aligned with host element left edge.</li>
2445        *     <li>top-right: element on top, lerightft edge aligned with host element right edge.</li>
2446        *     <li>bottom: element on bottom, horizontally centered on host element.</li>
2447        *     <li>bottom-left: element on bottom, left edge aligned with host element left edge.</li>
2448        *     <li>bottom-right: element on bottom, right edge aligned with host element right edge.</li>
2449        *     <li>left: element on left, vertically centered on host element.</li>
2450        *     <li>left-top: element on left, top edge aligned with host element top edge.</li>
2451        *     <li>left-bottom: element on left, bottom edge aligned with host element bottom edge.</li>
2452        *     <li>right: element on right, vertically centered on host element.</li>
2453        *     <li>right-top: element on right, top edge aligned with host element top edge.</li>
2454        *     <li>right-bottom: element on right, bottom edge aligned with host element bottom edge.</li>
2455        *   </ul>
2456        * A placement string with an 'auto' indicator is expected to be
2457        * space separated from the placement, i.e: 'auto bottom-left'  If
2458        * the primary and secondary placement values do not match 'top,
2459        * bottom, left, right' then 'top' will be the primary placement and
2460        * 'center' will be the secondary placement.  If 'auto' is passed, true
2461        * will be returned as the 3rd value of the array.
2462        *
2463        * @param {string} placement - The placement string to parse.
2464        *
2465        * @returns {array} An array with the following values
2466        * <ul>
2467        *   <li>**[0]**: The primary placement.</li>
2468        *   <li>**[1]**: The secondary placement.</li>
2469        *   <li>**[2]**: If auto is passed: true, else undefined.</li>
2470        * </ul>
2471        */
2472       parsePlacement: function(placement) {
2473         var autoPlace = PLACEMENT_REGEX.auto.test(placement);
2474         if (autoPlace) {
2475           placement = placement.replace(PLACEMENT_REGEX.auto, '');
2476         }
2477
2478         placement = placement.split('-');
2479
2480         placement[0] = placement[0] || 'top';
2481         if (!PLACEMENT_REGEX.primary.test(placement[0])) {
2482           placement[0] = 'top';
2483         }
2484
2485         placement[1] = placement[1] || 'center';
2486         if (!PLACEMENT_REGEX.secondary.test(placement[1])) {
2487           placement[1] = 'center';
2488         }
2489
2490         if (autoPlace) {
2491           placement[2] = true;
2492         } else {
2493           placement[2] = false;
2494         }
2495
2496         return placement;
2497       },
2498
2499       /**
2500        * Provides coordinates for an element to be positioned relative to
2501        * another element.  Passing 'auto' as part of the placement parameter
2502        * will enable smart placement - where the element fits. i.e:
2503        * 'auto left-top' will check to see if there is enough space to the left
2504        * of the hostElem to fit the targetElem, if not place right (same for secondary
2505        * top placement).  Available space is calculated using the viewportOffset
2506        * function.
2507        *
2508        * @param {element} hostElem - The element to position against.
2509        * @param {element} targetElem - The element to position.
2510        * @param {string=} [placement=top] - The placement for the targetElem,
2511        *   default is 'top'. 'center' is assumed as secondary placement for
2512        *   'top', 'left', 'right', and 'bottom' placements.  Available placements are:
2513        *   <ul>
2514        *     <li>top</li>
2515        *     <li>top-right</li>
2516        *     <li>top-left</li>
2517        *     <li>bottom</li>
2518        *     <li>bottom-left</li>
2519        *     <li>bottom-right</li>
2520        *     <li>left</li>
2521        *     <li>left-top</li>
2522        *     <li>left-bottom</li>
2523        *     <li>right</li>
2524        *     <li>right-top</li>
2525        *     <li>right-bottom</li>
2526        *   </ul>
2527        * @param {boolean=} [appendToBody=false] - Should the top and left values returned
2528        *   be calculated from the body element, default is false.
2529        *
2530        * @returns {object} An object with the following properties:
2531        *   <ul>
2532        *     <li>**top**: Value for targetElem top.</li>
2533        *     <li>**left**: Value for targetElem left.</li>
2534        *     <li>**placement**: The resolved placement.</li>
2535        *   </ul>
2536        */
2537       positionElements: function(hostElem, targetElem, placement, appendToBody) {
2538         hostElem = this.getRawNode(hostElem);
2539         targetElem = this.getRawNode(targetElem);
2540
2541         // need to read from prop to support tests.
2542         var targetWidth = angular.isDefined(targetElem.offsetWidth) ? targetElem.offsetWidth : targetElem.prop('offsetWidth');
2543         var targetHeight = angular.isDefined(targetElem.offsetHeight) ? targetElem.offsetHeight : targetElem.prop('offsetHeight');
2544
2545         placement = this.parsePlacement(placement);
2546
2547         var hostElemPos = appendToBody ? this.offset(hostElem) : this.position(hostElem);
2548         var targetElemPos = {top: 0, left: 0, placement: ''};
2549
2550         if (placement[2]) {
2551           var viewportOffset = this.viewportOffset(hostElem, appendToBody);
2552
2553           var targetElemStyle = $window.getComputedStyle(targetElem);
2554           var adjustedSize = {
2555             width: targetWidth + Math.round(Math.abs(this.parseStyle(targetElemStyle.marginLeft) + this.parseStyle(targetElemStyle.marginRight))),
2556             height: targetHeight + Math.round(Math.abs(this.parseStyle(targetElemStyle.marginTop) + this.parseStyle(targetElemStyle.marginBottom)))
2557           };
2558
2559           placement[0] = placement[0] === 'top' && adjustedSize.height > viewportOffset.top && adjustedSize.height <= viewportOffset.bottom ? 'bottom' :
2560                          placement[0] === 'bottom' && adjustedSize.height > viewportOffset.bottom && adjustedSize.height <= viewportOffset.top ? 'top' :
2561                          placement[0] === 'left' && adjustedSize.width > viewportOffset.left && adjustedSize.width <= viewportOffset.right ? 'right' :
2562                          placement[0] === 'right' && adjustedSize.width > viewportOffset.right && adjustedSize.width <= viewportOffset.left ? 'left' :
2563                          placement[0];
2564
2565           placement[1] = placement[1] === 'top' && adjustedSize.height - hostElemPos.height > viewportOffset.bottom && adjustedSize.height - hostElemPos.height <= viewportOffset.top ? 'bottom' :
2566                          placement[1] === 'bottom' && adjustedSize.height - hostElemPos.height > viewportOffset.top && adjustedSize.height - hostElemPos.height <= viewportOffset.bottom ? 'top' :
2567                          placement[1] === 'left' && adjustedSize.width - hostElemPos.width > viewportOffset.right && adjustedSize.width - hostElemPos.width <= viewportOffset.left ? 'right' :
2568                          placement[1] === 'right' && adjustedSize.width - hostElemPos.width > viewportOffset.left && adjustedSize.width - hostElemPos.width <= viewportOffset.right ? 'left' :
2569                          placement[1];
2570
2571           if (placement[1] === 'center') {
2572             if (PLACEMENT_REGEX.vertical.test(placement[0])) {
2573               var xOverflow = hostElemPos.width / 2 - targetWidth / 2;
2574               if (viewportOffset.left + xOverflow < 0 && adjustedSize.width - hostElemPos.width <= viewportOffset.right) {
2575                 placement[1] = 'left';
2576               } else if (viewportOffset.right + xOverflow < 0 && adjustedSize.width - hostElemPos.width <= viewportOffset.left) {
2577                 placement[1] = 'right';
2578               }
2579             } else {
2580               var yOverflow = hostElemPos.height / 2 - adjustedSize.height / 2;
2581               if (viewportOffset.top + yOverflow < 0 && adjustedSize.height - hostElemPos.height <= viewportOffset.bottom) {
2582                 placement[1] = 'top';
2583               } else if (viewportOffset.bottom + yOverflow < 0 && adjustedSize.height - hostElemPos.height <= viewportOffset.top) {
2584                 placement[1] = 'bottom';
2585               }
2586             }
2587           }
2588         }
2589
2590         switch (placement[0]) {
2591           case 'top':
2592             targetElemPos.top = hostElemPos.top - targetHeight;
2593             break;
2594           case 'bottom':
2595             targetElemPos.top = hostElemPos.top + hostElemPos.height;
2596             break;
2597           case 'left':
2598             targetElemPos.left = hostElemPos.left - targetWidth;
2599             break;
2600           case 'right':
2601             targetElemPos.left = hostElemPos.left + hostElemPos.width;
2602             break;
2603         }
2604
2605         switch (placement[1]) {
2606           case 'top':
2607             targetElemPos.top = hostElemPos.top;
2608             break;
2609           case 'bottom':
2610             targetElemPos.top = hostElemPos.top + hostElemPos.height - targetHeight;
2611             break;
2612           case 'left':
2613             targetElemPos.left = hostElemPos.left;
2614             break;
2615           case 'right':
2616             targetElemPos.left = hostElemPos.left + hostElemPos.width - targetWidth;
2617             break;
2618           case 'center':
2619             if (PLACEMENT_REGEX.vertical.test(placement[0])) {
2620               targetElemPos.left = hostElemPos.left + hostElemPos.width / 2 - targetWidth / 2;
2621             } else {
2622               targetElemPos.top = hostElemPos.top + hostElemPos.height / 2 - targetHeight / 2;
2623             }
2624             break;
2625         }
2626
2627         targetElemPos.top = Math.round(targetElemPos.top);
2628         targetElemPos.left = Math.round(targetElemPos.left);
2629         targetElemPos.placement = placement[1] === 'center' ? placement[0] : placement[0] + '-' + placement[1];
2630
2631         return targetElemPos;
2632       },
2633
2634       /**
2635        * Provides a way to adjust the top positioning after first
2636        * render to correctly align element to top after content
2637        * rendering causes resized element height
2638        *
2639        * @param {array} placementClasses - The array of strings of classes
2640        * element should have.
2641        * @param {object} containerPosition - The object with container
2642        * position information
2643        * @param {number} initialHeight - The initial height for the elem.
2644        * @param {number} currentHeight - The current height for the elem.
2645        */
2646       adjustTop: function(placementClasses, containerPosition, initialHeight, currentHeight) {
2647         if (placementClasses.indexOf('top') !== -1 && initialHeight !== currentHeight) {
2648           return {
2649             top: containerPosition.top - currentHeight + 'px'
2650           };
2651         }
2652       },
2653
2654       /**
2655        * Provides a way for positioning tooltip & dropdown
2656        * arrows when using placement options beyond the standard
2657        * left, right, top, or bottom.
2658        *
2659        * @param {element} elem - The tooltip/dropdown element.
2660        * @param {string} placement - The placement for the elem.
2661        */
2662       positionArrow: function(elem, placement) {
2663         elem = this.getRawNode(elem);
2664
2665         var innerElem = elem.querySelector('.tooltip-inner, .popover-inner');
2666         if (!innerElem) {
2667           return;
2668         }
2669
2670         var isTooltip = angular.element(innerElem).hasClass('tooltip-inner');
2671
2672         var arrowElem = isTooltip ? elem.querySelector('.tooltip-arrow') : elem.querySelector('.arrow');
2673         if (!arrowElem) {
2674           return;
2675         }
2676
2677         var arrowCss = {
2678           top: '',
2679           bottom: '',
2680           left: '',
2681           right: ''
2682         };
2683
2684         placement = this.parsePlacement(placement);
2685         if (placement[1] === 'center') {
2686           // no adjustment necessary - just reset styles
2687           angular.element(arrowElem).css(arrowCss);
2688           return;
2689         }
2690
2691         var borderProp = 'border-' + placement[0] + '-width';
2692         var borderWidth = $window.getComputedStyle(arrowElem)[borderProp];
2693
2694         var borderRadiusProp = 'border-';
2695         if (PLACEMENT_REGEX.vertical.test(placement[0])) {
2696           borderRadiusProp += placement[0] + '-' + placement[1];
2697         } else {
2698           borderRadiusProp += placement[1] + '-' + placement[0];
2699         }
2700         borderRadiusProp += '-radius';
2701         var borderRadius = $window.getComputedStyle(isTooltip ? innerElem : elem)[borderRadiusProp];
2702
2703         switch (placement[0]) {
2704           case 'top':
2705             arrowCss.bottom = isTooltip ? '0' : '-' + borderWidth;
2706             break;
2707           case 'bottom':
2708             arrowCss.top = isTooltip ? '0' : '-' + borderWidth;
2709             break;
2710           case 'left':
2711             arrowCss.right = isTooltip ? '0' : '-' + borderWidth;
2712             break;
2713           case 'right':
2714             arrowCss.left = isTooltip ? '0' : '-' + borderWidth;
2715             break;
2716         }
2717
2718         arrowCss[placement[1]] = borderRadius;
2719
2720         angular.element(arrowElem).css(arrowCss);
2721       }
2722     };
2723   }]);
2724
2725 angular.module('ui.bootstrap.datepickerPopup', ['ui.bootstrap.datepicker', 'ui.bootstrap.position'])
2726
2727 .value('$datepickerPopupLiteralWarning', true)
2728
2729 .constant('uibDatepickerPopupConfig', {
2730   altInputFormats: [],
2731   appendToBody: false,
2732   clearText: 'Clear',
2733   closeOnDateSelection: true,
2734   closeText: 'Done',
2735   currentText: 'Today',
2736   datepickerPopup: 'yyyy-MM-dd',
2737   datepickerPopupTemplateUrl: 'uib/template/datepickerPopup/popup.html',
2738   datepickerTemplateUrl: 'uib/template/datepicker/datepicker.html',
2739   html5Types: {
2740     date: 'yyyy-MM-dd',
2741     'datetime-local': 'yyyy-MM-ddTHH:mm:ss.sss',
2742     'month': 'yyyy-MM'
2743   },
2744   onOpenFocus: true,
2745   showButtonBar: true,
2746   placement: 'auto bottom-left'
2747 })
2748
2749 .controller('UibDatepickerPopupController', ['$scope', '$element', '$attrs', '$compile', '$log', '$parse', '$window', '$document', '$rootScope', '$uibPosition', 'dateFilter', 'uibDateParser', 'uibDatepickerPopupConfig', '$timeout', 'uibDatepickerConfig', '$datepickerPopupLiteralWarning',
2750 function($scope, $element, $attrs, $compile, $log, $parse, $window, $document, $rootScope, $position, dateFilter, dateParser, datepickerPopupConfig, $timeout, datepickerConfig, $datepickerPopupLiteralWarning) {
2751   var cache = {},
2752     isHtml5DateInput = false;
2753   var dateFormat, closeOnDateSelection, appendToBody, onOpenFocus,
2754     datepickerPopupTemplateUrl, datepickerTemplateUrl, popupEl, datepickerEl, scrollParentEl,
2755     ngModel, ngModelOptions, $popup, altInputFormats, watchListeners = [];
2756
2757   this.init = function(_ngModel_) {
2758     ngModel = _ngModel_;
2759     ngModelOptions = angular.isObject(_ngModel_.$options) ?
2760       _ngModel_.$options :
2761       {
2762         timezone: null
2763       };
2764     closeOnDateSelection = angular.isDefined($attrs.closeOnDateSelection) ?
2765       $scope.$parent.$eval($attrs.closeOnDateSelection) :
2766       datepickerPopupConfig.closeOnDateSelection;
2767     appendToBody = angular.isDefined($attrs.datepickerAppendToBody) ?
2768       $scope.$parent.$eval($attrs.datepickerAppendToBody) :
2769       datepickerPopupConfig.appendToBody;
2770     onOpenFocus = angular.isDefined($attrs.onOpenFocus) ?
2771       $scope.$parent.$eval($attrs.onOpenFocus) : datepickerPopupConfig.onOpenFocus;
2772     datepickerPopupTemplateUrl = angular.isDefined($attrs.datepickerPopupTemplateUrl) ?
2773       $attrs.datepickerPopupTemplateUrl :
2774       datepickerPopupConfig.datepickerPopupTemplateUrl;
2775     datepickerTemplateUrl = angular.isDefined($attrs.datepickerTemplateUrl) ?
2776       $attrs.datepickerTemplateUrl : datepickerPopupConfig.datepickerTemplateUrl;
2777     altInputFormats = angular.isDefined($attrs.altInputFormats) ?
2778       $scope.$parent.$eval($attrs.altInputFormats) :
2779       datepickerPopupConfig.altInputFormats;
2780
2781     $scope.showButtonBar = angular.isDefined($attrs.showButtonBar) ?
2782       $scope.$parent.$eval($attrs.showButtonBar) :
2783       datepickerPopupConfig.showButtonBar;
2784
2785     if (datepickerPopupConfig.html5Types[$attrs.type]) {
2786       dateFormat = datepickerPopupConfig.html5Types[$attrs.type];
2787       isHtml5DateInput = true;
2788     } else {
2789       dateFormat = $attrs.uibDatepickerPopup || datepickerPopupConfig.datepickerPopup;
2790       $attrs.$observe('uibDatepickerPopup', function(value, oldValue) {
2791         var newDateFormat = value || datepickerPopupConfig.datepickerPopup;
2792         // Invalidate the $modelValue to ensure that formatters re-run
2793         // FIXME: Refactor when PR is merged: https://github.com/angular/angular.js/pull/10764
2794         if (newDateFormat !== dateFormat) {
2795           dateFormat = newDateFormat;
2796           ngModel.$modelValue = null;
2797
2798           if (!dateFormat) {
2799             throw new Error('uibDatepickerPopup must have a date format specified.');
2800           }
2801         }
2802       });
2803     }
2804
2805     if (!dateFormat) {
2806       throw new Error('uibDatepickerPopup must have a date format specified.');
2807     }
2808
2809     if (isHtml5DateInput && $attrs.uibDatepickerPopup) {
2810       throw new Error('HTML5 date input types do not support custom formats.');
2811     }
2812
2813     // popup element used to display calendar
2814     popupEl = angular.element('<div uib-datepicker-popup-wrap><div uib-datepicker></div></div>');
2815
2816     popupEl.attr({
2817       'ng-model': 'date',
2818       'ng-change': 'dateSelection(date)',
2819       'template-url': datepickerPopupTemplateUrl
2820     });
2821
2822     // datepicker element
2823     datepickerEl = angular.element(popupEl.children()[0]);
2824     datepickerEl.attr('template-url', datepickerTemplateUrl);
2825
2826     if (!$scope.datepickerOptions) {
2827       $scope.datepickerOptions = {};
2828     }
2829
2830     if (isHtml5DateInput) {
2831       if ($attrs.type === 'month') {
2832         $scope.datepickerOptions.datepickerMode = 'month';
2833         $scope.datepickerOptions.minMode = 'month';
2834       }
2835     }
2836
2837     datepickerEl.attr('datepicker-options', 'datepickerOptions');
2838
2839     if (!isHtml5DateInput) {
2840       // Internal API to maintain the correct ng-invalid-[key] class
2841       ngModel.$$parserName = 'date';
2842       ngModel.$validators.date = validator;
2843       ngModel.$parsers.unshift(parseDate);
2844       ngModel.$formatters.push(function(value) {
2845         if (ngModel.$isEmpty(value)) {
2846           $scope.date = value;
2847           return value;
2848         }
2849
2850         if (angular.isNumber(value)) {
2851           value = new Date(value);
2852         }
2853
2854         $scope.date = dateParser.fromTimezone(value, ngModelOptions.timezone);
2855
2856         return dateParser.filter($scope.date, dateFormat);
2857       });
2858     } else {
2859       ngModel.$formatters.push(function(value) {
2860         $scope.date = dateParser.fromTimezone(value, ngModelOptions.timezone);
2861         return value;
2862       });
2863     }
2864
2865     // Detect changes in the view from the text box
2866     ngModel.$viewChangeListeners.push(function() {
2867       $scope.date = parseDateString(ngModel.$viewValue);
2868     });
2869
2870     $element.on('keydown', inputKeydownBind);
2871
2872     $popup = $compile(popupEl)($scope);
2873     // Prevent jQuery cache memory leak (template is now redundant after linking)
2874     popupEl.remove();
2875
2876     if (appendToBody) {
2877       $document.find('body').append($popup);
2878     } else {
2879       $element.after($popup);
2880     }
2881
2882     $scope.$on('$destroy', function() {
2883       if ($scope.isOpen === true) {
2884         if (!$rootScope.$$phase) {
2885           $scope.$apply(function() {
2886             $scope.isOpen = false;
2887           });
2888         }
2889       }
2890
2891       $popup.remove();
2892       $element.off('keydown', inputKeydownBind);
2893       $document.off('click', documentClickBind);
2894       if (scrollParentEl) {
2895         scrollParentEl.off('scroll', positionPopup);
2896       }
2897       angular.element($window).off('resize', positionPopup);
2898
2899       //Clear all watch listeners on destroy
2900       while (watchListeners.length) {
2901         watchListeners.shift()();
2902       }
2903     });
2904   };
2905
2906   $scope.getText = function(key) {
2907     return $scope[key + 'Text'] || datepickerPopupConfig[key + 'Text'];
2908   };
2909
2910   $scope.isDisabled = function(date) {
2911     if (date === 'today') {
2912       date = dateParser.fromTimezone(new Date(), ngModelOptions.timezone);
2913     }
2914
2915     var dates = {};
2916     angular.forEach(['minDate', 'maxDate'], function(key) {
2917       if (!$scope.datepickerOptions[key]) {
2918         dates[key] = null;
2919       } else if (angular.isDate($scope.datepickerOptions[key])) {
2920         dates[key] = new Date($scope.datepickerOptions[key]);
2921       } else {
2922         if ($datepickerPopupLiteralWarning) {
2923           $log.warn('Literal date support has been deprecated, please switch to date object usage');
2924         }
2925
2926         dates[key] = new Date(dateFilter($scope.datepickerOptions[key], 'medium'));
2927       }
2928     });
2929
2930     return $scope.datepickerOptions &&
2931       dates.minDate && $scope.compare(date, dates.minDate) < 0 ||
2932       dates.maxDate && $scope.compare(date, dates.maxDate) > 0;
2933   };
2934
2935   $scope.compare = function(date1, date2) {
2936     return new Date(date1.getFullYear(), date1.getMonth(), date1.getDate()) - new Date(date2.getFullYear(), date2.getMonth(), date2.getDate());
2937   };
2938
2939   // Inner change
2940   $scope.dateSelection = function(dt) {
2941     $scope.date = dt;
2942     var date = $scope.date ? dateParser.filter($scope.date, dateFormat) : null; // Setting to NULL is necessary for form validators to function
2943     $element.val(date);
2944     ngModel.$setViewValue(date);
2945
2946     if (closeOnDateSelection) {
2947       $scope.isOpen = false;
2948       $element[0].focus();
2949     }
2950   };
2951
2952   $scope.keydown = function(evt) {
2953     if (evt.which === 27) {
2954       evt.stopPropagation();
2955       $scope.isOpen = false;
2956       $element[0].focus();
2957     }
2958   };
2959
2960   $scope.select = function(date, evt) {
2961     evt.stopPropagation();
2962
2963     if (date === 'today') {
2964       var today = new Date();
2965       if (angular.isDate($scope.date)) {
2966         date = new Date($scope.date);
2967         date.setFullYear(today.getFullYear(), today.getMonth(), today.getDate());
2968       } else {
2969         date = dateParser.fromTimezone(today, ngModelOptions.timezone);
2970         date.setHours(0, 0, 0, 0);
2971       }
2972     }
2973     $scope.dateSelection(date);
2974   };
2975
2976   $scope.close = function(evt) {
2977     evt.stopPropagation();
2978
2979     $scope.isOpen = false;
2980     $element[0].focus();
2981   };
2982
2983   $scope.disabled = angular.isDefined($attrs.disabled) || false;
2984   if ($attrs.ngDisabled) {
2985     watchListeners.push($scope.$parent.$watch($parse($attrs.ngDisabled), function(disabled) {
2986       $scope.disabled = disabled;
2987     }));
2988   }
2989
2990   $scope.$watch('isOpen', function(value) {
2991     if (value) {
2992       if (!$scope.disabled) {
2993         $timeout(function() {
2994           positionPopup();
2995
2996           if (onOpenFocus) {
2997             $scope.$broadcast('uib:datepicker.focus');
2998           }
2999
3000           $document.on('click', documentClickBind);
3001
3002           var placement = $attrs.popupPlacement ? $attrs.popupPlacement : datepickerPopupConfig.placement;
3003           if (appendToBody || $position.parsePlacement(placement)[2]) {
3004             scrollParentEl = scrollParentEl || angular.element($position.scrollParent($element));
3005             if (scrollParentEl) {
3006               scrollParentEl.on('scroll', positionPopup);
3007             }
3008           } else {
3009             scrollParentEl = null;
3010           }
3011
3012           angular.element($window).on('resize', positionPopup);
3013         }, 0, false);
3014       } else {
3015         $scope.isOpen = false;
3016       }
3017     } else {
3018       $document.off('click', documentClickBind);
3019       if (scrollParentEl) {
3020         scrollParentEl.off('scroll', positionPopup);
3021       }
3022       angular.element($window).off('resize', positionPopup);
3023     }
3024   });
3025
3026   function cameltoDash(string) {
3027     return string.replace(/([A-Z])/g, function($1) { return '-' + $1.toLowerCase(); });
3028   }
3029
3030   function parseDateString(viewValue) {
3031     var date = dateParser.parse(viewValue, dateFormat, $scope.date);
3032     if (isNaN(date)) {
3033       for (var i = 0; i < altInputFormats.length; i++) {
3034         date = dateParser.parse(viewValue, altInputFormats[i], $scope.date);
3035         if (!isNaN(date)) {
3036           return date;
3037         }
3038       }
3039     }
3040     return date;
3041   }
3042
3043   function parseDate(viewValue) {
3044     if (angular.isNumber(viewValue)) {
3045       // presumably timestamp to date object
3046       viewValue = new Date(viewValue);
3047     }
3048
3049     if (!viewValue) {
3050       return null;
3051     }
3052
3053     if (angular.isDate(viewValue) && !isNaN(viewValue)) {
3054       return viewValue;
3055     }
3056
3057     if (angular.isString(viewValue)) {
3058       var date = parseDateString(viewValue);
3059       if (!isNaN(date)) {
3060         return dateParser.toTimezone(date, ngModelOptions.timezone);
3061       }
3062     }
3063
3064     return ngModel.$options && ngModel.$options.allowInvalid ? viewValue : undefined;
3065   }
3066
3067   function validator(modelValue, viewValue) {
3068     var value = modelValue || viewValue;
3069
3070     if (!$attrs.ngRequired && !value) {
3071       return true;
3072     }
3073
3074     if (angular.isNumber(value)) {
3075       value = new Date(value);
3076     }
3077
3078     if (!value) {
3079       return true;
3080     }
3081
3082     if (angular.isDate(value) && !isNaN(value)) {
3083       return true;
3084     }
3085
3086     if (angular.isString(value)) {
3087       return !isNaN(parseDateString(value));
3088     }
3089
3090     return false;
3091   }
3092
3093   function documentClickBind(event) {
3094     if (!$scope.isOpen && $scope.disabled) {
3095       return;
3096     }
3097
3098     var popup = $popup[0];
3099     var dpContainsTarget = $element[0].contains(event.target);
3100     // The popup node may not be an element node
3101     // In some browsers (IE) only element nodes have the 'contains' function
3102     var popupContainsTarget = popup.contains !== undefined && popup.contains(event.target);
3103     if ($scope.isOpen && !(dpContainsTarget || popupContainsTarget)) {
3104       $scope.$apply(function() {
3105         $scope.isOpen = false;
3106       });
3107     }
3108   }
3109
3110   function inputKeydownBind(evt) {
3111     if (evt.which === 27 && $scope.isOpen) {
3112       evt.preventDefault();
3113       evt.stopPropagation();
3114       $scope.$apply(function() {
3115         $scope.isOpen = false;
3116       });
3117       $element[0].focus();
3118     } else if (evt.which === 40 && !$scope.isOpen) {
3119       evt.preventDefault();
3120       evt.stopPropagation();
3121       $scope.$apply(function() {
3122         $scope.isOpen = true;
3123       });
3124     }
3125   }
3126
3127   function positionPopup() {
3128     if ($scope.isOpen) {
3129       var dpElement = angular.element($popup[0].querySelector('.uib-datepicker-popup'));
3130       var placement = $attrs.popupPlacement ? $attrs.popupPlacement : datepickerPopupConfig.placement;
3131       var position = $position.positionElements($element, dpElement, placement, appendToBody);
3132       dpElement.css({top: position.top + 'px', left: position.left + 'px'});
3133       if (dpElement.hasClass('uib-position-measure')) {
3134         dpElement.removeClass('uib-position-measure');
3135       }
3136     }
3137   }
3138
3139   $scope.$on('uib:datepicker.mode', function() {
3140     $timeout(positionPopup, 0, false);
3141   });
3142 }])
3143
3144 .directive('uibDatepickerPopup', function() {
3145   return {
3146     require: ['ngModel', 'uibDatepickerPopup'],
3147     controller: 'UibDatepickerPopupController',
3148     scope: {
3149       datepickerOptions: '=?',
3150       isOpen: '=?',
3151       currentText: '@',
3152       clearText: '@',
3153       closeText: '@'
3154     },
3155     link: function(scope, element, attrs, ctrls) {
3156       var ngModel = ctrls[0],
3157         ctrl = ctrls[1];
3158
3159       ctrl.init(ngModel);
3160     }
3161   };
3162 })
3163
3164 .directive('uibDatepickerPopupWrap', function() {
3165   return {
3166     restrict: 'A',
3167     transclude: true,
3168     templateUrl: function(element, attrs) {
3169       return attrs.templateUrl || 'uib/template/datepickerPopup/popup.html';
3170     }
3171   };
3172 });
3173
3174 angular.module('ui.bootstrap.debounce', [])
3175 /**
3176  * A helper, internal service that debounces a function
3177  */
3178   .factory('$$debounce', ['$timeout', function($timeout) {
3179     return function(callback, debounceTime) {
3180       var timeoutPromise;
3181
3182       return function() {
3183         var self = this;
3184         var args = Array.prototype.slice.call(arguments);
3185         if (timeoutPromise) {
3186           $timeout.cancel(timeoutPromise);
3187         }
3188
3189         timeoutPromise = $timeout(function() {
3190           callback.apply(self, args);
3191         }, debounceTime);
3192       };
3193     };
3194   }]);
3195
3196 angular.module('ui.bootstrap.multiMap', [])
3197 /**
3198  * A helper, internal data structure that stores all references attached to key
3199  */
3200   .factory('$$multiMap', function() {
3201     return {
3202       createNew: function() {
3203         var map = {};
3204
3205         return {
3206           entries: function() {
3207             return Object.keys(map).map(function(key) {
3208               return {
3209                 key: key,
3210                 value: map[key]
3211               };
3212             });
3213           },
3214           get: function(key) {
3215             return map[key];
3216           },
3217           hasKey: function(key) {
3218             return !!map[key];
3219           },
3220           keys: function() {
3221             return Object.keys(map);
3222           },
3223           put: function(key, value) {
3224             if (!map[key]) {
3225               map[key] = [];
3226             }
3227
3228             map[key].push(value);
3229           },
3230           remove: function(key, value) {
3231             var values = map[key];
3232
3233             if (!values) {
3234               return;
3235             }
3236
3237             var idx = values.indexOf(value);
3238
3239             if (idx !== -1) {
3240               values.splice(idx, 1);
3241             }
3242
3243             if (!values.length) {
3244               delete map[key];
3245             }
3246           }
3247         };
3248       }
3249     };
3250   });
3251
3252 angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.multiMap', 'ui.bootstrap.position'])
3253
3254 .constant('uibDropdownConfig', {
3255   appendToOpenClass: 'uib-dropdown-open',
3256   openClass: 'open'
3257 })
3258
3259 .service('uibDropdownService', ['$document', '$rootScope', '$$multiMap', function($document, $rootScope, $$multiMap) {
3260   var openScope = null;
3261   var openedContainers = $$multiMap.createNew();
3262
3263   this.isOnlyOpen = function(dropdownScope, appendTo) {
3264     var openedDropdowns = openedContainers.get(appendTo);
3265     if (openedDropdowns) {
3266       var openDropdown = openedDropdowns.reduce(function(toClose, dropdown) {
3267         if (dropdown.scope === dropdownScope) {
3268           return dropdown;
3269         }
3270
3271         return toClose;
3272       }, {});
3273       if (openDropdown) {
3274         return openedDropdowns.length === 1;
3275       }
3276     }
3277
3278     return false;
3279   };
3280
3281   this.open = function(dropdownScope, element, appendTo) {
3282     if (!openScope) {
3283       $document.on('click', closeDropdown);
3284     }
3285
3286     if (openScope && openScope !== dropdownScope) {
3287       openScope.isOpen = false;
3288     }
3289
3290     openScope = dropdownScope;
3291
3292     if (!appendTo) {
3293       return;
3294     }
3295
3296     var openedDropdowns = openedContainers.get(appendTo);
3297     if (openedDropdowns) {
3298       var openedScopes = openedDropdowns.map(function(dropdown) {
3299         return dropdown.scope;
3300       });
3301       if (openedScopes.indexOf(dropdownScope) === -1) {
3302         openedContainers.put(appendTo, {
3303           scope: dropdownScope
3304         });
3305       }
3306     } else {
3307       openedContainers.put(appendTo, {
3308         scope: dropdownScope
3309       });
3310     }
3311   };
3312
3313   this.close = function(dropdownScope, element, appendTo) {
3314     if (openScope === dropdownScope) {
3315       $document.off('click', closeDropdown);
3316       $document.off('keydown', this.keybindFilter);
3317       openScope = null;
3318     }
3319
3320     if (!appendTo) {
3321       return;
3322     }
3323
3324     var openedDropdowns = openedContainers.get(appendTo);
3325     if (openedDropdowns) {
3326       var dropdownToClose = openedDropdowns.reduce(function(toClose, dropdown) {
3327         if (dropdown.scope === dropdownScope) {
3328           return dropdown;
3329         }
3330
3331         return toClose;
3332       }, {});
3333       if (dropdownToClose) {
3334         openedContainers.remove(appendTo, dropdownToClose);
3335       }
3336     }
3337   };
3338
3339   var closeDropdown = function(evt) {
3340     // This method may still be called during the same mouse event that
3341     // unbound this event handler. So check openScope before proceeding.
3342     if (!openScope) { return; }
3343
3344     if (evt && openScope.getAutoClose() === 'disabled') { return; }
3345
3346     if (evt && evt.which === 3) { return; }
3347
3348     var toggleElement = openScope.getToggleElement();
3349     if (evt && toggleElement && toggleElement[0].contains(evt.target)) {
3350       return;
3351     }
3352
3353     var dropdownElement = openScope.getDropdownElement();
3354     if (evt && openScope.getAutoClose() === 'outsideClick' &&
3355       dropdownElement && dropdownElement[0].contains(evt.target)) {
3356       return;
3357     }
3358
3359     openScope.focusToggleElement();
3360     openScope.isOpen = false;
3361
3362     if (!$rootScope.$$phase) {
3363       openScope.$apply();
3364     }
3365   };
3366
3367   this.keybindFilter = function(evt) {
3368     if (!openScope) {
3369       // see this.close as ESC could have been pressed which kills the scope so we can not proceed
3370       return;
3371     }
3372
3373     var dropdownElement = openScope.getDropdownElement();
3374     var toggleElement = openScope.getToggleElement();
3375     var dropdownElementTargeted = dropdownElement && dropdownElement[0].contains(evt.target);
3376     var toggleElementTargeted = toggleElement && toggleElement[0].contains(evt.target);
3377     if (evt.which === 27) {
3378       evt.stopPropagation();
3379       openScope.focusToggleElement();
3380       closeDropdown();
3381     } else if (openScope.isKeynavEnabled() && [38, 40].indexOf(evt.which) !== -1 && openScope.isOpen && (dropdownElementTargeted || toggleElementTargeted)) {
3382       evt.preventDefault();
3383       evt.stopPropagation();
3384       openScope.focusDropdownEntry(evt.which);
3385     }
3386   };
3387 }])
3388
3389 .controller('UibDropdownController', ['$scope', '$element', '$attrs', '$parse', 'uibDropdownConfig', 'uibDropdownService', '$animate', '$uibPosition', '$document', '$compile', '$templateRequest', function($scope, $element, $attrs, $parse, dropdownConfig, uibDropdownService, $animate, $position, $document, $compile, $templateRequest) {
3390   var self = this,
3391     scope = $scope.$new(), // create a child scope so we are not polluting original one
3392     templateScope,
3393     appendToOpenClass = dropdownConfig.appendToOpenClass,
3394     openClass = dropdownConfig.openClass,
3395     getIsOpen,
3396     setIsOpen = angular.noop,
3397     toggleInvoker = $attrs.onToggle ? $parse($attrs.onToggle) : angular.noop,
3398     appendToBody = false,
3399     appendTo = null,
3400     keynavEnabled = false,
3401     selectedOption = null,
3402     body = $document.find('body');
3403
3404   $element.addClass('dropdown');
3405
3406   this.init = function() {
3407     if ($attrs.isOpen) {
3408       getIsOpen = $parse($attrs.isOpen);
3409       setIsOpen = getIsOpen.assign;
3410
3411       $scope.$watch(getIsOpen, function(value) {
3412         scope.isOpen = !!value;
3413       });
3414     }
3415
3416     if (angular.isDefined($attrs.dropdownAppendTo)) {
3417       var appendToEl = $parse($attrs.dropdownAppendTo)(scope);
3418       if (appendToEl) {
3419         appendTo = angular.element(appendToEl);
3420       }
3421     }
3422
3423     appendToBody = angular.isDefined($attrs.dropdownAppendToBody);
3424     keynavEnabled = angular.isDefined($attrs.keyboardNav);
3425
3426     if (appendToBody && !appendTo) {
3427       appendTo = body;
3428     }
3429
3430     if (appendTo && self.dropdownMenu) {
3431       appendTo.append(self.dropdownMenu);
3432       $element.on('$destroy', function handleDestroyEvent() {
3433         self.dropdownMenu.remove();
3434       });
3435     }
3436   };
3437
3438   this.toggle = function(open) {
3439     scope.isOpen = arguments.length ? !!open : !scope.isOpen;
3440     if (angular.isFunction(setIsOpen)) {
3441       setIsOpen(scope, scope.isOpen);
3442     }
3443
3444     return scope.isOpen;
3445   };
3446
3447   // Allow other directives to watch status
3448   this.isOpen = function() {
3449     return scope.isOpen;
3450   };
3451
3452   scope.getToggleElement = function() {
3453     return self.toggleElement;
3454   };
3455
3456   scope.getAutoClose = function() {
3457     return $attrs.autoClose || 'always'; //or 'outsideClick' or 'disabled'
3458   };
3459
3460   scope.getElement = function() {
3461     return $element;
3462   };
3463
3464   scope.isKeynavEnabled = function() {
3465     return keynavEnabled;
3466   };
3467
3468   scope.focusDropdownEntry = function(keyCode) {
3469     var elems = self.dropdownMenu ? //If append to body is used.
3470       angular.element(self.dropdownMenu).find('a') :
3471       $element.find('ul').eq(0).find('a');
3472
3473     switch (keyCode) {
3474       case 40: {
3475         if (!angular.isNumber(self.selectedOption)) {
3476           self.selectedOption = 0;
3477         } else {
3478           self.selectedOption = self.selectedOption === elems.length - 1 ?
3479             self.selectedOption :
3480             self.selectedOption + 1;
3481         }
3482         break;
3483       }
3484       case 38: {
3485         if (!angular.isNumber(self.selectedOption)) {
3486           self.selectedOption = elems.length - 1;
3487         } else {
3488           self.selectedOption = self.selectedOption === 0 ?
3489             0 : self.selectedOption - 1;
3490         }
3491         break;
3492       }
3493     }
3494     elems[self.selectedOption].focus();
3495   };
3496
3497   scope.getDropdownElement = function() {
3498     return self.dropdownMenu;
3499   };
3500
3501   scope.focusToggleElement = function() {
3502     if (self.toggleElement) {
3503       self.toggleElement[0].focus();
3504     }
3505   };
3506
3507   scope.$watch('isOpen', function(isOpen, wasOpen) {
3508     if (appendTo && self.dropdownMenu) {
3509       var pos = $position.positionElements($element, self.dropdownMenu, 'bottom-left', true),
3510         css,
3511         rightalign,
3512         scrollbarPadding,
3513         scrollbarWidth = 0;
3514
3515       css = {
3516         top: pos.top + 'px',
3517         display: isOpen ? 'block' : 'none'
3518       };
3519
3520       rightalign = self.dropdownMenu.hasClass('dropdown-menu-right');
3521       if (!rightalign) {
3522         css.left = pos.left + 'px';
3523         css.right = 'auto';
3524       } else {
3525         css.left = 'auto';
3526         scrollbarPadding = $position.scrollbarPadding(appendTo);
3527
3528         if (scrollbarPadding.heightOverflow && scrollbarPadding.scrollbarWidth) {
3529           scrollbarWidth = scrollbarPadding.scrollbarWidth;
3530         }
3531
3532         css.right = window.innerWidth - scrollbarWidth -
3533           (pos.left + $element.prop('offsetWidth')) + 'px';
3534       }
3535
3536       // Need to adjust our positioning to be relative to the appendTo container
3537       // if it's not the body element
3538       if (!appendToBody) {
3539         var appendOffset = $position.offset(appendTo);
3540
3541         css.top = pos.top - appendOffset.top + 'px';
3542
3543         if (!rightalign) {
3544           css.left = pos.left - appendOffset.left + 'px';
3545         } else {
3546           css.right = window.innerWidth -
3547             (pos.left - appendOffset.left + $element.prop('offsetWidth')) + 'px';
3548         }
3549       }
3550
3551       self.dropdownMenu.css(css);
3552     }
3553
3554     var openContainer = appendTo ? appendTo : $element;
3555     var dropdownOpenClass = appendTo ? appendToOpenClass : openClass;
3556     var hasOpenClass = openContainer.hasClass(dropdownOpenClass);
3557     var isOnlyOpen = uibDropdownService.isOnlyOpen($scope, appendTo);
3558
3559     if (hasOpenClass === !isOpen) {
3560       var toggleClass;
3561       if (appendTo) {
3562         toggleClass = !isOnlyOpen ? 'addClass' : 'removeClass';
3563       } else {
3564         toggleClass = isOpen ? 'addClass' : 'removeClass';
3565       }
3566       $animate[toggleClass](openContainer, dropdownOpenClass).then(function() {
3567         if (angular.isDefined(isOpen) && isOpen !== wasOpen) {
3568           toggleInvoker($scope, { open: !!isOpen });
3569         }
3570       });
3571     }
3572
3573     if (isOpen) {
3574       if (self.dropdownMenuTemplateUrl) {
3575         $templateRequest(self.dropdownMenuTemplateUrl).then(function(tplContent) {
3576           templateScope = scope.$new();
3577           $compile(tplContent.trim())(templateScope, function(dropdownElement) {
3578             var newEl = dropdownElement;
3579             self.dropdownMenu.replaceWith(newEl);
3580             self.dropdownMenu = newEl;
3581             $document.on('keydown', uibDropdownService.keybindFilter);
3582           });
3583         });
3584       } else {
3585         $document.on('keydown', uibDropdownService.keybindFilter);
3586       }
3587
3588       scope.focusToggleElement();
3589       uibDropdownService.open(scope, $element, appendTo);
3590     } else {
3591       uibDropdownService.close(scope, $element, appendTo);
3592       if (self.dropdownMenuTemplateUrl) {
3593         if (templateScope) {
3594           templateScope.$destroy();
3595         }
3596         var newEl = angular.element('<ul class="dropdown-menu"></ul>');
3597         self.dropdownMenu.replaceWith(newEl);
3598         self.dropdownMenu = newEl;
3599       }
3600
3601       self.selectedOption = null;
3602     }
3603
3604     if (angular.isFunction(setIsOpen)) {
3605       setIsOpen($scope, isOpen);
3606     }
3607   });
3608 }])
3609
3610 .directive('uibDropdown', function() {
3611   return {
3612     controller: 'UibDropdownController',
3613     link: function(scope, element, attrs, dropdownCtrl) {
3614       dropdownCtrl.init();
3615     }
3616   };
3617 })
3618
3619 .directive('uibDropdownMenu', function() {
3620   return {
3621     restrict: 'A',
3622     require: '?^uibDropdown',
3623     link: function(scope, element, attrs, dropdownCtrl) {
3624       if (!dropdownCtrl || angular.isDefined(attrs.dropdownNested)) {
3625         return;
3626       }
3627
3628       element.addClass('dropdown-menu');
3629
3630       var tplUrl = attrs.templateUrl;
3631       if (tplUrl) {
3632         dropdownCtrl.dropdownMenuTemplateUrl = tplUrl;
3633       }
3634
3635       if (!dropdownCtrl.dropdownMenu) {
3636         dropdownCtrl.dropdownMenu = element;
3637       }
3638     }
3639   };
3640 })
3641
3642 .directive('uibDropdownToggle', function() {
3643   return {
3644     require: '?^uibDropdown',
3645     link: function(scope, element, attrs, dropdownCtrl) {
3646       if (!dropdownCtrl) {
3647         return;
3648       }
3649
3650       element.addClass('dropdown-toggle');
3651
3652       dropdownCtrl.toggleElement = element;
3653
3654       var toggleDropdown = function(event) {
3655         event.preventDefault();
3656
3657         if (!element.hasClass('disabled') && !attrs.disabled) {
3658           scope.$apply(function() {
3659             dropdownCtrl.toggle();
3660           });
3661         }
3662       };
3663
3664       element.on('click', toggleDropdown);
3665
3666       // WAI-ARIA
3667       element.attr({ 'aria-haspopup': true, 'aria-expanded': false });
3668       scope.$watch(dropdownCtrl.isOpen, function(isOpen) {
3669         element.attr('aria-expanded', !!isOpen);
3670       });
3671
3672       scope.$on('$destroy', function() {
3673         element.off('click', toggleDropdown);
3674       });
3675     }
3676   };
3677 });
3678
3679 angular.module('ui.bootstrap.stackedMap', [])
3680 /**
3681  * A helper, internal data structure that acts as a map but also allows getting / removing
3682  * elements in the LIFO order
3683  */
3684   .factory('$$stackedMap', function() {
3685     return {
3686       createNew: function() {
3687         var stack = [];
3688
3689         return {
3690           add: function(key, value) {
3691             stack.push({
3692               key: key,
3693               value: value
3694             });
3695           },
3696           get: function(key) {
3697             for (var i = 0; i < stack.length; i++) {
3698               if (key === stack[i].key) {
3699                 return stack[i];
3700               }
3701             }
3702           },
3703           keys: function() {
3704             var keys = [];
3705             for (var i = 0; i < stack.length; i++) {
3706               keys.push(stack[i].key);
3707             }
3708             return keys;
3709           },
3710           top: function() {
3711             return stack[stack.length - 1];
3712           },
3713           remove: function(key) {
3714             var idx = -1;
3715             for (var i = 0; i < stack.length; i++) {
3716               if (key === stack[i].key) {
3717                 idx = i;
3718                 break;
3719               }
3720             }
3721             return stack.splice(idx, 1)[0];
3722           },
3723           removeTop: function() {
3724             return stack.pop();
3725           },
3726           length: function() {
3727             return stack.length;
3728           }
3729         };
3730       }
3731     };
3732   });
3733 angular.module('ui.bootstrap.modal', ['ui.bootstrap.multiMap', 'ui.bootstrap.stackedMap', 'ui.bootstrap.position'])
3734 /**
3735  * Pluggable resolve mechanism for the modal resolve resolution
3736  * Supports UI Router's $resolve service
3737  */
3738   .provider('$uibResolve', function() {
3739     var resolve = this;
3740     this.resolver = null;
3741
3742     this.setResolver = function(resolver) {
3743       this.resolver = resolver;
3744     };
3745
3746     this.$get = ['$injector', '$q', function($injector, $q) {
3747       var resolver = resolve.resolver ? $injector.get(resolve.resolver) : null;
3748       return {
3749         resolve: function(invocables, locals, parent, self) {
3750           if (resolver) {
3751             return resolver.resolve(invocables, locals, parent, self);
3752           }
3753
3754           var promises = [];
3755
3756           angular.forEach(invocables, function(value) {
3757             if (angular.isFunction(value) || angular.isArray(value)) {
3758               promises.push($q.resolve($injector.invoke(value)));
3759             } else if (angular.isString(value)) {
3760               promises.push($q.resolve($injector.get(value)));
3761             } else {
3762               promises.push($q.resolve(value));
3763             }
3764           });
3765
3766           return $q.all(promises).then(function(resolves) {
3767             var resolveObj = {};
3768             var resolveIter = 0;
3769             angular.forEach(invocables, function(value, key) {
3770               resolveObj[key] = resolves[resolveIter++];
3771             });
3772
3773             return resolveObj;
3774           });
3775         }
3776       };
3777     }];
3778   })
3779
3780 /**
3781  * A helper directive for the $modal service. It creates a backdrop element.
3782  */
3783   .directive('uibModalBackdrop', ['$animate', '$injector', '$uibModalStack',
3784   function($animate, $injector, $modalStack) {
3785     return {
3786       restrict: 'A',
3787       compile: function(tElement, tAttrs) {
3788         tElement.addClass(tAttrs.backdropClass);
3789         return linkFn;
3790       }
3791     };
3792
3793     function linkFn(scope, element, attrs) {
3794       if (attrs.modalInClass) {
3795         $animate.addClass(element, attrs.modalInClass);
3796
3797         scope.$on($modalStack.NOW_CLOSING_EVENT, function(e, setIsAsync) {
3798           var done = setIsAsync();
3799           if (scope.modalOptions.animation) {
3800             $animate.removeClass(element, attrs.modalInClass).then(done);
3801           } else {
3802             done();
3803           }
3804         });
3805       }
3806     }
3807   }])
3808
3809   .directive('uibModalWindow', ['$uibModalStack', '$q', '$animateCss', '$document',
3810   function($modalStack, $q, $animateCss, $document) {
3811     return {
3812       scope: {
3813         index: '@'
3814       },
3815       restrict: 'A',
3816       transclude: true,
3817       templateUrl: function(tElement, tAttrs) {
3818         return tAttrs.templateUrl || 'uib/template/modal/window.html';
3819       },
3820       link: function(scope, element, attrs) {
3821         element.addClass(attrs.windowTopClass || '');
3822         scope.size = attrs.size;
3823
3824         scope.close = function(evt) {
3825           var modal = $modalStack.getTop();
3826           if (modal && modal.value.backdrop &&
3827             modal.value.backdrop !== 'static' &&
3828             evt.target === evt.currentTarget) {
3829             evt.preventDefault();
3830             evt.stopPropagation();
3831             $modalStack.dismiss(modal.key, 'backdrop click');
3832           }
3833         };
3834
3835         // moved from template to fix issue #2280
3836         element.on('click', scope.close);
3837
3838         // This property is only added to the scope for the purpose of detecting when this directive is rendered.
3839         // We can detect that by using this property in the template associated with this directive and then use
3840         // {@link Attribute#$observe} on it. For more details please see {@link TableColumnResize}.
3841         scope.$isRendered = true;
3842
3843         // Deferred object that will be resolved when this modal is rendered.
3844         var modalRenderDeferObj = $q.defer();
3845         // Resolve render promise post-digest
3846         scope.$$postDigest(function() {
3847           modalRenderDeferObj.resolve();
3848         });
3849
3850         modalRenderDeferObj.promise.then(function() {
3851           var animationPromise = null;
3852
3853           if (attrs.modalInClass) {
3854             animationPromise = $animateCss(element, {
3855               addClass: attrs.modalInClass
3856             }).start();
3857
3858             scope.$on($modalStack.NOW_CLOSING_EVENT, function(e, setIsAsync) {
3859               var done = setIsAsync();
3860               $animateCss(element, {
3861                 removeClass: attrs.modalInClass
3862               }).start().then(done);
3863             });
3864           }
3865
3866
3867           $q.when(animationPromise).then(function() {
3868             // Notify {@link $modalStack} that modal is rendered.
3869             var modal = $modalStack.getTop();
3870             if (modal) {
3871               $modalStack.modalRendered(modal.key);
3872             }
3873
3874             /**
3875              * If something within the freshly-opened modal already has focus (perhaps via a
3876              * directive that causes focus) then there's no need to try to focus anything.
3877              */
3878             if (!($document[0].activeElement && element[0].contains($document[0].activeElement))) {
3879               var inputWithAutofocus = element[0].querySelector('[autofocus]');
3880               /**
3881                * Auto-focusing of a freshly-opened modal element causes any child elements
3882                * with the autofocus attribute to lose focus. This is an issue on touch
3883                * based devices which will show and then hide the onscreen keyboard.
3884                * Attempts to refocus the autofocus element via JavaScript will not reopen
3885                * the onscreen keyboard. Fixed by updated the focusing logic to only autofocus
3886                * the modal element if the modal does not contain an autofocus element.
3887                */
3888               if (inputWithAutofocus) {
3889                 inputWithAutofocus.focus();
3890               } else {
3891                 element[0].focus();
3892               }
3893             }
3894           });
3895         });
3896       }
3897     };
3898   }])
3899
3900   .directive('uibModalAnimationClass', function() {
3901     return {
3902       compile: function(tElement, tAttrs) {
3903         if (tAttrs.modalAnimation) {
3904           tElement.addClass(tAttrs.uibModalAnimationClass);
3905         }
3906       }
3907     };
3908   })
3909
3910   .directive('uibModalTransclude', ['$animate', function($animate) {
3911     return {
3912       link: function(scope, element, attrs, controller, transclude) {
3913         transclude(scope.$parent, function(clone) {
3914           element.empty();
3915           $animate.enter(clone, element);
3916         });
3917       }
3918     };
3919   }])
3920
3921   .factory('$uibModalStack', ['$animate', '$animateCss', '$document',
3922     '$compile', '$rootScope', '$q', '$$multiMap', '$$stackedMap', '$uibPosition',
3923     function($animate, $animateCss, $document, $compile, $rootScope, $q, $$multiMap, $$stackedMap, $uibPosition) {
3924       var OPENED_MODAL_CLASS = 'modal-open';
3925
3926       var backdropDomEl, backdropScope;
3927       var openedWindows = $$stackedMap.createNew();
3928       var openedClasses = $$multiMap.createNew();
3929       var $modalStack = {
3930         NOW_CLOSING_EVENT: 'modal.stack.now-closing'
3931       };
3932       var topModalIndex = 0;
3933       var previousTopOpenedModal = null;
3934       var ARIA_HIDDEN_ATTRIBUTE_NAME = 'data-bootstrap-modal-aria-hidden-count';
3935
3936       //Modal focus behavior
3937       var tabbableSelector = 'a[href], area[href], input:not([disabled]):not([tabindex=\'-1\']), ' +
3938         'button:not([disabled]):not([tabindex=\'-1\']),select:not([disabled]):not([tabindex=\'-1\']), textarea:not([disabled]):not([tabindex=\'-1\']), ' +
3939         'iframe, object, embed, *[tabindex]:not([tabindex=\'-1\']), *[contenteditable=true]';
3940       var scrollbarPadding;
3941       var SNAKE_CASE_REGEXP = /[A-Z]/g;
3942
3943       // TODO: extract into common dependency with tooltip
3944       function snake_case(name) {
3945         var separator = '-';
3946         return name.replace(SNAKE_CASE_REGEXP, function(letter, pos) {
3947           return (pos ? separator : '') + letter.toLowerCase();
3948         });
3949       }
3950
3951       function isVisible(element) {
3952         return !!(element.offsetWidth ||
3953           element.offsetHeight ||
3954           element.getClientRects().length);
3955       }
3956
3957       function backdropIndex() {
3958         var topBackdropIndex = -1;
3959         var opened = openedWindows.keys();
3960         for (var i = 0; i < opened.length; i++) {
3961           if (openedWindows.get(opened[i]).value.backdrop) {
3962             topBackdropIndex = i;
3963           }
3964         }
3965
3966         // If any backdrop exist, ensure that it's index is always
3967         // right below the top modal
3968         if (topBackdropIndex > -1 && topBackdropIndex < topModalIndex) {
3969           topBackdropIndex = topModalIndex;
3970         }
3971         return topBackdropIndex;
3972       }
3973
3974       $rootScope.$watch(backdropIndex, function(newBackdropIndex) {
3975         if (backdropScope) {
3976           backdropScope.index = newBackdropIndex;
3977         }
3978       });
3979
3980       function removeModalWindow(modalInstance, elementToReceiveFocus) {
3981         var modalWindow = openedWindows.get(modalInstance).value;
3982         var appendToElement = modalWindow.appendTo;
3983
3984         //clean up the stack
3985         openedWindows.remove(modalInstance);
3986         previousTopOpenedModal = openedWindows.top();
3987         if (previousTopOpenedModal) {
3988           topModalIndex = parseInt(previousTopOpenedModal.value.modalDomEl.attr('index'), 10);
3989         }
3990
3991         removeAfterAnimate(modalWindow.modalDomEl, modalWindow.modalScope, function() {
3992           var modalBodyClass = modalWindow.openedClass || OPENED_MODAL_CLASS;
3993           openedClasses.remove(modalBodyClass, modalInstance);
3994           var areAnyOpen = openedClasses.hasKey(modalBodyClass);
3995           appendToElement.toggleClass(modalBodyClass, areAnyOpen);
3996           if (!areAnyOpen && scrollbarPadding && scrollbarPadding.heightOverflow && scrollbarPadding.scrollbarWidth) {
3997             if (scrollbarPadding.originalRight) {
3998               appendToElement.css({paddingRight: scrollbarPadding.originalRight + 'px'});
3999             } else {
4000               appendToElement.css({paddingRight: ''});
4001             }
4002             scrollbarPadding = null;
4003           }
4004           toggleTopWindowClass(true);
4005         }, modalWindow.closedDeferred);
4006         checkRemoveBackdrop();
4007
4008         //move focus to specified element if available, or else to body
4009         if (elementToReceiveFocus && elementToReceiveFocus.focus) {
4010           elementToReceiveFocus.focus();
4011         } else if (appendToElement.focus) {
4012           appendToElement.focus();
4013         }
4014       }
4015
4016       // Add or remove "windowTopClass" from the top window in the stack
4017       function toggleTopWindowClass(toggleSwitch) {
4018         var modalWindow;
4019
4020         if (openedWindows.length() > 0) {
4021           modalWindow = openedWindows.top().value;
4022           modalWindow.modalDomEl.toggleClass(modalWindow.windowTopClass || '', toggleSwitch);
4023         }
4024       }
4025
4026       function checkRemoveBackdrop() {
4027         //remove backdrop if no longer needed
4028         if (backdropDomEl && backdropIndex() === -1) {
4029           var backdropScopeRef = backdropScope;
4030           removeAfterAnimate(backdropDomEl, backdropScope, function() {
4031             backdropScopeRef = null;
4032           });
4033           backdropDomEl = undefined;
4034           backdropScope = undefined;
4035         }
4036       }
4037
4038       function removeAfterAnimate(domEl, scope, done, closedDeferred) {
4039         var asyncDeferred;
4040         var asyncPromise = null;
4041         var setIsAsync = function() {
4042           if (!asyncDeferred) {
4043             asyncDeferred = $q.defer();
4044             asyncPromise = asyncDeferred.promise;
4045           }
4046
4047           return function asyncDone() {
4048             asyncDeferred.resolve();
4049           };
4050         };
4051         scope.$broadcast($modalStack.NOW_CLOSING_EVENT, setIsAsync);
4052
4053         // Note that it's intentional that asyncPromise might be null.
4054         // That's when setIsAsync has not been called during the
4055         // NOW_CLOSING_EVENT broadcast.
4056         return $q.when(asyncPromise).then(afterAnimating);
4057
4058         function afterAnimating() {
4059           if (afterAnimating.done) {
4060             return;
4061           }
4062           afterAnimating.done = true;
4063
4064           $animate.leave(domEl).then(function() {
4065             if (done) {
4066               done();
4067             }
4068
4069             domEl.remove();
4070             if (closedDeferred) {
4071               closedDeferred.resolve();
4072             }
4073           });
4074
4075           scope.$destroy();
4076         }
4077       }
4078
4079       $document.on('keydown', keydownListener);
4080
4081       $rootScope.$on('$destroy', function() {
4082         $document.off('keydown', keydownListener);
4083       });
4084
4085       function keydownListener(evt) {
4086         if (evt.isDefaultPrevented()) {
4087           return evt;
4088         }
4089
4090         var modal = openedWindows.top();
4091         if (modal) {
4092           switch (evt.which) {
4093             case 27: {
4094               if (modal.value.keyboard) {
4095                 evt.preventDefault();
4096                 $rootScope.$apply(function() {
4097                   $modalStack.dismiss(modal.key, 'escape key press');
4098                 });
4099               }
4100               break;
4101             }
4102             case 9: {
4103               var list = $modalStack.loadFocusElementList(modal);
4104               var focusChanged = false;
4105               if (evt.shiftKey) {
4106                 if ($modalStack.isFocusInFirstItem(evt, list) || $modalStack.isModalFocused(evt, modal)) {
4107                   focusChanged = $modalStack.focusLastFocusableElement(list);
4108                 }
4109               } else {
4110                 if ($modalStack.isFocusInLastItem(evt, list)) {
4111                   focusChanged = $modalStack.focusFirstFocusableElement(list);
4112                 }
4113               }
4114
4115               if (focusChanged) {
4116                 evt.preventDefault();
4117                 evt.stopPropagation();
4118               }
4119
4120               break;
4121             }
4122           }
4123         }
4124       }
4125
4126       $modalStack.open = function(modalInstance, modal) {
4127         var modalOpener = $document[0].activeElement,
4128           modalBodyClass = modal.openedClass || OPENED_MODAL_CLASS;
4129
4130         toggleTopWindowClass(false);
4131
4132         // Store the current top first, to determine what index we ought to use
4133         // for the current top modal
4134         previousTopOpenedModal = openedWindows.top();
4135
4136         openedWindows.add(modalInstance, {
4137           deferred: modal.deferred,
4138           renderDeferred: modal.renderDeferred,
4139           closedDeferred: modal.closedDeferred,
4140           modalScope: modal.scope,
4141           backdrop: modal.backdrop,
4142           keyboard: modal.keyboard,
4143           openedClass: modal.openedClass,
4144           windowTopClass: modal.windowTopClass,
4145           animation: modal.animation,
4146           appendTo: modal.appendTo
4147         });
4148
4149         openedClasses.put(modalBodyClass, modalInstance);
4150
4151         var appendToElement = modal.appendTo,
4152             currBackdropIndex = backdropIndex();
4153
4154         if (!appendToElement.length) {
4155           throw new Error('appendTo element not found. Make sure that the element passed is in DOM.');
4156         }
4157
4158         if (currBackdropIndex >= 0 && !backdropDomEl) {
4159           backdropScope = $rootScope.$new(true);
4160           backdropScope.modalOptions = modal;
4161           backdropScope.index = currBackdropIndex;
4162           backdropDomEl = angular.element('<div uib-modal-backdrop="modal-backdrop"></div>');
4163           backdropDomEl.attr({
4164             'class': 'modal-backdrop',
4165             'ng-style': '{\'z-index\': 1040 + (index && 1 || 0) + index*10}',
4166             'uib-modal-animation-class': 'fade',
4167             'modal-in-class': 'in'
4168           });
4169           if (modal.backdropClass) {
4170             backdropDomEl.addClass(modal.backdropClass);
4171           }
4172
4173           if (modal.animation) {
4174             backdropDomEl.attr('modal-animation', 'true');
4175           }
4176           $compile(backdropDomEl)(backdropScope);
4177           $animate.enter(backdropDomEl, appendToElement);
4178           if ($uibPosition.isScrollable(appendToElement)) {
4179             scrollbarPadding = $uibPosition.scrollbarPadding(appendToElement);
4180             if (scrollbarPadding.heightOverflow && scrollbarPadding.scrollbarWidth) {
4181               appendToElement.css({paddingRight: scrollbarPadding.right + 'px'});
4182             }
4183           }
4184         }
4185
4186         var content;
4187         if (modal.component) {
4188           content = document.createElement(snake_case(modal.component.name));
4189           content = angular.element(content);
4190           content.attr({
4191             resolve: '$resolve',
4192             'modal-instance': '$uibModalInstance',
4193             close: '$close($value)',
4194             dismiss: '$dismiss($value)'
4195           });
4196         } else {
4197           content = modal.content;
4198         }
4199
4200         // Set the top modal index based on the index of the previous top modal
4201         topModalIndex = previousTopOpenedModal ? parseInt(previousTopOpenedModal.value.modalDomEl.attr('index'), 10) + 1 : 0;
4202         var angularDomEl = angular.element('<div uib-modal-window="modal-window"></div>');
4203         angularDomEl.attr({
4204           'class': 'modal',
4205           'template-url': modal.windowTemplateUrl,
4206           'window-top-class': modal.windowTopClass,
4207           'role': 'dialog',
4208           'aria-labelledby': modal.ariaLabelledBy,
4209           'aria-describedby': modal.ariaDescribedBy,
4210           'size': modal.size,
4211           'index': topModalIndex,
4212           'animate': 'animate',
4213           'ng-style': '{\'z-index\': 1050 + $$topModalIndex*10, display: \'block\'}',
4214           'tabindex': -1,
4215           'uib-modal-animation-class': 'fade',
4216           'modal-in-class': 'in'
4217         }).append(content);
4218         if (modal.windowClass) {
4219           angularDomEl.addClass(modal.windowClass);
4220         }
4221
4222         if (modal.animation) {
4223           angularDomEl.attr('modal-animation', 'true');
4224         }
4225
4226         appendToElement.addClass(modalBodyClass);
4227         if (modal.scope) {
4228           // we need to explicitly add the modal index to the modal scope
4229           // because it is needed by ngStyle to compute the zIndex property.
4230           modal.scope.$$topModalIndex = topModalIndex;
4231         }
4232         $animate.enter($compile(angularDomEl)(modal.scope), appendToElement);
4233
4234         openedWindows.top().value.modalDomEl = angularDomEl;
4235         openedWindows.top().value.modalOpener = modalOpener;
4236
4237         applyAriaHidden(angularDomEl);
4238
4239         function applyAriaHidden(el) {
4240           if (!el || el[0].tagName === 'BODY') {
4241             return;
4242           }
4243
4244           getSiblings(el).forEach(function(sibling) {
4245             var elemIsAlreadyHidden = sibling.getAttribute('aria-hidden') === 'true',
4246               ariaHiddenCount = parseInt(sibling.getAttribute(ARIA_HIDDEN_ATTRIBUTE_NAME), 10);
4247
4248             if (!ariaHiddenCount) {
4249               ariaHiddenCount = elemIsAlreadyHidden ? 1 : 0;
4250             }
4251
4252             sibling.setAttribute(ARIA_HIDDEN_ATTRIBUTE_NAME, ariaHiddenCount + 1);
4253             sibling.setAttribute('aria-hidden', 'true');
4254           });
4255
4256           return applyAriaHidden(el.parent());
4257
4258           function getSiblings(el) {
4259             var children = el.parent() ? el.parent().children() : [];
4260
4261             return Array.prototype.filter.call(children, function(child) {
4262               return child !== el[0];
4263             });
4264           }
4265         }
4266       };
4267
4268       function broadcastClosing(modalWindow, resultOrReason, closing) {
4269         return !modalWindow.value.modalScope.$broadcast('modal.closing', resultOrReason, closing).defaultPrevented;
4270       }
4271
4272       function unhideBackgroundElements() {
4273         Array.prototype.forEach.call(
4274           document.querySelectorAll('[' + ARIA_HIDDEN_ATTRIBUTE_NAME + ']'),
4275           function(hiddenEl) {
4276             var ariaHiddenCount = parseInt(hiddenEl.getAttribute(ARIA_HIDDEN_ATTRIBUTE_NAME), 10),
4277               newHiddenCount = ariaHiddenCount - 1;
4278             hiddenEl.setAttribute(ARIA_HIDDEN_ATTRIBUTE_NAME, newHiddenCount);
4279
4280             if (!newHiddenCount) {
4281               hiddenEl.removeAttribute(ARIA_HIDDEN_ATTRIBUTE_NAME);
4282               hiddenEl.removeAttribute('aria-hidden');
4283             }
4284           }
4285         );
4286       }
4287
4288       $modalStack.close = function(modalInstance, result) {
4289         var modalWindow = openedWindows.get(modalInstance);
4290         unhideBackgroundElements();
4291         if (modalWindow && broadcastClosing(modalWindow, result, true)) {
4292           modalWindow.value.modalScope.$$uibDestructionScheduled = true;
4293           modalWindow.value.deferred.resolve(result);
4294           removeModalWindow(modalInstance, modalWindow.value.modalOpener);
4295           return true;
4296         }
4297
4298         return !modalWindow;
4299       };
4300
4301       $modalStack.dismiss = function(modalInstance, reason) {
4302         var modalWindow = openedWindows.get(modalInstance);
4303         unhideBackgroundElements();
4304         if (modalWindow && broadcastClosing(modalWindow, reason, false)) {
4305           modalWindow.value.modalScope.$$uibDestructionScheduled = true;
4306           modalWindow.value.deferred.reject(reason);
4307           removeModalWindow(modalInstance, modalWindow.value.modalOpener);
4308           return true;
4309         }
4310         return !modalWindow;
4311       };
4312
4313       $modalStack.dismissAll = function(reason) {
4314         var topModal = this.getTop();
4315         while (topModal && this.dismiss(topModal.key, reason)) {
4316           topModal = this.getTop();
4317         }
4318       };
4319
4320       $modalStack.getTop = function() {
4321         return openedWindows.top();
4322       };
4323
4324       $modalStack.modalRendered = function(modalInstance) {
4325         var modalWindow = openedWindows.get(modalInstance);
4326         if (modalWindow) {
4327           modalWindow.value.renderDeferred.resolve();
4328         }
4329       };
4330
4331       $modalStack.focusFirstFocusableElement = function(list) {
4332         if (list.length > 0) {
4333           list[0].focus();
4334           return true;
4335         }
4336         return false;
4337       };
4338
4339       $modalStack.focusLastFocusableElement = function(list) {
4340         if (list.length > 0) {
4341           list[list.length - 1].focus();
4342           return true;
4343         }
4344         return false;
4345       };
4346
4347       $modalStack.isModalFocused = function(evt, modalWindow) {
4348         if (evt && modalWindow) {
4349           var modalDomEl = modalWindow.value.modalDomEl;
4350           if (modalDomEl && modalDomEl.length) {
4351             return (evt.target || evt.srcElement) === modalDomEl[0];
4352           }
4353         }
4354         return false;
4355       };
4356
4357       $modalStack.isFocusInFirstItem = function(evt, list) {
4358         if (list.length > 0) {
4359           return (evt.target || evt.srcElement) === list[0];
4360         }
4361         return false;
4362       };
4363
4364       $modalStack.isFocusInLastItem = function(evt, list) {
4365         if (list.length > 0) {
4366           return (evt.target || evt.srcElement) === list[list.length - 1];
4367         }
4368         return false;
4369       };
4370
4371       $modalStack.loadFocusElementList = function(modalWindow) {
4372         if (modalWindow) {
4373           var modalDomE1 = modalWindow.value.modalDomEl;
4374           if (modalDomE1 && modalDomE1.length) {
4375             var elements = modalDomE1[0].querySelectorAll(tabbableSelector);
4376             return elements ?
4377               Array.prototype.filter.call(elements, function(element) {
4378                 return isVisible(element);
4379               }) : elements;
4380           }
4381         }
4382       };
4383
4384       return $modalStack;
4385     }])
4386
4387   .provider('$uibModal', function() {
4388     var $modalProvider = {
4389       options: {
4390         animation: true,
4391         backdrop: true, //can also be false or 'static'
4392         keyboard: true
4393       },
4394       $get: ['$rootScope', '$q', '$document', '$templateRequest', '$controller', '$uibResolve', '$uibModalStack',
4395         function ($rootScope, $q, $document, $templateRequest, $controller, $uibResolve, $modalStack) {
4396           var $modal = {};
4397
4398           function getTemplatePromise(options) {
4399             return options.template ? $q.when(options.template) :
4400               $templateRequest(angular.isFunction(options.templateUrl) ?
4401                 options.templateUrl() : options.templateUrl);
4402           }
4403
4404           var promiseChain = null;
4405           $modal.getPromiseChain = function() {
4406             return promiseChain;
4407           };
4408
4409           $modal.open = function(modalOptions) {
4410             var modalResultDeferred = $q.defer();
4411             var modalOpenedDeferred = $q.defer();
4412             var modalClosedDeferred = $q.defer();
4413             var modalRenderDeferred = $q.defer();
4414
4415             //prepare an instance of a modal to be injected into controllers and returned to a caller
4416             var modalInstance = {
4417               result: modalResultDeferred.promise,
4418               opened: modalOpenedDeferred.promise,
4419               closed: modalClosedDeferred.promise,
4420               rendered: modalRenderDeferred.promise,
4421               close: function (result) {
4422                 return $modalStack.close(modalInstance, result);
4423               },
4424               dismiss: function (reason) {
4425                 return $modalStack.dismiss(modalInstance, reason);
4426               }
4427             };
4428
4429             //merge and clean up options
4430             modalOptions = angular.extend({}, $modalProvider.options, modalOptions);
4431             modalOptions.resolve = modalOptions.resolve || {};
4432             modalOptions.appendTo = modalOptions.appendTo || $document.find('body').eq(0);
4433
4434             //verify options
4435             if (!modalOptions.component && !modalOptions.template && !modalOptions.templateUrl) {
4436               throw new Error('One of component or template or templateUrl options is required.');
4437             }
4438
4439             var templateAndResolvePromise;
4440             if (modalOptions.component) {
4441               templateAndResolvePromise = $q.when($uibResolve.resolve(modalOptions.resolve, {}, null, null));
4442             } else {
4443               templateAndResolvePromise =
4444                 $q.all([getTemplatePromise(modalOptions), $uibResolve.resolve(modalOptions.resolve, {}, null, null)]);
4445             }
4446
4447             function resolveWithTemplate() {
4448               return templateAndResolvePromise;
4449             }
4450
4451             // Wait for the resolution of the existing promise chain.
4452             // Then switch to our own combined promise dependency (regardless of how the previous modal fared).
4453             // Then add to $modalStack and resolve opened.
4454             // Finally clean up the chain variable if no subsequent modal has overwritten it.
4455             var samePromise;
4456             samePromise = promiseChain = $q.all([promiseChain])
4457               .then(resolveWithTemplate, resolveWithTemplate)
4458               .then(function resolveSuccess(tplAndVars) {
4459                 var providedScope = modalOptions.scope || $rootScope;
4460
4461                 var modalScope = providedScope.$new();
4462                 modalScope.$close = modalInstance.close;
4463                 modalScope.$dismiss = modalInstance.dismiss;
4464
4465                 modalScope.$on('$destroy', function() {
4466                   if (!modalScope.$$uibDestructionScheduled) {
4467                     modalScope.$dismiss('$uibUnscheduledDestruction');
4468                   }
4469                 });
4470
4471                 var modal = {
4472                   scope: modalScope,
4473                   deferred: modalResultDeferred,
4474                   renderDeferred: modalRenderDeferred,
4475                   closedDeferred: modalClosedDeferred,
4476                   animation: modalOptions.animation,
4477                   backdrop: modalOptions.backdrop,
4478                   keyboard: modalOptions.keyboard,
4479                   backdropClass: modalOptions.backdropClass,
4480                   windowTopClass: modalOptions.windowTopClass,
4481                   windowClass: modalOptions.windowClass,
4482                   windowTemplateUrl: modalOptions.windowTemplateUrl,
4483                   ariaLabelledBy: modalOptions.ariaLabelledBy,
4484                   ariaDescribedBy: modalOptions.ariaDescribedBy,
4485                   size: modalOptions.size,
4486                   openedClass: modalOptions.openedClass,
4487                   appendTo: modalOptions.appendTo
4488                 };
4489
4490                 var component = {};
4491                 var ctrlInstance, ctrlInstantiate, ctrlLocals = {};
4492
4493                 if (modalOptions.component) {
4494                   constructLocals(component, false, true, false);
4495                   component.name = modalOptions.component;
4496                   modal.component = component;
4497                 } else if (modalOptions.controller) {
4498                   constructLocals(ctrlLocals, true, false, true);
4499
4500                   // the third param will make the controller instantiate later,private api
4501                   // @see https://github.com/angular/angular.js/blob/master/src/ng/controller.js#L126
4502                   ctrlInstantiate = $controller(modalOptions.controller, ctrlLocals, true, modalOptions.controllerAs);
4503                   if (modalOptions.controllerAs && modalOptions.bindToController) {
4504                     ctrlInstance = ctrlInstantiate.instance;
4505                     ctrlInstance.$close = modalScope.$close;
4506                     ctrlInstance.$dismiss = modalScope.$dismiss;
4507                     angular.extend(ctrlInstance, {
4508                       $resolve: ctrlLocals.$scope.$resolve
4509                     }, providedScope);
4510                   }
4511
4512                   ctrlInstance = ctrlInstantiate();
4513
4514                   if (angular.isFunction(ctrlInstance.$onInit)) {
4515                     ctrlInstance.$onInit();
4516                   }
4517                 }
4518
4519                 if (!modalOptions.component) {
4520                   modal.content = tplAndVars[0];
4521                 }
4522
4523                 $modalStack.open(modalInstance, modal);
4524                 modalOpenedDeferred.resolve(true);
4525
4526                 function constructLocals(obj, template, instanceOnScope, injectable) {
4527                   obj.$scope = modalScope;
4528                   obj.$scope.$resolve = {};
4529                   if (instanceOnScope) {
4530                     obj.$scope.$uibModalInstance = modalInstance;
4531                   } else {
4532                     obj.$uibModalInstance = modalInstance;
4533                   }
4534
4535                   var resolves = template ? tplAndVars[1] : tplAndVars;
4536                   angular.forEach(resolves, function(value, key) {
4537                     if (injectable) {
4538                       obj[key] = value;
4539                     }
4540
4541                     obj.$scope.$resolve[key] = value;
4542                   });
4543                 }
4544             }, function resolveError(reason) {
4545               modalOpenedDeferred.reject(reason);
4546               modalResultDeferred.reject(reason);
4547             })['finally'](function() {
4548               if (promiseChain === samePromise) {
4549                 promiseChain = null;
4550               }
4551             });
4552
4553             return modalInstance;
4554           };
4555
4556           return $modal;
4557         }
4558       ]
4559     };
4560
4561     return $modalProvider;
4562   });
4563
4564 angular.module('ui.bootstrap.paging', [])
4565 /**
4566  * Helper internal service for generating common controller code between the
4567  * pager and pagination components
4568  */
4569 .factory('uibPaging', ['$parse', function($parse) {
4570   return {
4571     create: function(ctrl, $scope, $attrs) {
4572       ctrl.setNumPages = $attrs.numPages ? $parse($attrs.numPages).assign : angular.noop;
4573       ctrl.ngModelCtrl = { $setViewValue: angular.noop }; // nullModelCtrl
4574       ctrl._watchers = [];
4575
4576       ctrl.init = function(ngModelCtrl, config) {
4577         ctrl.ngModelCtrl = ngModelCtrl;
4578         ctrl.config = config;
4579
4580         ngModelCtrl.$render = function() {
4581           ctrl.render();
4582         };
4583
4584         if ($attrs.itemsPerPage) {
4585           ctrl._watchers.push($scope.$parent.$watch($attrs.itemsPerPage, function(value) {
4586             ctrl.itemsPerPage = parseInt(value, 10);
4587             $scope.totalPages = ctrl.calculateTotalPages();
4588             ctrl.updatePage();
4589           }));
4590         } else {
4591           ctrl.itemsPerPage = config.itemsPerPage;
4592         }
4593
4594         $scope.$watch('totalItems', function(newTotal, oldTotal) {
4595           if (angular.isDefined(newTotal) || newTotal !== oldTotal) {
4596             $scope.totalPages = ctrl.calculateTotalPages();
4597             ctrl.updatePage();
4598           }
4599         });
4600       };
4601
4602       ctrl.calculateTotalPages = function() {
4603         var totalPages = ctrl.itemsPerPage < 1 ? 1 : Math.ceil($scope.totalItems / ctrl.itemsPerPage);
4604         return Math.max(totalPages || 0, 1);
4605       };
4606
4607       ctrl.render = function() {
4608         $scope.page = parseInt(ctrl.ngModelCtrl.$viewValue, 10) || 1;
4609       };
4610
4611       $scope.selectPage = function(page, evt) {
4612         if (evt) {
4613           evt.preventDefault();
4614         }
4615
4616         var clickAllowed = !$scope.ngDisabled || !evt;
4617         if (clickAllowed && $scope.page !== page && page > 0 && page <= $scope.totalPages) {
4618           if (evt && evt.target) {
4619             evt.target.blur();
4620           }
4621           ctrl.ngModelCtrl.$setViewValue(page);
4622           ctrl.ngModelCtrl.$render();
4623         }
4624       };
4625
4626       $scope.getText = function(key) {
4627         return $scope[key + 'Text'] || ctrl.config[key + 'Text'];
4628       };
4629
4630       $scope.noPrevious = function() {
4631         return $scope.page === 1;
4632       };
4633
4634       $scope.noNext = function() {
4635         return $scope.page === $scope.totalPages;
4636       };
4637
4638       ctrl.updatePage = function() {
4639         ctrl.setNumPages($scope.$parent, $scope.totalPages); // Readonly variable
4640
4641         if ($scope.page > $scope.totalPages) {
4642           $scope.selectPage($scope.totalPages);
4643         } else {
4644           ctrl.ngModelCtrl.$render();
4645         }
4646       };
4647
4648       $scope.$on('$destroy', function() {
4649         while (ctrl._watchers.length) {
4650           ctrl._watchers.shift()();
4651         }
4652       });
4653     }
4654   };
4655 }]);
4656
4657 angular.module('ui.bootstrap.pager', ['ui.bootstrap.paging', 'ui.bootstrap.tabindex'])
4658
4659 .controller('UibPagerController', ['$scope', '$attrs', 'uibPaging', 'uibPagerConfig', function($scope, $attrs, uibPaging, uibPagerConfig) {
4660   $scope.align = angular.isDefined($attrs.align) ? $scope.$parent.$eval($attrs.align) : uibPagerConfig.align;
4661
4662   uibPaging.create(this, $scope, $attrs);
4663 }])
4664
4665 .constant('uibPagerConfig', {
4666   itemsPerPage: 10,
4667   previousText: '« Previous',
4668   nextText: 'Next Â»',
4669   align: true
4670 })
4671
4672 .directive('uibPager', ['uibPagerConfig', function(uibPagerConfig) {
4673   return {
4674     scope: {
4675       totalItems: '=',
4676       previousText: '@',
4677       nextText: '@',
4678       ngDisabled: '='
4679     },
4680     require: ['uibPager', '?ngModel'],
4681     restrict: 'A',
4682     controller: 'UibPagerController',
4683     controllerAs: 'pager',
4684     templateUrl: function(element, attrs) {
4685       return attrs.templateUrl || 'uib/template/pager/pager.html';
4686     },
4687     link: function(scope, element, attrs, ctrls) {
4688       element.addClass('pager');
4689       var paginationCtrl = ctrls[0], ngModelCtrl = ctrls[1];
4690
4691       if (!ngModelCtrl) {
4692         return; // do nothing if no ng-model
4693       }
4694
4695       paginationCtrl.init(ngModelCtrl, uibPagerConfig);
4696     }
4697   };
4698 }]);
4699
4700 angular.module('ui.bootstrap.pagination', ['ui.bootstrap.paging', 'ui.bootstrap.tabindex'])
4701 .controller('UibPaginationController', ['$scope', '$attrs', '$parse', 'uibPaging', 'uibPaginationConfig', function($scope, $attrs, $parse, uibPaging, uibPaginationConfig) {
4702   var ctrl = this;
4703   // Setup configuration parameters
4704   var maxSize = angular.isDefined($attrs.maxSize) ? $scope.$parent.$eval($attrs.maxSize) : uibPaginationConfig.maxSize,
4705     rotate = angular.isDefined($attrs.rotate) ? $scope.$parent.$eval($attrs.rotate) : uibPaginationConfig.rotate,
4706     forceEllipses = angular.isDefined($attrs.forceEllipses) ? $scope.$parent.$eval($attrs.forceEllipses) : uibPaginationConfig.forceEllipses,
4707     boundaryLinkNumbers = angular.isDefined($attrs.boundaryLinkNumbers) ? $scope.$parent.$eval($attrs.boundaryLinkNumbers) : uibPaginationConfig.boundaryLinkNumbers,
4708     pageLabel = angular.isDefined($attrs.pageLabel) ? function(idx) { return $scope.$parent.$eval($attrs.pageLabel, {$page: idx}); } : angular.identity;
4709   $scope.boundaryLinks = angular.isDefined($attrs.boundaryLinks) ? $scope.$parent.$eval($attrs.boundaryLinks) : uibPaginationConfig.boundaryLinks;
4710   $scope.directionLinks = angular.isDefined($attrs.directionLinks) ? $scope.$parent.$eval($attrs.directionLinks) : uibPaginationConfig.directionLinks;
4711
4712   uibPaging.create(this, $scope, $attrs);
4713
4714   if ($attrs.maxSize) {
4715     ctrl._watchers.push($scope.$parent.$watch($parse($attrs.maxSize), function(value) {
4716       maxSize = parseInt(value, 10);
4717       ctrl.render();
4718     }));
4719   }
4720
4721   // Create page object used in template
4722   function makePage(number, text, isActive) {
4723     return {
4724       number: number,
4725       text: text,
4726       active: isActive
4727     };
4728   }
4729
4730   function getPages(currentPage, totalPages) {
4731     var pages = [];
4732
4733     // Default page limits
4734     var startPage = 1, endPage = totalPages;
4735     var isMaxSized = angular.isDefined(maxSize) && maxSize < totalPages;
4736
4737     // recompute if maxSize
4738     if (isMaxSized) {
4739       if (rotate) {
4740         // Current page is displayed in the middle of the visible ones
4741         startPage = Math.max(currentPage - Math.floor(maxSize / 2), 1);
4742         endPage = startPage + maxSize - 1;
4743
4744         // Adjust if limit is exceeded
4745         if (endPage > totalPages) {
4746           endPage = totalPages;
4747           startPage = endPage - maxSize + 1;
4748         }
4749       } else {
4750         // Visible pages are paginated with maxSize
4751         startPage = (Math.ceil(currentPage / maxSize) - 1) * maxSize + 1;
4752
4753         // Adjust last page if limit is exceeded
4754         endPage = Math.min(startPage + maxSize - 1, totalPages);
4755       }
4756     }
4757
4758     // Add page number links
4759     for (var number = startPage; number <= endPage; number++) {
4760       var page = makePage(number, pageLabel(number), number === currentPage);
4761       pages.push(page);
4762     }
4763
4764     // Add links to move between page sets
4765     if (isMaxSized && maxSize > 0 && (!rotate || forceEllipses || boundaryLinkNumbers)) {
4766       if (startPage > 1) {
4767         if (!boundaryLinkNumbers || startPage > 3) { //need ellipsis for all options unless range is too close to beginning
4768         var previousPageSet = makePage(startPage - 1, '...', false);
4769         pages.unshift(previousPageSet);
4770       }
4771         if (boundaryLinkNumbers) {
4772           if (startPage === 3) { //need to replace ellipsis when the buttons would be sequential
4773             var secondPageLink = makePage(2, '2', false);
4774             pages.unshift(secondPageLink);
4775           }
4776           //add the first page
4777           var firstPageLink = makePage(1, '1', false);
4778           pages.unshift(firstPageLink);
4779         }
4780       }
4781
4782       if (endPage < totalPages) {
4783         if (!boundaryLinkNumbers || endPage < totalPages - 2) { //need ellipsis for all options unless range is too close to end
4784         var nextPageSet = makePage(endPage + 1, '...', false);
4785         pages.push(nextPageSet);
4786       }
4787         if (boundaryLinkNumbers) {
4788           if (endPage === totalPages - 2) { //need to replace ellipsis when the buttons would be sequential
4789             var secondToLastPageLink = makePage(totalPages - 1, totalPages - 1, false);
4790             pages.push(secondToLastPageLink);
4791           }
4792           //add the last page
4793           var lastPageLink = makePage(totalPages, totalPages, false);
4794           pages.push(lastPageLink);
4795         }
4796       }
4797     }
4798     return pages;
4799   }
4800
4801   var originalRender = this.render;
4802   this.render = function() {
4803     originalRender();
4804     if ($scope.page > 0 && $scope.page <= $scope.totalPages) {
4805       $scope.pages = getPages($scope.page, $scope.totalPages);
4806     }
4807   };
4808 }])
4809
4810 .constant('uibPaginationConfig', {
4811   itemsPerPage: 10,
4812   boundaryLinks: false,
4813   boundaryLinkNumbers: false,
4814   directionLinks: true,
4815   firstText: 'First',
4816   previousText: 'Previous',
4817   nextText: 'Next',
4818   lastText: 'Last',
4819   rotate: true,
4820   forceEllipses: false
4821 })
4822
4823 .directive('uibPagination', ['$parse', 'uibPaginationConfig', function($parse, uibPaginationConfig) {
4824   return {
4825     scope: {
4826       totalItems: '=',
4827       firstText: '@',
4828       previousText: '@',
4829       nextText: '@',
4830       lastText: '@',
4831       ngDisabled:'='
4832     },
4833     require: ['uibPagination', '?ngModel'],
4834     restrict: 'A',
4835     controller: 'UibPaginationController',
4836     controllerAs: 'pagination',
4837     templateUrl: function(element, attrs) {
4838       return attrs.templateUrl || 'uib/template/pagination/pagination.html';
4839     },
4840     link: function(scope, element, attrs, ctrls) {
4841       element.addClass('pagination');
4842       var paginationCtrl = ctrls[0], ngModelCtrl = ctrls[1];
4843
4844       if (!ngModelCtrl) {
4845          return; // do nothing if no ng-model
4846       }
4847
4848       paginationCtrl.init(ngModelCtrl, uibPaginationConfig);
4849     }
4850   };
4851 }]);
4852
4853 /**
4854  * The following features are still outstanding: animation as a
4855  * function, placement as a function, inside, support for more triggers than
4856  * just mouse enter/leave, html tooltips, and selector delegation.
4857  */
4858 angular.module('ui.bootstrap.tooltip', ['ui.bootstrap.position', 'ui.bootstrap.stackedMap'])
4859
4860 /**
4861  * The $tooltip service creates tooltip- and popover-like directives as well as
4862  * houses global options for them.
4863  */
4864 .provider('$uibTooltip', function() {
4865   // The default options tooltip and popover.
4866   var defaultOptions = {
4867     placement: 'top',
4868     placementClassPrefix: '',
4869     animation: true,
4870     popupDelay: 0,
4871     popupCloseDelay: 0,
4872     useContentExp: false
4873   };
4874
4875   // Default hide triggers for each show trigger
4876   var triggerMap = {
4877     'mouseenter': 'mouseleave',
4878     'click': 'click',
4879     'outsideClick': 'outsideClick',
4880     'focus': 'blur',
4881     'none': ''
4882   };
4883
4884   // The options specified to the provider globally.
4885   var globalOptions = {};
4886
4887   /**
4888    * `options({})` allows global configuration of all tooltips in the
4889    * application.
4890    *
4891    *   var app = angular.module( 'App', ['ui.bootstrap.tooltip'], function( $tooltipProvider ) {
4892    *     // place tooltips left instead of top by default
4893    *     $tooltipProvider.options( { placement: 'left' } );
4894    *   });
4895    */
4896         this.options = function(value) {
4897                 angular.extend(globalOptions, value);
4898         };
4899
4900   /**
4901    * This allows you to extend the set of trigger mappings available. E.g.:
4902    *
4903    *   $tooltipProvider.setTriggers( { 'openTrigger': 'closeTrigger' } );
4904    */
4905   this.setTriggers = function setTriggers(triggers) {
4906     angular.extend(triggerMap, triggers);
4907   };
4908
4909   /**
4910    * This is a helper function for translating camel-case to snake_case.
4911    */
4912   function snake_case(name) {
4913     var regexp = /[A-Z]/g;
4914     var separator = '-';
4915     return name.replace(regexp, function(letter, pos) {
4916       return (pos ? separator : '') + letter.toLowerCase();
4917     });
4918   }
4919
4920   /**
4921    * Returns the actual instance of the $tooltip service.
4922    * TODO support multiple triggers
4923    */
4924   this.$get = ['$window', '$compile', '$timeout', '$document', '$uibPosition', '$interpolate', '$rootScope', '$parse', '$$stackedMap', function($window, $compile, $timeout, $document, $position, $interpolate, $rootScope, $parse, $$stackedMap) {
4925     var openedTooltips = $$stackedMap.createNew();
4926     $document.on('keyup', keypressListener);
4927
4928     $rootScope.$on('$destroy', function() {
4929       $document.off('keyup', keypressListener);
4930     });
4931
4932     function keypressListener(e) {
4933       if (e.which === 27) {
4934         var last = openedTooltips.top();
4935         if (last) {
4936           last.value.close();
4937           last = null;
4938         }
4939       }
4940     }
4941
4942     return function $tooltip(ttType, prefix, defaultTriggerShow, options) {
4943       options = angular.extend({}, defaultOptions, globalOptions, options);
4944
4945       /**
4946        * Returns an object of show and hide triggers.
4947        *
4948        * If a trigger is supplied,
4949        * it is used to show the tooltip; otherwise, it will use the `trigger`
4950        * option passed to the `$tooltipProvider.options` method; else it will
4951        * default to the trigger supplied to this directive factory.
4952        *
4953        * The hide trigger is based on the show trigger. If the `trigger` option
4954        * was passed to the `$tooltipProvider.options` method, it will use the
4955        * mapped trigger from `triggerMap` or the passed trigger if the map is
4956        * undefined; otherwise, it uses the `triggerMap` value of the show
4957        * trigger; else it will just use the show trigger.
4958        */
4959       function getTriggers(trigger) {
4960         var show = (trigger || options.trigger || defaultTriggerShow).split(' ');
4961         var hide = show.map(function(trigger) {
4962           return triggerMap[trigger] || trigger;
4963         });
4964         return {
4965           show: show,
4966           hide: hide
4967         };
4968       }
4969
4970       var directiveName = snake_case(ttType);
4971
4972       var startSym = $interpolate.startSymbol();
4973       var endSym = $interpolate.endSymbol();
4974       var template =
4975         '<div '+ directiveName + '-popup ' +
4976           'uib-title="' + startSym + 'title' + endSym + '" ' +
4977           (options.useContentExp ?
4978             'content-exp="contentExp()" ' :
4979             'content="' + startSym + 'content' + endSym + '" ') +
4980           'origin-scope="origScope" ' +
4981           'class="uib-position-measure ' + prefix + '" ' +
4982           'tooltip-animation-class="fade"' +
4983           'uib-tooltip-classes ' +
4984           'ng-class="{ in: isOpen }" ' +
4985           '>' +
4986         '</div>';
4987
4988       return {
4989         compile: function(tElem, tAttrs) {
4990           var tooltipLinker = $compile(template);
4991
4992           return function link(scope, element, attrs, tooltipCtrl) {
4993             var tooltip;
4994             var tooltipLinkedScope;
4995             var transitionTimeout;
4996             var showTimeout;
4997             var hideTimeout;
4998             var positionTimeout;
4999             var adjustmentTimeout;
5000             var appendToBody = angular.isDefined(options.appendToBody) ? options.appendToBody : false;
5001             var triggers = getTriggers(undefined);
5002             var hasEnableExp = angular.isDefined(attrs[prefix + 'Enable']);
5003             var ttScope = scope.$new(true);
5004             var repositionScheduled = false;
5005             var isOpenParse = angular.isDefined(attrs[prefix + 'IsOpen']) ? $parse(attrs[prefix + 'IsOpen']) : false;
5006             var contentParse = options.useContentExp ? $parse(attrs[ttType]) : false;
5007             var observers = [];
5008             var lastPlacement;
5009
5010             var positionTooltip = function() {
5011               // check if tooltip exists and is not empty
5012               if (!tooltip || !tooltip.html()) { return; }
5013
5014               if (!positionTimeout) {
5015                 positionTimeout = $timeout(function() {
5016                   var ttPosition = $position.positionElements(element, tooltip, ttScope.placement, appendToBody);
5017                   var initialHeight = angular.isDefined(tooltip.offsetHeight) ? tooltip.offsetHeight : tooltip.prop('offsetHeight');
5018                   var elementPos = appendToBody ? $position.offset(element) : $position.position(element);
5019                   tooltip.css({ top: ttPosition.top + 'px', left: ttPosition.left + 'px' });
5020                   var placementClasses = ttPosition.placement.split('-');
5021
5022                   if (!tooltip.hasClass(placementClasses[0])) {
5023                     tooltip.removeClass(lastPlacement.split('-')[0]);
5024                     tooltip.addClass(placementClasses[0]);
5025                   }
5026
5027                   if (!tooltip.hasClass(options.placementClassPrefix + ttPosition.placement)) {
5028                     tooltip.removeClass(options.placementClassPrefix + lastPlacement);
5029                     tooltip.addClass(options.placementClassPrefix + ttPosition.placement);
5030                   }
5031
5032                   adjustmentTimeout = $timeout(function() {
5033                     var currentHeight = angular.isDefined(tooltip.offsetHeight) ? tooltip.offsetHeight : tooltip.prop('offsetHeight');
5034                     var adjustment = $position.adjustTop(placementClasses, elementPos, initialHeight, currentHeight);
5035                     if (adjustment) {
5036                       tooltip.css(adjustment);
5037                     }
5038                     adjustmentTimeout = null;
5039                   }, 0, false);
5040
5041                   // first time through tt element will have the
5042                   // uib-position-measure class or if the placement
5043                   // has changed we need to position the arrow.
5044                   if (tooltip.hasClass('uib-position-measure')) {
5045                     $position.positionArrow(tooltip, ttPosition.placement);
5046                     tooltip.removeClass('uib-position-measure');
5047                   } else if (lastPlacement !== ttPosition.placement) {
5048                     $position.positionArrow(tooltip, ttPosition.placement);
5049                   }
5050                   lastPlacement = ttPosition.placement;
5051
5052                   positionTimeout = null;
5053                 }, 0, false);
5054               }
5055             };
5056
5057             // Set up the correct scope to allow transclusion later
5058             ttScope.origScope = scope;
5059
5060             // By default, the tooltip is not open.
5061             // TODO add ability to start tooltip opened
5062             ttScope.isOpen = false;
5063
5064             function toggleTooltipBind() {
5065               if (!ttScope.isOpen) {
5066                 showTooltipBind();
5067               } else {
5068                 hideTooltipBind();
5069               }
5070             }
5071
5072             // Show the tooltip with delay if specified, otherwise show it immediately
5073             function showTooltipBind() {
5074               if (hasEnableExp && !scope.$eval(attrs[prefix + 'Enable'])) {
5075                 return;
5076               }
5077
5078               cancelHide();
5079               prepareTooltip();
5080
5081               if (ttScope.popupDelay) {
5082                 // Do nothing if the tooltip was already scheduled to pop-up.
5083                 // This happens if show is triggered multiple times before any hide is triggered.
5084                 if (!showTimeout) {
5085                   showTimeout = $timeout(show, ttScope.popupDelay, false);
5086                 }
5087               } else {
5088                 show();
5089               }
5090             }
5091
5092             function hideTooltipBind() {
5093               cancelShow();
5094
5095               if (ttScope.popupCloseDelay) {
5096                 if (!hideTimeout) {
5097                   hideTimeout = $timeout(hide, ttScope.popupCloseDelay, false);
5098                 }
5099               } else {
5100                 hide();
5101               }
5102             }
5103
5104             // Show the tooltip popup element.
5105             function show() {
5106               cancelShow();
5107               cancelHide();
5108
5109               // Don't show empty tooltips.
5110               if (!ttScope.content) {
5111                 return angular.noop;
5112               }
5113
5114               createTooltip();
5115
5116               // And show the tooltip.
5117               ttScope.$evalAsync(function() {
5118                 ttScope.isOpen = true;
5119                 assignIsOpen(true);
5120                 positionTooltip();
5121               });
5122             }
5123
5124             function cancelShow() {
5125               if (showTimeout) {
5126                 $timeout.cancel(showTimeout);
5127                 showTimeout = null;
5128               }
5129
5130               if (positionTimeout) {
5131                 $timeout.cancel(positionTimeout);
5132                 positionTimeout = null;
5133               }
5134             }
5135
5136             // Hide the tooltip popup element.
5137             function hide() {
5138               if (!ttScope) {
5139                 return;
5140               }
5141
5142               // First things first: we don't show it anymore.
5143               ttScope.$evalAsync(function() {
5144                 if (ttScope) {
5145                   ttScope.isOpen = false;
5146                   assignIsOpen(false);
5147                   // And now we remove it from the DOM. However, if we have animation, we
5148                   // need to wait for it to expire beforehand.
5149                   // FIXME: this is a placeholder for a port of the transitions library.
5150                   // The fade transition in TWBS is 150ms.
5151                   if (ttScope.animation) {
5152                     if (!transitionTimeout) {
5153                       transitionTimeout = $timeout(removeTooltip, 150, false);
5154                     }
5155                   } else {
5156                     removeTooltip();
5157                   }
5158                 }
5159               });
5160             }
5161
5162             function cancelHide() {
5163               if (hideTimeout) {
5164                 $timeout.cancel(hideTimeout);
5165                 hideTimeout = null;
5166               }
5167
5168               if (transitionTimeout) {
5169                 $timeout.cancel(transitionTimeout);
5170                 transitionTimeout = null;
5171               }
5172             }
5173
5174             function createTooltip() {
5175               // There can only be one tooltip element per directive shown at once.
5176               if (tooltip) {
5177                 return;
5178               }
5179
5180               tooltipLinkedScope = ttScope.$new();
5181               tooltip = tooltipLinker(tooltipLinkedScope, function(tooltip) {
5182                 if (appendToBody) {
5183                   $document.find('body').append(tooltip);
5184                 } else {
5185                   element.after(tooltip);
5186                 }
5187               });
5188
5189               openedTooltips.add(ttScope, {
5190                 close: hide
5191               });
5192
5193               prepObservers();
5194             }
5195
5196             function removeTooltip() {
5197               cancelShow();
5198               cancelHide();
5199               unregisterObservers();
5200
5201               if (tooltip) {
5202                 tooltip.remove();
5203                 
5204                 tooltip = null;
5205                 if (adjustmentTimeout) {
5206                   $timeout.cancel(adjustmentTimeout);
5207                 }
5208               }
5209
5210               openedTooltips.remove(ttScope);
5211               
5212               if (tooltipLinkedScope) {
5213                 tooltipLinkedScope.$destroy();
5214                 tooltipLinkedScope = null;
5215               }
5216             }
5217
5218             /**
5219              * Set the initial scope values. Once
5220              * the tooltip is created, the observers
5221              * will be added to keep things in sync.
5222              */
5223             function prepareTooltip() {
5224               ttScope.title = attrs[prefix + 'Title'];
5225               if (contentParse) {
5226                 ttScope.content = contentParse(scope);
5227               } else {
5228                 ttScope.content = attrs[ttType];
5229               }
5230
5231               ttScope.popupClass = attrs[prefix + 'Class'];
5232               ttScope.placement = angular.isDefined(attrs[prefix + 'Placement']) ? attrs[prefix + 'Placement'] : options.placement;
5233               var placement = $position.parsePlacement(ttScope.placement);
5234               lastPlacement = placement[1] ? placement[0] + '-' + placement[1] : placement[0];
5235
5236               var delay = parseInt(attrs[prefix + 'PopupDelay'], 10);
5237               var closeDelay = parseInt(attrs[prefix + 'PopupCloseDelay'], 10);
5238               ttScope.popupDelay = !isNaN(delay) ? delay : options.popupDelay;
5239               ttScope.popupCloseDelay = !isNaN(closeDelay) ? closeDelay : options.popupCloseDelay;
5240             }
5241
5242             function assignIsOpen(isOpen) {
5243               if (isOpenParse && angular.isFunction(isOpenParse.assign)) {
5244                 isOpenParse.assign(scope, isOpen);
5245               }
5246             }
5247
5248             ttScope.contentExp = function() {
5249               return ttScope.content;
5250             };
5251
5252             /**
5253              * Observe the relevant attributes.
5254              */
5255             attrs.$observe('disabled', function(val) {
5256               if (val) {
5257                 cancelShow();
5258               }
5259
5260               if (val && ttScope.isOpen) {
5261                 hide();
5262               }
5263             });
5264
5265             if (isOpenParse) {
5266               scope.$watch(isOpenParse, function(val) {
5267                 if (ttScope && !val === ttScope.isOpen) {
5268                   toggleTooltipBind();
5269                 }
5270               });
5271             }
5272
5273             function prepObservers() {
5274               observers.length = 0;
5275
5276               if (contentParse) {
5277                 observers.push(
5278                   scope.$watch(contentParse, function(val) {
5279                     ttScope.content = val;
5280                     if (!val && ttScope.isOpen) {
5281                       hide();
5282                     }
5283                   })
5284                 );
5285
5286                 observers.push(
5287                   tooltipLinkedScope.$watch(function() {
5288                     if (!repositionScheduled) {
5289                       repositionScheduled = true;
5290                       tooltipLinkedScope.$$postDigest(function() {
5291                         repositionScheduled = false;
5292                         if (ttScope && ttScope.isOpen) {
5293                           positionTooltip();
5294                         }
5295                       });
5296                     }
5297                   })
5298                 );
5299               } else {
5300                 observers.push(
5301                   attrs.$observe(ttType, function(val) {
5302                     ttScope.content = val;
5303                     if (!val && ttScope.isOpen) {
5304                       hide();
5305                     } else {
5306                       positionTooltip();
5307                     }
5308                   })
5309                 );
5310               }
5311
5312               observers.push(
5313                 attrs.$observe(prefix + 'Title', function(val) {
5314                   ttScope.title = val;
5315                   if (ttScope.isOpen) {
5316                     positionTooltip();
5317                   }
5318                 })
5319               );
5320
5321               observers.push(
5322                 attrs.$observe(prefix + 'Placement', function(val) {
5323                   ttScope.placement = val ? val : options.placement;
5324                   if (ttScope.isOpen) {
5325                     positionTooltip();
5326                   }
5327                 })
5328               );
5329             }
5330
5331             function unregisterObservers() {
5332               if (observers.length) {
5333                 angular.forEach(observers, function(observer) {
5334                   observer();
5335                 });
5336                 observers.length = 0;
5337               }
5338             }
5339
5340             // hide tooltips/popovers for outsideClick trigger
5341             function bodyHideTooltipBind(e) {
5342               if (!ttScope || !ttScope.isOpen || !tooltip) {
5343                 return;
5344               }
5345               // make sure the tooltip/popover link or tool tooltip/popover itself were not clicked
5346               if (!element[0].contains(e.target) && !tooltip[0].contains(e.target)) {
5347                 hideTooltipBind();
5348               }
5349             }
5350
5351             var unregisterTriggers = function() {
5352               triggers.show.forEach(function(trigger) {
5353                 if (trigger === 'outsideClick') {
5354                   element.off('click', toggleTooltipBind);
5355                 } else {
5356                   element.off(trigger, showTooltipBind);
5357                   element.off(trigger, toggleTooltipBind);
5358                 }
5359               });
5360               triggers.hide.forEach(function(trigger) {
5361                 if (trigger === 'outsideClick') {
5362                   $document.off('click', bodyHideTooltipBind);
5363                 } else {
5364                   element.off(trigger, hideTooltipBind);
5365                 }
5366               });
5367             };
5368
5369             function prepTriggers() {
5370               var showTriggers = [], hideTriggers = [];
5371               var val = scope.$eval(attrs[prefix + 'Trigger']);
5372               unregisterTriggers();
5373
5374               if (angular.isObject(val)) {
5375                 Object.keys(val).forEach(function(key) {
5376                   showTriggers.push(key);
5377                   hideTriggers.push(val[key]);
5378                 });
5379                 triggers = {
5380                   show: showTriggers,
5381                   hide: hideTriggers
5382                 };
5383               } else {
5384                 triggers = getTriggers(val);
5385               }
5386
5387               if (triggers.show !== 'none') {
5388                 triggers.show.forEach(function(trigger, idx) {
5389                   if (trigger === 'outsideClick') {
5390                     element.on('click', toggleTooltipBind);
5391                     $document.on('click', bodyHideTooltipBind);
5392                   } else if (trigger === triggers.hide[idx]) {
5393                     element.on(trigger, toggleTooltipBind);
5394                   } else if (trigger) {
5395                     element.on(trigger, showTooltipBind);
5396                     element.on(triggers.hide[idx], hideTooltipBind);
5397                   }
5398
5399                   element.on('keypress', function(e) {
5400                     if (e.which === 27) {
5401                       hideTooltipBind();
5402                     }
5403                   });
5404                 });
5405               }
5406             }
5407
5408             prepTriggers();
5409
5410             var animation = scope.$eval(attrs[prefix + 'Animation']);
5411             ttScope.animation = angular.isDefined(animation) ? !!animation : options.animation;
5412
5413             var appendToBodyVal;
5414             var appendKey = prefix + 'AppendToBody';
5415             if (appendKey in attrs && attrs[appendKey] === undefined) {
5416               appendToBodyVal = true;
5417             } else {
5418               appendToBodyVal = scope.$eval(attrs[appendKey]);
5419             }
5420
5421             appendToBody = angular.isDefined(appendToBodyVal) ? appendToBodyVal : appendToBody;
5422
5423             // Make sure tooltip is destroyed and removed.
5424             scope.$on('$destroy', function onDestroyTooltip() {
5425               unregisterTriggers();
5426               removeTooltip();
5427               ttScope = null;
5428             });
5429           };
5430         }
5431       };
5432     };
5433   }];
5434 })
5435
5436 // This is mostly ngInclude code but with a custom scope
5437 .directive('uibTooltipTemplateTransclude', [
5438          '$animate', '$sce', '$compile', '$templateRequest',
5439 function ($animate, $sce, $compile, $templateRequest) {
5440   return {
5441     link: function(scope, elem, attrs) {
5442       var origScope = scope.$eval(attrs.tooltipTemplateTranscludeScope);
5443
5444       var changeCounter = 0,
5445         currentScope,
5446         previousElement,
5447         currentElement;
5448
5449       var cleanupLastIncludeContent = function() {
5450         if (previousElement) {
5451           previousElement.remove();
5452           previousElement = null;
5453         }
5454
5455         if (currentScope) {
5456           currentScope.$destroy();
5457           currentScope = null;
5458         }
5459
5460         if (currentElement) {
5461           $animate.leave(currentElement).then(function() {
5462             previousElement = null;
5463           });
5464           previousElement = currentElement;
5465           currentElement = null;
5466         }
5467       };
5468
5469       scope.$watch($sce.parseAsResourceUrl(attrs.uibTooltipTemplateTransclude), function(src) {
5470         var thisChangeId = ++changeCounter;
5471
5472         if (src) {
5473           //set the 2nd param to true to ignore the template request error so that the inner
5474           //contents and scope can be cleaned up.
5475           $templateRequest(src, true).then(function(response) {
5476             if (thisChangeId !== changeCounter) { return; }
5477             var newScope = origScope.$new();
5478             var template = response;
5479
5480             var clone = $compile(template)(newScope, function(clone) {
5481               cleanupLastIncludeContent();
5482               $animate.enter(clone, elem);
5483             });
5484
5485             currentScope = newScope;
5486             currentElement = clone;
5487
5488             currentScope.$emit('$includeContentLoaded', src);
5489           }, function() {
5490             if (thisChangeId === changeCounter) {
5491               cleanupLastIncludeContent();
5492               scope.$emit('$includeContentError', src);
5493             }
5494           });
5495           scope.$emit('$includeContentRequested', src);
5496         } else {
5497           cleanupLastIncludeContent();
5498         }
5499       });
5500
5501       scope.$on('$destroy', cleanupLastIncludeContent);
5502     }
5503   };
5504 }])
5505
5506 /**
5507  * Note that it's intentional that these classes are *not* applied through $animate.
5508  * They must not be animated as they're expected to be present on the tooltip on
5509  * initialization.
5510  */
5511 .directive('uibTooltipClasses', ['$uibPosition', function($uibPosition) {
5512   return {
5513     restrict: 'A',
5514     link: function(scope, element, attrs) {
5515       // need to set the primary position so the
5516       // arrow has space during position measure.
5517       // tooltip.positionTooltip()
5518       if (scope.placement) {
5519         // // There are no top-left etc... classes
5520         // // in TWBS, so we need the primary position.
5521         var position = $uibPosition.parsePlacement(scope.placement);
5522         element.addClass(position[0]);
5523       }
5524
5525       if (scope.popupClass) {
5526         element.addClass(scope.popupClass);
5527       }
5528
5529       if (scope.animation) {
5530         element.addClass(attrs.tooltipAnimationClass);
5531       }
5532     }
5533   };
5534 }])
5535
5536 .directive('uibTooltipPopup', function() {
5537   return {
5538     restrict: 'A',
5539     scope: { content: '@' },
5540     templateUrl: 'uib/template/tooltip/tooltip-popup.html'
5541   };
5542 })
5543
5544 .directive('uibTooltip', [ '$uibTooltip', function($uibTooltip) {
5545   return $uibTooltip('uibTooltip', 'tooltip', 'mouseenter');
5546 }])
5547
5548 .directive('uibTooltipTemplatePopup', function() {
5549   return {
5550     restrict: 'A',
5551     scope: { contentExp: '&', originScope: '&' },
5552     templateUrl: 'uib/template/tooltip/tooltip-template-popup.html'
5553   };
5554 })
5555
5556 .directive('uibTooltipTemplate', ['$uibTooltip', function($uibTooltip) {
5557   return $uibTooltip('uibTooltipTemplate', 'tooltip', 'mouseenter', {
5558     useContentExp: true
5559   });
5560 }])
5561
5562 .directive('uibTooltipHtmlPopup', function() {
5563   return {
5564     restrict: 'A',
5565     scope: { contentExp: '&' },
5566     templateUrl: 'uib/template/tooltip/tooltip-html-popup.html'
5567   };
5568 })
5569
5570 .directive('uibTooltipHtml', ['$uibTooltip', function($uibTooltip) {
5571   return $uibTooltip('uibTooltipHtml', 'tooltip', 'mouseenter', {
5572     useContentExp: true
5573   });
5574 }]);
5575
5576 /**
5577  * The following features are still outstanding: popup delay, animation as a
5578  * function, placement as a function, inside, support for more triggers than
5579  * just mouse enter/leave, and selector delegatation.
5580  */
5581 angular.module('ui.bootstrap.popover', ['ui.bootstrap.tooltip'])
5582
5583 .directive('uibPopoverTemplatePopup', function() {
5584   return {
5585     restrict: 'A',
5586     scope: { uibTitle: '@', contentExp: '&', originScope: '&' },
5587     templateUrl: 'uib/template/popover/popover-template.html'
5588   };
5589 })
5590
5591 .directive('uibPopoverTemplate', ['$uibTooltip', function($uibTooltip) {
5592   return $uibTooltip('uibPopoverTemplate', 'popover', 'click', {
5593     useContentExp: true
5594   });
5595 }])
5596
5597 .directive('uibPopoverHtmlPopup', function() {
5598   return {
5599     restrict: 'A',
5600     scope: { contentExp: '&', uibTitle: '@' },
5601     templateUrl: 'uib/template/popover/popover-html.html'
5602   };
5603 })
5604
5605 .directive('uibPopoverHtml', ['$uibTooltip', function($uibTooltip) {
5606   return $uibTooltip('uibPopoverHtml', 'popover', 'click', {
5607     useContentExp: true
5608   });
5609 }])
5610
5611 .directive('uibPopoverPopup', function() {
5612   return {
5613     restrict: 'A',
5614     scope: { uibTitle: '@', content: '@' },
5615     templateUrl: 'uib/template/popover/popover.html'
5616   };
5617 })
5618
5619 .directive('uibPopover', ['$uibTooltip', function($uibTooltip) {
5620   return $uibTooltip('uibPopover', 'popover', 'click');
5621 }]);
5622
5623 angular.module('ui.bootstrap.progressbar', [])
5624
5625 .constant('uibProgressConfig', {
5626   animate: true,
5627   max: 100
5628 })
5629
5630 .controller('UibProgressController', ['$scope', '$attrs', 'uibProgressConfig', function($scope, $attrs, progressConfig) {
5631   var self = this,
5632       animate = angular.isDefined($attrs.animate) ? $scope.$parent.$eval($attrs.animate) : progressConfig.animate;
5633
5634   this.bars = [];
5635   $scope.max = getMaxOrDefault();
5636
5637   this.addBar = function(bar, element, attrs) {
5638     if (!animate) {
5639       element.css({'transition': 'none'});
5640     }
5641
5642     this.bars.push(bar);
5643
5644     bar.max = getMaxOrDefault();
5645     bar.title = attrs && angular.isDefined(attrs.title) ? attrs.title : 'progressbar';
5646
5647     bar.$watch('value', function(value) {
5648       bar.recalculatePercentage();
5649     });
5650
5651     bar.recalculatePercentage = function() {
5652       var totalPercentage = self.bars.reduce(function(total, bar) {
5653         bar.percent = +(100 * bar.value / bar.max).toFixed(2);
5654         return total + bar.percent;
5655       }, 0);
5656
5657       if (totalPercentage > 100) {
5658         bar.percent -= totalPercentage - 100;
5659       }
5660     };
5661
5662     bar.$on('$destroy', function() {
5663       element = null;
5664       self.removeBar(bar);
5665     });
5666   };
5667
5668   this.removeBar = function(bar) {
5669     this.bars.splice(this.bars.indexOf(bar), 1);
5670     this.bars.forEach(function (bar) {
5671       bar.recalculatePercentage();
5672     });
5673   };
5674
5675   //$attrs.$observe('maxParam', function(maxParam) {
5676   $scope.$watch('maxParam', function(maxParam) {
5677     self.bars.forEach(function(bar) {
5678       bar.max = getMaxOrDefault();
5679       bar.recalculatePercentage();
5680     });
5681   });
5682
5683   function getMaxOrDefault () {
5684     return angular.isDefined($scope.maxParam) ? $scope.maxParam : progressConfig.max;
5685   }
5686 }])
5687
5688 .directive('uibProgress', function() {
5689   return {
5690     replace: true,
5691     transclude: true,
5692     controller: 'UibProgressController',
5693     require: 'uibProgress',
5694     scope: {
5695       maxParam: '=?max'
5696     },
5697     templateUrl: 'uib/template/progressbar/progress.html'
5698   };
5699 })
5700
5701 .directive('uibBar', function() {
5702   return {
5703     replace: true,
5704     transclude: true,
5705     require: '^uibProgress',
5706     scope: {
5707       value: '=',
5708       type: '@'
5709     },
5710     templateUrl: 'uib/template/progressbar/bar.html',
5711     link: function(scope, element, attrs, progressCtrl) {
5712       progressCtrl.addBar(scope, element, attrs);
5713     }
5714   };
5715 })
5716
5717 .directive('uibProgressbar', function() {
5718   return {
5719     replace: true,
5720     transclude: true,
5721     controller: 'UibProgressController',
5722     scope: {
5723       value: '=',
5724       maxParam: '=?max',
5725       type: '@'
5726     },
5727     templateUrl: 'uib/template/progressbar/progressbar.html',
5728     link: function(scope, element, attrs, progressCtrl) {
5729       progressCtrl.addBar(scope, angular.element(element.children()[0]), {title: attrs.title});
5730     }
5731   };
5732 });
5733
5734 angular.module('ui.bootstrap.rating', [])
5735
5736 .constant('uibRatingConfig', {
5737   max: 5,
5738   stateOn: null,
5739   stateOff: null,
5740   enableReset: true,
5741   titles: ['one', 'two', 'three', 'four', 'five']
5742 })
5743
5744 .controller('UibRatingController', ['$scope', '$attrs', 'uibRatingConfig', function($scope, $attrs, ratingConfig) {
5745   var ngModelCtrl = { $setViewValue: angular.noop },
5746     self = this;
5747
5748   this.init = function(ngModelCtrl_) {
5749     ngModelCtrl = ngModelCtrl_;
5750     ngModelCtrl.$render = this.render;
5751
5752     ngModelCtrl.$formatters.push(function(value) {
5753       if (angular.isNumber(value) && value << 0 !== value) {
5754         value = Math.round(value);
5755       }
5756
5757       return value;
5758     });
5759
5760     this.stateOn = angular.isDefined($attrs.stateOn) ? $scope.$parent.$eval($attrs.stateOn) : ratingConfig.stateOn;
5761     this.stateOff = angular.isDefined($attrs.stateOff) ? $scope.$parent.$eval($attrs.stateOff) : ratingConfig.stateOff;
5762     this.enableReset = angular.isDefined($attrs.enableReset) ?
5763       $scope.$parent.$eval($attrs.enableReset) : ratingConfig.enableReset;
5764     var tmpTitles = angular.isDefined($attrs.titles) ? $scope.$parent.$eval($attrs.titles) : ratingConfig.titles;
5765     this.titles = angular.isArray(tmpTitles) && tmpTitles.length > 0 ?
5766       tmpTitles : ratingConfig.titles;
5767
5768     var ratingStates = angular.isDefined($attrs.ratingStates) ?
5769       $scope.$parent.$eval($attrs.ratingStates) :
5770       new Array(angular.isDefined($attrs.max) ? $scope.$parent.$eval($attrs.max) : ratingConfig.max);
5771     $scope.range = this.buildTemplateObjects(ratingStates);
5772   };
5773
5774   this.buildTemplateObjects = function(states) {
5775     for (var i = 0, n = states.length; i < n; i++) {
5776       states[i] = angular.extend({ index: i }, { stateOn: this.stateOn, stateOff: this.stateOff, title: this.getTitle(i) }, states[i]);
5777     }
5778     return states;
5779   };
5780
5781   this.getTitle = function(index) {
5782     if (index >= this.titles.length) {
5783       return index + 1;
5784     }
5785
5786     return this.titles[index];
5787   };
5788
5789   $scope.rate = function(value) {
5790     if (!$scope.readonly && value >= 0 && value <= $scope.range.length) {
5791       var newViewValue = self.enableReset && ngModelCtrl.$viewValue === value ? 0 : value;
5792       ngModelCtrl.$setViewValue(newViewValue);
5793       ngModelCtrl.$render();
5794     }
5795   };
5796
5797   $scope.enter = function(value) {
5798     if (!$scope.readonly) {
5799       $scope.value = value;
5800     }
5801     $scope.onHover({value: value});
5802   };
5803
5804   $scope.reset = function() {
5805     $scope.value = ngModelCtrl.$viewValue;
5806     $scope.onLeave();
5807   };
5808
5809   $scope.onKeydown = function(evt) {
5810     if (/(37|38|39|40)/.test(evt.which)) {
5811       evt.preventDefault();
5812       evt.stopPropagation();
5813       $scope.rate($scope.value + (evt.which === 38 || evt.which === 39 ? 1 : -1));
5814     }
5815   };
5816
5817   this.render = function() {
5818     $scope.value = ngModelCtrl.$viewValue;
5819     $scope.title = self.getTitle($scope.value - 1);
5820   };
5821 }])
5822
5823 .directive('uibRating', function() {
5824   return {
5825     require: ['uibRating', 'ngModel'],
5826     restrict: 'A',
5827     scope: {
5828       readonly: '=?readOnly',
5829       onHover: '&',
5830       onLeave: '&'
5831     },
5832     controller: 'UibRatingController',
5833     templateUrl: 'uib/template/rating/rating.html',
5834     link: function(scope, element, attrs, ctrls) {
5835       var ratingCtrl = ctrls[0], ngModelCtrl = ctrls[1];
5836       ratingCtrl.init(ngModelCtrl);
5837     }
5838   };
5839 });
5840
5841 angular.module('ui.bootstrap.tabs', [])
5842
5843 .controller('UibTabsetController', ['$scope', function ($scope) {
5844   var ctrl = this,
5845     oldIndex;
5846   ctrl.tabs = [];
5847
5848   ctrl.select = function(index, evt) {
5849     if (!destroyed) {
5850       var previousIndex = findTabIndex(oldIndex);
5851       var previousSelected = ctrl.tabs[previousIndex];
5852       if (previousSelected) {
5853         previousSelected.tab.onDeselect({
5854           $event: evt,
5855           $selectedIndex: index
5856         });
5857         if (evt && evt.isDefaultPrevented()) {
5858           return;
5859         }
5860         previousSelected.tab.active = false;
5861       }
5862
5863       var selected = ctrl.tabs[index];
5864       if (selected) {
5865         selected.tab.onSelect({
5866           $event: evt
5867         });
5868         selected.tab.active = true;
5869         ctrl.active = selected.index;
5870         oldIndex = selected.index;
5871       } else if (!selected && angular.isDefined(oldIndex)) {
5872         ctrl.active = null;
5873         oldIndex = null;
5874       }
5875     }
5876   };
5877
5878   ctrl.addTab = function addTab(tab) {
5879     ctrl.tabs.push({
5880       tab: tab,
5881       index: tab.index
5882     });
5883     ctrl.tabs.sort(function(t1, t2) {
5884       if (t1.index > t2.index) {
5885         return 1;
5886       }
5887
5888       if (t1.index < t2.index) {
5889         return -1;
5890       }
5891
5892       return 0;
5893     });
5894
5895     if (tab.index === ctrl.active || !angular.isDefined(ctrl.active) && ctrl.tabs.length === 1) {
5896       var newActiveIndex = findTabIndex(tab.index);
5897       ctrl.select(newActiveIndex);
5898     }
5899   };
5900
5901   ctrl.removeTab = function removeTab(tab) {
5902     var index;
5903     for (var i = 0; i < ctrl.tabs.length; i++) {
5904       if (ctrl.tabs[i].tab === tab) {
5905         index = i;
5906         break;
5907       }
5908     }
5909
5910     if (ctrl.tabs[index].index === ctrl.active) {
5911       var newActiveTabIndex = index === ctrl.tabs.length - 1 ?
5912         index - 1 : index + 1 % ctrl.tabs.length;
5913       ctrl.select(newActiveTabIndex);
5914     }
5915
5916     ctrl.tabs.splice(index, 1);
5917   };
5918
5919   $scope.$watch('tabset.active', function(val) {
5920     if (angular.isDefined(val) && val !== oldIndex) {
5921       ctrl.select(findTabIndex(val));
5922     }
5923   });
5924
5925   var destroyed;
5926   $scope.$on('$destroy', function() {
5927     destroyed = true;
5928   });
5929
5930   function findTabIndex(index) {
5931     for (var i = 0; i < ctrl.tabs.length; i++) {
5932       if (ctrl.tabs[i].index === index) {
5933         return i;
5934       }
5935     }
5936   }
5937 }])
5938
5939 .directive('uibTabset', function() {
5940   return {
5941     transclude: true,
5942     replace: true,
5943     scope: {},
5944     bindToController: {
5945       active: '=?',
5946       type: '@'
5947     },
5948     controller: 'UibTabsetController',
5949     controllerAs: 'tabset',
5950     templateUrl: function(element, attrs) {
5951       return attrs.templateUrl || 'uib/template/tabs/tabset.html';
5952     },
5953     link: function(scope, element, attrs) {
5954       scope.vertical = angular.isDefined(attrs.vertical) ?
5955         scope.$parent.$eval(attrs.vertical) : false;
5956       scope.justified = angular.isDefined(attrs.justified) ?
5957         scope.$parent.$eval(attrs.justified) : false;
5958     }
5959   };
5960 })
5961
5962 .directive('uibTab', ['$parse', function($parse) {
5963   return {
5964     require: '^uibTabset',
5965     replace: true,
5966     templateUrl: function(element, attrs) {
5967       return attrs.templateUrl || 'uib/template/tabs/tab.html';
5968     },
5969     transclude: true,
5970     scope: {
5971       heading: '@',
5972       index: '=?',
5973       classes: '@?',
5974       onSelect: '&select', //This callback is called in contentHeadingTransclude
5975                           //once it inserts the tab's content into the dom
5976       onDeselect: '&deselect'
5977     },
5978     controller: function() {
5979       //Empty controller so other directives can require being 'under' a tab
5980     },
5981     controllerAs: 'tab',
5982     link: function(scope, elm, attrs, tabsetCtrl, transclude) {
5983       scope.disabled = false;
5984       if (attrs.disable) {
5985         scope.$parent.$watch($parse(attrs.disable), function(value) {
5986           scope.disabled = !! value;
5987         });
5988       }
5989
5990       if (angular.isUndefined(attrs.index)) {
5991         if (tabsetCtrl.tabs && tabsetCtrl.tabs.length) {
5992           scope.index = Math.max.apply(null, tabsetCtrl.tabs.map(function(t) { return t.index; })) + 1;
5993         } else {
5994           scope.index = 0;
5995         }
5996       }
5997
5998       if (angular.isUndefined(attrs.classes)) {
5999         scope.classes = '';
6000       }
6001
6002       scope.select = function(evt) {
6003         if (!scope.disabled) {
6004           var index;
6005           for (var i = 0; i < tabsetCtrl.tabs.length; i++) {
6006             if (tabsetCtrl.tabs[i].tab === scope) {
6007               index = i;
6008               break;
6009             }
6010           }
6011
6012           tabsetCtrl.select(index, evt);
6013         }
6014       };
6015
6016       tabsetCtrl.addTab(scope);
6017       scope.$on('$destroy', function() {
6018         tabsetCtrl.removeTab(scope);
6019       });
6020
6021       //We need to transclude later, once the content container is ready.
6022       //when this link happens, we're inside a tab heading.
6023       scope.$transcludeFn = transclude;
6024     }
6025   };
6026 }])
6027
6028 .directive('uibTabHeadingTransclude', function() {
6029   return {
6030     restrict: 'A',
6031     require: '^uibTab',
6032     link: function(scope, elm) {
6033       scope.$watch('headingElement', function updateHeadingElement(heading) {
6034         if (heading) {
6035           elm.html('');
6036           elm.append(heading);
6037         }
6038       });
6039     }
6040   };
6041 })
6042
6043 .directive('uibTabContentTransclude', function() {
6044   return {
6045     restrict: 'A',
6046     require: '^uibTabset',
6047     link: function(scope, elm, attrs) {
6048       var tab = scope.$eval(attrs.uibTabContentTransclude).tab;
6049
6050       //Now our tab is ready to be transcluded: both the tab heading area
6051       //and the tab content area are loaded.  Transclude 'em both.
6052       tab.$transcludeFn(tab.$parent, function(contents) {
6053         angular.forEach(contents, function(node) {
6054           if (isTabHeading(node)) {
6055             //Let tabHeadingTransclude know.
6056             tab.headingElement = node;
6057           } else {
6058             elm.append(node);
6059           }
6060         });
6061       });
6062     }
6063   };
6064
6065   function isTabHeading(node) {
6066     return node.tagName && (
6067       node.hasAttribute('uib-tab-heading') ||
6068       node.hasAttribute('data-uib-tab-heading') ||
6069       node.hasAttribute('x-uib-tab-heading') ||
6070       node.tagName.toLowerCase() === 'uib-tab-heading' ||
6071       node.tagName.toLowerCase() === 'data-uib-tab-heading' ||
6072       node.tagName.toLowerCase() === 'x-uib-tab-heading' ||
6073       node.tagName.toLowerCase() === 'uib:tab-heading'
6074     );
6075   }
6076 });
6077
6078 angular.module('ui.bootstrap.timepicker', [])
6079
6080 .constant('uibTimepickerConfig', {
6081   hourStep: 1,
6082   minuteStep: 1,
6083   secondStep: 1,
6084   showMeridian: true,
6085   showSeconds: false,
6086   meridians: null,
6087   readonlyInput: false,
6088   mousewheel: true,
6089   arrowkeys: true,
6090   showSpinners: true,
6091   templateUrl: 'uib/template/timepicker/timepicker.html'
6092 })
6093
6094 .controller('UibTimepickerController', ['$scope', '$element', '$attrs', '$parse', '$log', '$locale', 'uibTimepickerConfig', function($scope, $element, $attrs, $parse, $log, $locale, timepickerConfig) {
6095   var hoursModelCtrl, minutesModelCtrl, secondsModelCtrl;
6096   var selected = new Date(),
6097     watchers = [],
6098     ngModelCtrl = { $setViewValue: angular.noop }, // nullModelCtrl
6099     meridians = angular.isDefined($attrs.meridians) ? $scope.$parent.$eval($attrs.meridians) : timepickerConfig.meridians || $locale.DATETIME_FORMATS.AMPMS,
6100     padHours = angular.isDefined($attrs.padHours) ? $scope.$parent.$eval($attrs.padHours) : true;
6101
6102   $scope.tabindex = angular.isDefined($attrs.tabindex) ? $attrs.tabindex : 0;
6103   $element.removeAttr('tabindex');
6104
6105   this.init = function(ngModelCtrl_, inputs) {
6106     ngModelCtrl = ngModelCtrl_;
6107     ngModelCtrl.$render = this.render;
6108
6109     ngModelCtrl.$formatters.unshift(function(modelValue) {
6110       return modelValue ? new Date(modelValue) : null;
6111     });
6112
6113     var hoursInputEl = inputs.eq(0),
6114         minutesInputEl = inputs.eq(1),
6115         secondsInputEl = inputs.eq(2);
6116
6117     hoursModelCtrl = hoursInputEl.controller('ngModel');
6118     minutesModelCtrl = minutesInputEl.controller('ngModel');
6119     secondsModelCtrl = secondsInputEl.controller('ngModel');
6120
6121     var mousewheel = angular.isDefined($attrs.mousewheel) ? $scope.$parent.$eval($attrs.mousewheel) : timepickerConfig.mousewheel;
6122
6123     if (mousewheel) {
6124       this.setupMousewheelEvents(hoursInputEl, minutesInputEl, secondsInputEl);
6125     }
6126
6127     var arrowkeys = angular.isDefined($attrs.arrowkeys) ? $scope.$parent.$eval($attrs.arrowkeys) : timepickerConfig.arrowkeys;
6128     if (arrowkeys) {
6129       this.setupArrowkeyEvents(hoursInputEl, minutesInputEl, secondsInputEl);
6130     }
6131
6132     $scope.readonlyInput = angular.isDefined($attrs.readonlyInput) ? $scope.$parent.$eval($attrs.readonlyInput) : timepickerConfig.readonlyInput;
6133     this.setupInputEvents(hoursInputEl, minutesInputEl, secondsInputEl);
6134   };
6135
6136   var hourStep = timepickerConfig.hourStep;
6137   if ($attrs.hourStep) {
6138     watchers.push($scope.$parent.$watch($parse($attrs.hourStep), function(value) {
6139       hourStep = +value;
6140     }));
6141   }
6142
6143   var minuteStep = timepickerConfig.minuteStep;
6144   if ($attrs.minuteStep) {
6145     watchers.push($scope.$parent.$watch($parse($attrs.minuteStep), function(value) {
6146       minuteStep = +value;
6147     }));
6148   }
6149
6150   var min;
6151   watchers.push($scope.$parent.$watch($parse($attrs.min), function(value) {
6152     var dt = new Date(value);
6153     min = isNaN(dt) ? undefined : dt;
6154   }));
6155
6156   var max;
6157   watchers.push($scope.$parent.$watch($parse($attrs.max), function(value) {
6158     var dt = new Date(value);
6159     max = isNaN(dt) ? undefined : dt;
6160   }));
6161
6162   var disabled = false;
6163   if ($attrs.ngDisabled) {
6164     watchers.push($scope.$parent.$watch($parse($attrs.ngDisabled), function(value) {
6165       disabled = value;
6166     }));
6167   }
6168
6169   $scope.noIncrementHours = function() {
6170     var incrementedSelected = addMinutes(selected, hourStep * 60);
6171     return disabled || incrementedSelected > max ||
6172       incrementedSelected < selected && incrementedSelected < min;
6173   };
6174
6175   $scope.noDecrementHours = function() {
6176     var decrementedSelected = addMinutes(selected, -hourStep * 60);
6177     return disabled || decrementedSelected < min ||
6178       decrementedSelected > selected && decrementedSelected > max;
6179   };
6180
6181   $scope.noIncrementMinutes = function() {
6182     var incrementedSelected = addMinutes(selected, minuteStep);
6183     return disabled || incrementedSelected > max ||
6184       incrementedSelected < selected && incrementedSelected < min;
6185   };
6186
6187   $scope.noDecrementMinutes = function() {
6188     var decrementedSelected = addMinutes(selected, -minuteStep);
6189     return disabled || decrementedSelected < min ||
6190       decrementedSelected > selected && decrementedSelected > max;
6191   };
6192
6193   $scope.noIncrementSeconds = function() {
6194     var incrementedSelected = addSeconds(selected, secondStep);
6195     return disabled || incrementedSelected > max ||
6196       incrementedSelected < selected && incrementedSelected < min;
6197   };
6198
6199   $scope.noDecrementSeconds = function() {
6200     var decrementedSelected = addSeconds(selected, -secondStep);
6201     return disabled || decrementedSelected < min ||
6202       decrementedSelected > selected && decrementedSelected > max;
6203   };
6204
6205   $scope.noToggleMeridian = function() {
6206     if (selected.getHours() < 12) {
6207       return disabled || addMinutes(selected, 12 * 60) > max;
6208     }
6209
6210     return disabled || addMinutes(selected, -12 * 60) < min;
6211   };
6212
6213   var secondStep = timepickerConfig.secondStep;
6214   if ($attrs.secondStep) {
6215     watchers.push($scope.$parent.$watch($parse($attrs.secondStep), function(value) {
6216       secondStep = +value;
6217     }));
6218   }
6219
6220   $scope.showSeconds = timepickerConfig.showSeconds;
6221   if ($attrs.showSeconds) {
6222     watchers.push($scope.$parent.$watch($parse($attrs.showSeconds), function(value) {
6223       $scope.showSeconds = !!value;
6224     }));
6225   }
6226
6227   // 12H / 24H mode
6228   $scope.showMeridian = timepickerConfig.showMeridian;
6229   if ($attrs.showMeridian) {
6230     watchers.push($scope.$parent.$watch($parse($attrs.showMeridian), function(value) {
6231       $scope.showMeridian = !!value;
6232
6233       if (ngModelCtrl.$error.time) {
6234         // Evaluate from template
6235         var hours = getHoursFromTemplate(), minutes = getMinutesFromTemplate();
6236         if (angular.isDefined(hours) && angular.isDefined(minutes)) {
6237           selected.setHours(hours);
6238           refresh();
6239         }
6240       } else {
6241         updateTemplate();
6242       }
6243     }));
6244   }
6245
6246   // Get $scope.hours in 24H mode if valid
6247   function getHoursFromTemplate() {
6248     var hours = +$scope.hours;
6249     var valid = $scope.showMeridian ? hours > 0 && hours < 13 :
6250       hours >= 0 && hours < 24;
6251     if (!valid || $scope.hours === '') {
6252       return undefined;
6253     }
6254
6255     if ($scope.showMeridian) {
6256       if (hours === 12) {
6257         hours = 0;
6258       }
6259       if ($scope.meridian === meridians[1]) {
6260         hours = hours + 12;
6261       }
6262     }
6263     return hours;
6264   }
6265
6266   function getMinutesFromTemplate() {
6267     var minutes = +$scope.minutes;
6268     var valid = minutes >= 0 && minutes < 60;
6269     if (!valid || $scope.minutes === '') {
6270       return undefined;
6271     }
6272     return minutes;
6273   }
6274
6275   function getSecondsFromTemplate() {
6276     var seconds = +$scope.seconds;
6277     return seconds >= 0 && seconds < 60 ? seconds : undefined;
6278   }
6279
6280   function pad(value, noPad) {
6281     if (value === null) {
6282       return '';
6283     }
6284
6285     return angular.isDefined(value) && value.toString().length < 2 && !noPad ?
6286       '0' + value : value.toString();
6287   }
6288
6289   // Respond on mousewheel spin
6290   this.setupMousewheelEvents = function(hoursInputEl, minutesInputEl, secondsInputEl) {
6291     var isScrollingUp = function(e) {
6292       if (e.originalEvent) {
6293         e = e.originalEvent;
6294       }
6295       //pick correct delta variable depending on event
6296       var delta = e.wheelDelta ? e.wheelDelta : -e.deltaY;
6297       return e.detail || delta > 0;
6298     };
6299
6300     hoursInputEl.on('mousewheel wheel', function(e) {
6301       if (!disabled) {
6302         $scope.$apply(isScrollingUp(e) ? $scope.incrementHours() : $scope.decrementHours());
6303       }
6304       e.preventDefault();
6305     });
6306
6307     minutesInputEl.on('mousewheel wheel', function(e) {
6308       if (!disabled) {
6309         $scope.$apply(isScrollingUp(e) ? $scope.incrementMinutes() : $scope.decrementMinutes());
6310       }
6311       e.preventDefault();
6312     });
6313
6314      secondsInputEl.on('mousewheel wheel', function(e) {
6315       if (!disabled) {
6316         $scope.$apply(isScrollingUp(e) ? $scope.incrementSeconds() : $scope.decrementSeconds());
6317       }
6318       e.preventDefault();
6319     });
6320   };
6321
6322   // Respond on up/down arrowkeys
6323   this.setupArrowkeyEvents = function(hoursInputEl, minutesInputEl, secondsInputEl) {
6324     hoursInputEl.on('keydown', function(e) {
6325       if (!disabled) {
6326         if (e.which === 38) { // up
6327           e.preventDefault();
6328           $scope.incrementHours();
6329           $scope.$apply();
6330         } else if (e.which === 40) { // down
6331           e.preventDefault();
6332           $scope.decrementHours();
6333           $scope.$apply();
6334         }
6335       }
6336     });
6337
6338     minutesInputEl.on('keydown', function(e) {
6339       if (!disabled) {
6340         if (e.which === 38) { // up
6341           e.preventDefault();
6342           $scope.incrementMinutes();
6343           $scope.$apply();
6344         } else if (e.which === 40) { // down
6345           e.preventDefault();
6346           $scope.decrementMinutes();
6347           $scope.$apply();
6348         }
6349       }
6350     });
6351
6352     secondsInputEl.on('keydown', function(e) {
6353       if (!disabled) {
6354         if (e.which === 38) { // up
6355           e.preventDefault();
6356           $scope.incrementSeconds();
6357           $scope.$apply();
6358         } else if (e.which === 40) { // down
6359           e.preventDefault();
6360           $scope.decrementSeconds();
6361           $scope.$apply();
6362         }
6363       }
6364     });
6365   };
6366
6367   this.setupInputEvents = function(hoursInputEl, minutesInputEl, secondsInputEl) {
6368     if ($scope.readonlyInput) {
6369       $scope.updateHours = angular.noop;
6370       $scope.updateMinutes = angular.noop;
6371       $scope.updateSeconds = angular.noop;
6372       return;
6373     }
6374
6375     var invalidate = function(invalidHours, invalidMinutes, invalidSeconds) {
6376       ngModelCtrl.$setViewValue(null);
6377       ngModelCtrl.$setValidity('time', false);
6378       if (angular.isDefined(invalidHours)) {
6379         $scope.invalidHours = invalidHours;
6380         if (hoursModelCtrl) {
6381           hoursModelCtrl.$setValidity('hours', false);
6382         }
6383       }
6384
6385       if (angular.isDefined(invalidMinutes)) {
6386         $scope.invalidMinutes = invalidMinutes;
6387         if (minutesModelCtrl) {
6388           minutesModelCtrl.$setValidity('minutes', false);
6389         }
6390       }
6391
6392       if (angular.isDefined(invalidSeconds)) {
6393         $scope.invalidSeconds = invalidSeconds;
6394         if (secondsModelCtrl) {
6395           secondsModelCtrl.$setValidity('seconds', false);
6396         }
6397       }
6398     };
6399
6400     $scope.updateHours = function() {
6401       var hours = getHoursFromTemplate(),
6402         minutes = getMinutesFromTemplate();
6403
6404       ngModelCtrl.$setDirty();
6405
6406       if (angular.isDefined(hours) && angular.isDefined(minutes)) {
6407         selected.setHours(hours);
6408         selected.setMinutes(minutes);
6409         if (selected < min || selected > max) {
6410           invalidate(true);
6411         } else {
6412           refresh('h');
6413         }
6414       } else {
6415         invalidate(true);
6416       }
6417     };
6418
6419     hoursInputEl.on('blur', function(e) {
6420       ngModelCtrl.$setTouched();
6421       if (modelIsEmpty()) {
6422         makeValid();
6423       } else if ($scope.hours === null || $scope.hours === '') {
6424         invalidate(true);
6425       } else if (!$scope.invalidHours && $scope.hours < 10) {
6426         $scope.$apply(function() {
6427           $scope.hours = pad($scope.hours, !padHours);
6428         });
6429       }
6430     });
6431
6432     $scope.updateMinutes = function() {
6433       var minutes = getMinutesFromTemplate(),
6434         hours = getHoursFromTemplate();
6435
6436       ngModelCtrl.$setDirty();
6437
6438       if (angular.isDefined(minutes) && angular.isDefined(hours)) {
6439         selected.setHours(hours);
6440         selected.setMinutes(minutes);
6441         if (selected < min || selected > max) {
6442           invalidate(undefined, true);
6443         } else {
6444           refresh('m');
6445         }
6446       } else {
6447         invalidate(undefined, true);
6448       }
6449     };
6450
6451     minutesInputEl.on('blur', function(e) {
6452       ngModelCtrl.$setTouched();
6453       if (modelIsEmpty()) {
6454         makeValid();
6455       } else if ($scope.minutes === null) {
6456         invalidate(undefined, true);
6457       } else if (!$scope.invalidMinutes && $scope.minutes < 10) {
6458         $scope.$apply(function() {
6459           $scope.minutes = pad($scope.minutes);
6460         });
6461       }
6462     });
6463
6464     $scope.updateSeconds = function() {
6465       var seconds = getSecondsFromTemplate();
6466
6467       ngModelCtrl.$setDirty();
6468
6469       if (angular.isDefined(seconds)) {
6470         selected.setSeconds(seconds);
6471         refresh('s');
6472       } else {
6473         invalidate(undefined, undefined, true);
6474       }
6475     };
6476
6477     secondsInputEl.on('blur', function(e) {
6478       if (modelIsEmpty()) {
6479         makeValid();
6480       } else if (!$scope.invalidSeconds && $scope.seconds < 10) {
6481         $scope.$apply( function() {
6482           $scope.seconds = pad($scope.seconds);
6483         });
6484       }
6485     });
6486
6487   };
6488
6489   this.render = function() {
6490     var date = ngModelCtrl.$viewValue;
6491
6492     if (isNaN(date)) {
6493       ngModelCtrl.$setValidity('time', false);
6494       $log.error('Timepicker directive: "ng-model" value must be a Date object, a number of milliseconds since 01.01.1970 or a string representing an RFC2822 or ISO 8601 date.');
6495     } else {
6496       if (date) {
6497         selected = date;
6498       }
6499
6500       if (selected < min || selected > max) {
6501         ngModelCtrl.$setValidity('time', false);
6502         $scope.invalidHours = true;
6503         $scope.invalidMinutes = true;
6504       } else {
6505         makeValid();
6506       }
6507       updateTemplate();
6508     }
6509   };
6510
6511   // Call internally when we know that model is valid.
6512   function refresh(keyboardChange) {
6513     makeValid();
6514     ngModelCtrl.$setViewValue(new Date(selected));
6515     updateTemplate(keyboardChange);
6516   }
6517
6518   function makeValid() {
6519     if (hoursModelCtrl) {
6520       hoursModelCtrl.$setValidity('hours', true);
6521     }
6522
6523     if (minutesModelCtrl) {
6524       minutesModelCtrl.$setValidity('minutes', true);
6525     }
6526
6527     if (secondsModelCtrl) {
6528       secondsModelCtrl.$setValidity('seconds', true);
6529     }
6530
6531     ngModelCtrl.$setValidity('time', true);
6532     $scope.invalidHours = false;
6533     $scope.invalidMinutes = false;
6534     $scope.invalidSeconds = false;
6535   }
6536
6537   function updateTemplate(keyboardChange) {
6538     if (!ngModelCtrl.$modelValue) {
6539       $scope.hours = null;
6540       $scope.minutes = null;
6541       $scope.seconds = null;
6542       $scope.meridian = meridians[0];
6543     } else {
6544       var hours = selected.getHours(),
6545         minutes = selected.getMinutes(),
6546         seconds = selected.getSeconds();
6547
6548       if ($scope.showMeridian) {
6549         hours = hours === 0 || hours === 12 ? 12 : hours % 12; // Convert 24 to 12 hour system
6550       }
6551
6552       $scope.hours = keyboardChange === 'h' ? hours : pad(hours, !padHours);
6553       if (keyboardChange !== 'm') {
6554         $scope.minutes = pad(minutes);
6555       }
6556       $scope.meridian = selected.getHours() < 12 ? meridians[0] : meridians[1];
6557
6558       if (keyboardChange !== 's') {
6559         $scope.seconds = pad(seconds);
6560       }
6561       $scope.meridian = selected.getHours() < 12 ? meridians[0] : meridians[1];
6562     }
6563   }
6564
6565   function addSecondsToSelected(seconds) {
6566     selected = addSeconds(selected, seconds);
6567     refresh();
6568   }
6569
6570   function addMinutes(selected, minutes) {
6571     return addSeconds(selected, minutes*60);
6572   }
6573
6574   function addSeconds(date, seconds) {
6575     var dt = new Date(date.getTime() + seconds * 1000);
6576     var newDate = new Date(date);
6577     newDate.setHours(dt.getHours(), dt.getMinutes(), dt.getSeconds());
6578     return newDate;
6579   }
6580
6581   function modelIsEmpty() {
6582     return ($scope.hours === null || $scope.hours === '') &&
6583       ($scope.minutes === null || $scope.minutes === '') &&
6584       (!$scope.showSeconds || $scope.showSeconds && ($scope.seconds === null || $scope.seconds === ''));
6585   }
6586
6587   $scope.showSpinners = angular.isDefined($attrs.showSpinners) ?
6588     $scope.$parent.$eval($attrs.showSpinners) : timepickerConfig.showSpinners;
6589
6590   $scope.incrementHours = function() {
6591     if (!$scope.noIncrementHours()) {
6592       addSecondsToSelected(hourStep * 60 * 60);
6593     }
6594   };
6595
6596   $scope.decrementHours = function() {
6597     if (!$scope.noDecrementHours()) {
6598       addSecondsToSelected(-hourStep * 60 * 60);
6599     }
6600   };
6601
6602   $scope.incrementMinutes = function() {
6603     if (!$scope.noIncrementMinutes()) {
6604       addSecondsToSelected(minuteStep * 60);
6605     }
6606   };
6607
6608   $scope.decrementMinutes = function() {
6609     if (!$scope.noDecrementMinutes()) {
6610       addSecondsToSelected(-minuteStep * 60);
6611     }
6612   };
6613
6614   $scope.incrementSeconds = function() {
6615     if (!$scope.noIncrementSeconds()) {
6616       addSecondsToSelected(secondStep);
6617     }
6618   };
6619
6620   $scope.decrementSeconds = function() {
6621     if (!$scope.noDecrementSeconds()) {
6622       addSecondsToSelected(-secondStep);
6623     }
6624   };
6625
6626   $scope.toggleMeridian = function() {
6627     var minutes = getMinutesFromTemplate(),
6628         hours = getHoursFromTemplate();
6629
6630     if (!$scope.noToggleMeridian()) {
6631       if (angular.isDefined(minutes) && angular.isDefined(hours)) {
6632         addSecondsToSelected(12 * 60 * (selected.getHours() < 12 ? 60 : -60));
6633       } else {
6634         $scope.meridian = $scope.meridian === meridians[0] ? meridians[1] : meridians[0];
6635       }
6636     }
6637   };
6638
6639   $scope.blur = function() {
6640     ngModelCtrl.$setTouched();
6641   };
6642
6643   $scope.$on('$destroy', function() {
6644     while (watchers.length) {
6645       watchers.shift()();
6646     }
6647   });
6648 }])
6649
6650 .directive('uibTimepicker', ['uibTimepickerConfig', function(uibTimepickerConfig) {
6651   return {
6652     require: ['uibTimepicker', '?^ngModel'],
6653     restrict: 'A',
6654     controller: 'UibTimepickerController',
6655     controllerAs: 'timepicker',
6656     scope: {},
6657     templateUrl: function(element, attrs) {
6658       return attrs.templateUrl || uibTimepickerConfig.templateUrl;
6659     },
6660     link: function(scope, element, attrs, ctrls) {
6661       var timepickerCtrl = ctrls[0], ngModelCtrl = ctrls[1];
6662
6663       if (ngModelCtrl) {
6664         timepickerCtrl.init(ngModelCtrl, element.find('input'));
6665       }
6666     }
6667   };
6668 }]);
6669
6670 angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.debounce', 'ui.bootstrap.position'])
6671
6672 /**
6673  * A helper service that can parse typeahead's syntax (string provided by users)
6674  * Extracted to a separate service for ease of unit testing
6675  */
6676   .factory('uibTypeaheadParser', ['$parse', function($parse) {
6677     //                      000001111111100000000000002222222200000000000000003333333333333330000000000044444444000
6678     var TYPEAHEAD_REGEXP = /^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?\s+for\s+(?:([\$\w][\$\w\d]*))\s+in\s+([\s\S]+?)$/;
6679     return {
6680       parse: function(input) {
6681         var match = input.match(TYPEAHEAD_REGEXP);
6682         if (!match) {
6683           throw new Error(
6684             'Expected typeahead specification in form of "_modelValue_ (as _label_)? for _item_ in _collection_"' +
6685               ' but got "' + input + '".');
6686         }
6687
6688         return {
6689           itemName: match[3],
6690           source: $parse(match[4]),
6691           viewMapper: $parse(match[2] || match[1]),
6692           modelMapper: $parse(match[1])
6693         };
6694       }
6695     };
6696   }])
6697
6698   .controller('UibTypeaheadController', ['$scope', '$element', '$attrs', '$compile', '$parse', '$q', '$timeout', '$document', '$window', '$rootScope', '$$debounce', '$uibPosition', 'uibTypeaheadParser',
6699     function(originalScope, element, attrs, $compile, $parse, $q, $timeout, $document, $window, $rootScope, $$debounce, $position, typeaheadParser) {
6700     var HOT_KEYS = [9, 13, 27, 38, 40];
6701     var eventDebounceTime = 200;
6702     var modelCtrl, ngModelOptions;
6703     //SUPPORTED ATTRIBUTES (OPTIONS)
6704
6705     //minimal no of characters that needs to be entered before typeahead kicks-in
6706     var minLength = originalScope.$eval(attrs.typeaheadMinLength);
6707     if (!minLength && minLength !== 0) {
6708       minLength = 1;
6709     }
6710
6711     originalScope.$watch(attrs.typeaheadMinLength, function (newVal) {
6712         minLength = !newVal && newVal !== 0 ? 1 : newVal;
6713     });
6714
6715     //minimal wait time after last character typed before typeahead kicks-in
6716     var waitTime = originalScope.$eval(attrs.typeaheadWaitMs) || 0;
6717
6718     //should it restrict model values to the ones selected from the popup only?
6719     var isEditable = originalScope.$eval(attrs.typeaheadEditable) !== false;
6720     originalScope.$watch(attrs.typeaheadEditable, function (newVal) {
6721       isEditable = newVal !== false;
6722     });
6723
6724     //binding to a variable that indicates if matches are being retrieved asynchronously
6725     var isLoadingSetter = $parse(attrs.typeaheadLoading).assign || angular.noop;
6726
6727     //a function to determine if an event should cause selection
6728     var isSelectEvent = attrs.typeaheadShouldSelect ? $parse(attrs.typeaheadShouldSelect) : function(scope, vals) {
6729       var evt = vals.$event;
6730       return evt.which === 13 || evt.which === 9;
6731     };
6732
6733     //a callback executed when a match is selected
6734     var onSelectCallback = $parse(attrs.typeaheadOnSelect);
6735
6736     //should it select highlighted popup value when losing focus?
6737     var isSelectOnBlur = angular.isDefined(attrs.typeaheadSelectOnBlur) ? originalScope.$eval(attrs.typeaheadSelectOnBlur) : false;
6738
6739     //binding to a variable that indicates if there were no results after the query is completed
6740     var isNoResultsSetter = $parse(attrs.typeaheadNoResults).assign || angular.noop;
6741
6742     var inputFormatter = attrs.typeaheadInputFormatter ? $parse(attrs.typeaheadInputFormatter) : undefined;
6743
6744     var appendToBody = attrs.typeaheadAppendToBody ? originalScope.$eval(attrs.typeaheadAppendToBody) : false;
6745
6746     var appendTo = attrs.typeaheadAppendTo ?
6747       originalScope.$eval(attrs.typeaheadAppendTo) : null;
6748
6749     var focusFirst = originalScope.$eval(attrs.typeaheadFocusFirst) !== false;
6750
6751     //If input matches an item of the list exactly, select it automatically
6752     var selectOnExact = attrs.typeaheadSelectOnExact ? originalScope.$eval(attrs.typeaheadSelectOnExact) : false;
6753
6754     //binding to a variable that indicates if dropdown is open
6755     var isOpenSetter = $parse(attrs.typeaheadIsOpen).assign || angular.noop;
6756
6757     var showHint = originalScope.$eval(attrs.typeaheadShowHint) || false;
6758
6759     //INTERNAL VARIABLES
6760
6761     //model setter executed upon match selection
6762     var parsedModel = $parse(attrs.ngModel);
6763     var invokeModelSetter = $parse(attrs.ngModel + '($$$p)');
6764     var $setModelValue = function(scope, newValue) {
6765       if (angular.isFunction(parsedModel(originalScope)) &&
6766         ngModelOptions && ngModelOptions.$options && ngModelOptions.$options.getterSetter) {
6767         return invokeModelSetter(scope, {$$$p: newValue});
6768       }
6769
6770       return parsedModel.assign(scope, newValue);
6771     };
6772
6773     //expressions used by typeahead
6774     var parserResult = typeaheadParser.parse(attrs.uibTypeahead);
6775
6776     var hasFocus;
6777
6778     //Used to avoid bug in iOS webview where iOS keyboard does not fire
6779     //mousedown & mouseup events
6780     //Issue #3699
6781     var selected;
6782
6783     //create a child scope for the typeahead directive so we are not polluting original scope
6784     //with typeahead-specific data (matches, query etc.)
6785     var scope = originalScope.$new();
6786     var offDestroy = originalScope.$on('$destroy', function() {
6787       scope.$destroy();
6788     });
6789     scope.$on('$destroy', offDestroy);
6790
6791     // WAI-ARIA
6792     var popupId = 'typeahead-' + scope.$id + '-' + Math.floor(Math.random() * 10000);
6793     element.attr({
6794       'aria-autocomplete': 'list',
6795       'aria-expanded': false,
6796       'aria-owns': popupId
6797     });
6798
6799     var inputsContainer, hintInputElem;
6800     //add read-only input to show hint
6801     if (showHint) {
6802       inputsContainer = angular.element('<div></div>');
6803       inputsContainer.css('position', 'relative');
6804       element.after(inputsContainer);
6805       hintInputElem = element.clone();
6806       hintInputElem.attr('placeholder', '');
6807       hintInputElem.attr('tabindex', '-1');
6808       hintInputElem.val('');
6809       hintInputElem.css({
6810         'position': 'absolute',
6811         'top': '0px',
6812         'left': '0px',
6813         'border-color': 'transparent',
6814         'box-shadow': 'none',
6815         'opacity': 1,
6816         'background': 'none 0% 0% / auto repeat scroll padding-box border-box rgb(255, 255, 255)',
6817         'color': '#999'
6818       });
6819       element.css({
6820         'position': 'relative',
6821         'vertical-align': 'top',
6822         'background-color': 'transparent'
6823       });
6824
6825       if (hintInputElem.attr('id')) {
6826         hintInputElem.removeAttr('id'); // remove duplicate id if present.
6827       }
6828       inputsContainer.append(hintInputElem);
6829       hintInputElem.after(element);
6830     }
6831
6832     //pop-up element used to display matches
6833     var popUpEl = angular.element('<div uib-typeahead-popup></div>');
6834     popUpEl.attr({
6835       id: popupId,
6836       matches: 'matches',
6837       active: 'activeIdx',
6838       select: 'select(activeIdx, evt)',
6839       'move-in-progress': 'moveInProgress',
6840       query: 'query',
6841       position: 'position',
6842       'assign-is-open': 'assignIsOpen(isOpen)',
6843       debounce: 'debounceUpdate'
6844     });
6845     //custom item template
6846     if (angular.isDefined(attrs.typeaheadTemplateUrl)) {
6847       popUpEl.attr('template-url', attrs.typeaheadTemplateUrl);
6848     }
6849
6850     if (angular.isDefined(attrs.typeaheadPopupTemplateUrl)) {
6851       popUpEl.attr('popup-template-url', attrs.typeaheadPopupTemplateUrl);
6852     }
6853
6854     var resetHint = function() {
6855       if (showHint) {
6856         hintInputElem.val('');
6857       }
6858     };
6859
6860     var resetMatches = function() {
6861       scope.matches = [];
6862       scope.activeIdx = -1;
6863       element.attr('aria-expanded', false);
6864       resetHint();
6865     };
6866
6867     var getMatchId = function(index) {
6868       return popupId + '-option-' + index;
6869     };
6870
6871     // Indicate that the specified match is the active (pre-selected) item in the list owned by this typeahead.
6872     // This attribute is added or removed automatically when the `activeIdx` changes.
6873     scope.$watch('activeIdx', function(index) {
6874       if (index < 0) {
6875         element.removeAttr('aria-activedescendant');
6876       } else {
6877         element.attr('aria-activedescendant', getMatchId(index));
6878       }
6879     });
6880
6881     var inputIsExactMatch = function(inputValue, index) {
6882       if (scope.matches.length > index && inputValue) {
6883         return inputValue.toUpperCase() === scope.matches[index].label.toUpperCase();
6884       }
6885
6886       return false;
6887     };
6888
6889     var getMatchesAsync = function(inputValue, evt) {
6890       var locals = {$viewValue: inputValue};
6891       isLoadingSetter(originalScope, true);
6892       isNoResultsSetter(originalScope, false);
6893       $q.when(parserResult.source(originalScope, locals)).then(function(matches) {
6894         //it might happen that several async queries were in progress if a user were typing fast
6895         //but we are interested only in responses that correspond to the current view value
6896         var onCurrentRequest = inputValue === modelCtrl.$viewValue;
6897         if (onCurrentRequest && hasFocus) {
6898           if (matches && matches.length > 0) {
6899             scope.activeIdx = focusFirst ? 0 : -1;
6900             isNoResultsSetter(originalScope, false);
6901             scope.matches.length = 0;
6902
6903             //transform labels
6904             for (var i = 0; i < matches.length; i++) {
6905               locals[parserResult.itemName] = matches[i];
6906               scope.matches.push({
6907                 id: getMatchId(i),
6908                 label: parserResult.viewMapper(scope, locals),
6909                 model: matches[i]
6910               });
6911             }
6912
6913             scope.query = inputValue;
6914             //position pop-up with matches - we need to re-calculate its position each time we are opening a window
6915             //with matches as a pop-up might be absolute-positioned and position of an input might have changed on a page
6916             //due to other elements being rendered
6917             recalculatePosition();
6918
6919             element.attr('aria-expanded', true);
6920
6921             //Select the single remaining option if user input matches
6922             if (selectOnExact && scope.matches.length === 1 && inputIsExactMatch(inputValue, 0)) {
6923               if (angular.isNumber(scope.debounceUpdate) || angular.isObject(scope.debounceUpdate)) {
6924                 $$debounce(function() {
6925                   scope.select(0, evt);
6926                 }, angular.isNumber(scope.debounceUpdate) ? scope.debounceUpdate : scope.debounceUpdate['default']);
6927               } else {
6928                 scope.select(0, evt);
6929               }
6930             }
6931
6932             if (showHint) {
6933               var firstLabel = scope.matches[0].label;
6934               if (angular.isString(inputValue) &&
6935                 inputValue.length > 0 &&
6936                 firstLabel.slice(0, inputValue.length).toUpperCase() === inputValue.toUpperCase()) {
6937                 hintInputElem.val(inputValue + firstLabel.slice(inputValue.length));
6938               } else {
6939                 hintInputElem.val('');
6940               }
6941             }
6942           } else {
6943             resetMatches();
6944             isNoResultsSetter(originalScope, true);
6945           }
6946         }
6947         if (onCurrentRequest) {
6948           isLoadingSetter(originalScope, false);
6949         }
6950       }, function() {
6951         resetMatches();
6952         isLoadingSetter(originalScope, false);
6953         isNoResultsSetter(originalScope, true);
6954       });
6955     };
6956
6957     // bind events only if appendToBody params exist - performance feature
6958     if (appendToBody) {
6959       angular.element($window).on('resize', fireRecalculating);
6960       $document.find('body').on('scroll', fireRecalculating);
6961     }
6962
6963     // Declare the debounced function outside recalculating for
6964     // proper debouncing
6965     var debouncedRecalculate = $$debounce(function() {
6966       // if popup is visible
6967       if (scope.matches.length) {
6968         recalculatePosition();
6969       }
6970
6971       scope.moveInProgress = false;
6972     }, eventDebounceTime);
6973
6974     // Default progress type
6975     scope.moveInProgress = false;
6976
6977     function fireRecalculating() {
6978       if (!scope.moveInProgress) {
6979         scope.moveInProgress = true;
6980         scope.$digest();
6981       }
6982
6983       debouncedRecalculate();
6984     }
6985
6986     // recalculate actual position and set new values to scope
6987     // after digest loop is popup in right position
6988     function recalculatePosition() {
6989       scope.position = appendToBody ? $position.offset(element) : $position.position(element);
6990       scope.position.top += element.prop('offsetHeight');
6991     }
6992
6993     //we need to propagate user's query so we can higlight matches
6994     scope.query = undefined;
6995
6996     //Declare the timeout promise var outside the function scope so that stacked calls can be cancelled later
6997     var timeoutPromise;
6998
6999     var scheduleSearchWithTimeout = function(inputValue) {
7000       timeoutPromise = $timeout(function() {
7001         getMatchesAsync(inputValue);
7002       }, waitTime);
7003     };
7004
7005     var cancelPreviousTimeout = function() {
7006       if (timeoutPromise) {
7007         $timeout.cancel(timeoutPromise);
7008       }
7009     };
7010
7011     resetMatches();
7012
7013     scope.assignIsOpen = function (isOpen) {
7014       isOpenSetter(originalScope, isOpen);
7015     };
7016
7017     scope.select = function(activeIdx, evt) {
7018       //called from within the $digest() cycle
7019       var locals = {};
7020       var model, item;
7021
7022       selected = true;
7023       locals[parserResult.itemName] = item = scope.matches[activeIdx].model;
7024       model = parserResult.modelMapper(originalScope, locals);
7025       $setModelValue(originalScope, model);
7026       modelCtrl.$setValidity('editable', true);
7027       modelCtrl.$setValidity('parse', true);
7028
7029       onSelectCallback(originalScope, {
7030         $item: item,
7031         $model: model,
7032         $label: parserResult.viewMapper(originalScope, locals),
7033         $event: evt
7034       });
7035
7036       resetMatches();
7037
7038       //return focus to the input element if a match was selected via a mouse click event
7039       // use timeout to avoid $rootScope:inprog error
7040       if (scope.$eval(attrs.typeaheadFocusOnSelect) !== false) {
7041         $timeout(function() { element[0].focus(); }, 0, false);
7042       }
7043     };
7044
7045     //bind keyboard events: arrows up(38) / down(40), enter(13) and tab(9), esc(27)
7046     element.on('keydown', function(evt) {
7047       //typeahead is open and an "interesting" key was pressed
7048       if (scope.matches.length === 0 || HOT_KEYS.indexOf(evt.which) === -1) {
7049         return;
7050       }
7051
7052       var shouldSelect = isSelectEvent(originalScope, {$event: evt});
7053
7054       /**
7055        * if there's nothing selected (i.e. focusFirst) and enter or tab is hit
7056        * or
7057        * shift + tab is pressed to bring focus to the previous element
7058        * then clear the results
7059        */
7060       if (scope.activeIdx === -1 && shouldSelect || evt.which === 9 && !!evt.shiftKey) {
7061         resetMatches();
7062         scope.$digest();
7063         return;
7064       }
7065
7066       evt.preventDefault();
7067       var target;
7068       switch (evt.which) {
7069         case 27: // escape
7070           evt.stopPropagation();
7071
7072           resetMatches();
7073           originalScope.$digest();
7074           break;
7075         case 38: // up arrow
7076           scope.activeIdx = (scope.activeIdx > 0 ? scope.activeIdx : scope.matches.length) - 1;
7077           scope.$digest();
7078           target = popUpEl[0].querySelectorAll('.uib-typeahead-match')[scope.activeIdx];
7079           target.parentNode.scrollTop = target.offsetTop;
7080           break;
7081         case 40: // down arrow
7082           scope.activeIdx = (scope.activeIdx + 1) % scope.matches.length;
7083           scope.$digest();
7084           target = popUpEl[0].querySelectorAll('.uib-typeahead-match')[scope.activeIdx];
7085           target.parentNode.scrollTop = target.offsetTop;
7086           break;
7087         default:
7088           if (shouldSelect) {
7089             scope.$apply(function() {
7090               if (angular.isNumber(scope.debounceUpdate) || angular.isObject(scope.debounceUpdate)) {
7091                 $$debounce(function() {
7092                   scope.select(scope.activeIdx, evt);
7093                 }, angular.isNumber(scope.debounceUpdate) ? scope.debounceUpdate : scope.debounceUpdate['default']);
7094               } else {
7095                 scope.select(scope.activeIdx, evt);
7096               }
7097             });
7098           }
7099       }
7100     });
7101
7102     element.on('focus', function (evt) {
7103       hasFocus = true;
7104       if (minLength === 0 && !modelCtrl.$viewValue) {
7105         $timeout(function() {
7106           getMatchesAsync(modelCtrl.$viewValue, evt);
7107         }, 0);
7108       }
7109     });
7110
7111     element.on('blur', function(evt) {
7112       if (isSelectOnBlur && scope.matches.length && scope.activeIdx !== -1 && !selected) {
7113         selected = true;
7114         scope.$apply(function() {
7115           if (angular.isObject(scope.debounceUpdate) && angular.isNumber(scope.debounceUpdate.blur)) {
7116             $$debounce(function() {
7117               scope.select(scope.activeIdx, evt);
7118             }, scope.debounceUpdate.blur);
7119           } else {
7120             scope.select(scope.activeIdx, evt);
7121           }
7122         });
7123       }
7124       if (!isEditable && modelCtrl.$error.editable) {
7125         modelCtrl.$setViewValue();
7126         scope.$apply(function() {
7127           // Reset validity as we are clearing
7128           modelCtrl.$setValidity('editable', true);
7129           modelCtrl.$setValidity('parse', true);
7130         });
7131         element.val('');
7132       }
7133       hasFocus = false;
7134       selected = false;
7135     });
7136
7137     // Keep reference to click handler to unbind it.
7138     var dismissClickHandler = function(evt) {
7139       // Issue #3973
7140       // Firefox treats right click as a click on document
7141       if (element[0] !== evt.target && evt.which !== 3 && scope.matches.length !== 0) {
7142         resetMatches();
7143         if (!$rootScope.$$phase) {
7144           originalScope.$digest();
7145         }
7146       }
7147     };
7148
7149     $document.on('click', dismissClickHandler);
7150
7151     originalScope.$on('$destroy', function() {
7152       $document.off('click', dismissClickHandler);
7153       if (appendToBody || appendTo) {
7154         $popup.remove();
7155       }
7156
7157       if (appendToBody) {
7158         angular.element($window).off('resize', fireRecalculating);
7159         $document.find('body').off('scroll', fireRecalculating);
7160       }
7161       // Prevent jQuery cache memory leak
7162       popUpEl.remove();
7163
7164       if (showHint) {
7165           inputsContainer.remove();
7166       }
7167     });
7168
7169     var $popup = $compile(popUpEl)(scope);
7170
7171     if (appendToBody) {
7172       $document.find('body').append($popup);
7173     } else if (appendTo) {
7174       angular.element(appendTo).eq(0).append($popup);
7175     } else {
7176       element.after($popup);
7177     }
7178
7179     this.init = function(_modelCtrl, _ngModelOptions) {
7180       modelCtrl = _modelCtrl;
7181       ngModelOptions = _ngModelOptions;
7182
7183       scope.debounceUpdate = modelCtrl.$options && $parse(modelCtrl.$options.debounce)(originalScope);
7184
7185       //plug into $parsers pipeline to open a typeahead on view changes initiated from DOM
7186       //$parsers kick-in on all the changes coming from the view as well as manually triggered by $setViewValue
7187       modelCtrl.$parsers.unshift(function(inputValue) {
7188         hasFocus = true;
7189
7190         if (minLength === 0 || inputValue && inputValue.length >= minLength) {
7191           if (waitTime > 0) {
7192             cancelPreviousTimeout();
7193             scheduleSearchWithTimeout(inputValue);
7194           } else {
7195             getMatchesAsync(inputValue);
7196           }
7197         } else {
7198           isLoadingSetter(originalScope, false);
7199           cancelPreviousTimeout();
7200           resetMatches();
7201         }
7202
7203         if (isEditable) {
7204           return inputValue;
7205         }
7206
7207         if (!inputValue) {
7208           // Reset in case user had typed something previously.
7209           modelCtrl.$setValidity('editable', true);
7210           return null;
7211         }
7212
7213         modelCtrl.$setValidity('editable', false);
7214         return undefined;
7215       });
7216
7217       modelCtrl.$formatters.push(function(modelValue) {
7218         var candidateViewValue, emptyViewValue;
7219         var locals = {};
7220
7221         // The validity may be set to false via $parsers (see above) if
7222         // the model is restricted to selected values. If the model
7223         // is set manually it is considered to be valid.
7224         if (!isEditable) {
7225           modelCtrl.$setValidity('editable', true);
7226         }
7227
7228         if (inputFormatter) {
7229           locals.$model = modelValue;
7230           return inputFormatter(originalScope, locals);
7231         }
7232
7233         //it might happen that we don't have enough info to properly render input value
7234         //we need to check for this situation and simply return model value if we can't apply custom formatting
7235         locals[parserResult.itemName] = modelValue;
7236         candidateViewValue = parserResult.viewMapper(originalScope, locals);
7237         locals[parserResult.itemName] = undefined;
7238         emptyViewValue = parserResult.viewMapper(originalScope, locals);
7239
7240         return candidateViewValue !== emptyViewValue ? candidateViewValue : modelValue;
7241       });
7242     };
7243   }])
7244
7245   .directive('uibTypeahead', function() {
7246     return {
7247       controller: 'UibTypeaheadController',
7248       require: ['ngModel', '^?ngModelOptions', 'uibTypeahead'],
7249       link: function(originalScope, element, attrs, ctrls) {
7250         ctrls[2].init(ctrls[0], ctrls[1]);
7251       }
7252     };
7253   })
7254
7255   .directive('uibTypeaheadPopup', ['$$debounce', function($$debounce) {
7256     return {
7257       scope: {
7258         matches: '=',
7259         query: '=',
7260         active: '=',
7261         position: '&',
7262         moveInProgress: '=',
7263         select: '&',
7264         assignIsOpen: '&',
7265         debounce: '&'
7266       },
7267       replace: true,
7268       templateUrl: function(element, attrs) {
7269         return attrs.popupTemplateUrl || 'uib/template/typeahead/typeahead-popup.html';
7270       },
7271       link: function(scope, element, attrs) {
7272         scope.templateUrl = attrs.templateUrl;
7273
7274         scope.isOpen = function() {
7275           var isDropdownOpen = scope.matches.length > 0;
7276           scope.assignIsOpen({ isOpen: isDropdownOpen });
7277           return isDropdownOpen;
7278         };
7279
7280         scope.isActive = function(matchIdx) {
7281           return scope.active === matchIdx;
7282         };
7283
7284         scope.selectActive = function(matchIdx) {
7285           scope.active = matchIdx;
7286         };
7287
7288         scope.selectMatch = function(activeIdx, evt) {
7289           var debounce = scope.debounce();
7290           if (angular.isNumber(debounce) || angular.isObject(debounce)) {
7291             $$debounce(function() {
7292               scope.select({activeIdx: activeIdx, evt: evt});
7293             }, angular.isNumber(debounce) ? debounce : debounce['default']);
7294           } else {
7295             scope.select({activeIdx: activeIdx, evt: evt});
7296           }
7297         };
7298       }
7299     };
7300   }])
7301
7302   .directive('uibTypeaheadMatch', ['$templateRequest', '$compile', '$parse', function($templateRequest, $compile, $parse) {
7303     return {
7304       scope: {
7305         index: '=',
7306         match: '=',
7307         query: '='
7308       },
7309       link: function(scope, element, attrs) {
7310         var tplUrl = $parse(attrs.templateUrl)(scope.$parent) || 'uib/template/typeahead/typeahead-match.html';
7311         $templateRequest(tplUrl).then(function(tplContent) {
7312           var tplEl = angular.element(tplContent.trim());
7313           element.replaceWith(tplEl);
7314           $compile(tplEl)(scope);
7315         });
7316       }
7317     };
7318   }])
7319
7320   .filter('uibTypeaheadHighlight', ['$sce', '$injector', '$log', function($sce, $injector, $log) {
7321     var isSanitizePresent;
7322     isSanitizePresent = $injector.has('$sanitize');
7323
7324     function escapeRegexp(queryToEscape) {
7325       // Regex: capture the whole query string and replace it with the string that will be used to match
7326       // the results, for example if the capture is "a" the result will be \a
7327       return queryToEscape.replace(/([.?*+^$[\]\\(){}|-])/g, '\\$1');
7328     }
7329
7330     function containsHtml(matchItem) {
7331       return /<.*>/g.test(matchItem);
7332     }
7333
7334     return function(matchItem, query) {
7335       if (!isSanitizePresent && containsHtml(matchItem)) {
7336         $log.warn('Unsafe use of typeahead please use ngSanitize'); // Warn the user about the danger
7337       }
7338       matchItem = query ? ('' + matchItem).replace(new RegExp(escapeRegexp(query), 'gi'), '<strong>$&</strong>') : matchItem; // Replaces the capture string with a the same string inside of a "strong" tag
7339       if (!isSanitizePresent) {
7340         matchItem = $sce.trustAsHtml(matchItem); // If $sanitize is not present we pack the string in a $sce object for the ng-bind-html directive
7341       }
7342       return matchItem;
7343     };
7344   }]);
7345 angular.module('ui.bootstrap.carousel').run(function() {!angular.$$csp().noInlineStyle && !angular.$$uibCarouselCss && angular.element(document).find('head').prepend('<style type="text/css">.ng-animate.item:not(.left):not(.right){-webkit-transition:0s ease-in-out left;transition:0s ease-in-out left}</style>'); angular.$$uibCarouselCss = true; });
7346 angular.module('ui.bootstrap.datepicker').run(function() {!angular.$$csp().noInlineStyle && !angular.$$uibDatepickerCss && angular.element(document).find('head').prepend('<style type="text/css">.uib-datepicker .uib-title{width:100%;}.uib-day button,.uib-month button,.uib-year button{min-width:100%;}.uib-left,.uib-right{width:100%}</style>'); angular.$$uibDatepickerCss = true; });
7347 angular.module('ui.bootstrap.position').run(function() {!angular.$$csp().noInlineStyle && !angular.$$uibPositionCss && angular.element(document).find('head').prepend('<style type="text/css">.uib-position-measure{display:block !important;visibility:hidden !important;position:absolute !important;top:-9999px !important;left:-9999px !important;}.uib-position-scrollbar-measure{position:absolute !important;top:-9999px !important;width:50px !important;height:50px !important;overflow:scroll !important;}.uib-position-body-scrollbar-measure{overflow:scroll !important;}</style>'); angular.$$uibPositionCss = true; });
7348 angular.module('ui.bootstrap.datepickerPopup').run(function() {!angular.$$csp().noInlineStyle && !angular.$$uibDatepickerpopupCss && angular.element(document).find('head').prepend('<style type="text/css">.uib-datepicker-popup.dropdown-menu{display:block;float:none;margin:0;}.uib-button-bar{padding:10px 9px 2px;}</style>'); angular.$$uibDatepickerpopupCss = true; });
7349 angular.module('ui.bootstrap.tooltip').run(function() {!angular.$$csp().noInlineStyle && !angular.$$uibTooltipCss && angular.element(document).find('head').prepend('<style type="text/css">[uib-tooltip-popup].tooltip.top-left > .tooltip-arrow,[uib-tooltip-popup].tooltip.top-right > .tooltip-arrow,[uib-tooltip-popup].tooltip.bottom-left > .tooltip-arrow,[uib-tooltip-popup].tooltip.bottom-right > .tooltip-arrow,[uib-tooltip-popup].tooltip.left-top > .tooltip-arrow,[uib-tooltip-popup].tooltip.left-bottom > .tooltip-arrow,[uib-tooltip-popup].tooltip.right-top > .tooltip-arrow,[uib-tooltip-popup].tooltip.right-bottom > .tooltip-arrow,[uib-tooltip-html-popup].tooltip.top-left > .tooltip-arrow,[uib-tooltip-html-popup].tooltip.top-right > .tooltip-arrow,[uib-tooltip-html-popup].tooltip.bottom-left > .tooltip-arrow,[uib-tooltip-html-popup].tooltip.bottom-right > .tooltip-arrow,[uib-tooltip-html-popup].tooltip.left-top > .tooltip-arrow,[uib-tooltip-html-popup].tooltip.left-bottom > .tooltip-arrow,[uib-tooltip-html-popup].tooltip.right-top > .tooltip-arrow,[uib-tooltip-html-popup].tooltip.right-bottom > .tooltip-arrow,[uib-tooltip-template-popup].tooltip.top-left > .tooltip-arrow,[uib-tooltip-template-popup].tooltip.top-right > .tooltip-arrow,[uib-tooltip-template-popup].tooltip.bottom-left > .tooltip-arrow,[uib-tooltip-template-popup].tooltip.bottom-right > .tooltip-arrow,[uib-tooltip-template-popup].tooltip.left-top > .tooltip-arrow,[uib-tooltip-template-popup].tooltip.left-bottom > .tooltip-arrow,[uib-tooltip-template-popup].tooltip.right-top > .tooltip-arrow,[uib-tooltip-template-popup].tooltip.right-bottom > .tooltip-arrow,[uib-popover-popup].popover.top-left > .arrow,[uib-popover-popup].popover.top-right > .arrow,[uib-popover-popup].popover.bottom-left > .arrow,[uib-popover-popup].popover.bottom-right > .arrow,[uib-popover-popup].popover.left-top > .arrow,[uib-popover-popup].popover.left-bottom > .arrow,[uib-popover-popup].popover.right-top > .arrow,[uib-popover-popup].popover.right-bottom > .arrow,[uib-popover-html-popup].popover.top-left > .arrow,[uib-popover-html-popup].popover.top-right > .arrow,[uib-popover-html-popup].popover.bottom-left > .arrow,[uib-popover-html-popup].popover.bottom-right > .arrow,[uib-popover-html-popup].popover.left-top > .arrow,[uib-popover-html-popup].popover.left-bottom > .arrow,[uib-popover-html-popup].popover.right-top > .arrow,[uib-popover-html-popup].popover.right-bottom > .arrow,[uib-popover-template-popup].popover.top-left > .arrow,[uib-popover-template-popup].popover.top-right > .arrow,[uib-popover-template-popup].popover.bottom-left > .arrow,[uib-popover-template-popup].popover.bottom-right > .arrow,[uib-popover-template-popup].popover.left-top > .arrow,[uib-popover-template-popup].popover.left-bottom > .arrow,[uib-popover-template-popup].popover.right-top > .arrow,[uib-popover-template-popup].popover.right-bottom > .arrow{top:auto;bottom:auto;left:auto;right:auto;margin:0;}[uib-popover-popup].popover,[uib-popover-html-popup].popover,[uib-popover-template-popup].popover{display:block !important;}</style>'); angular.$$uibTooltipCss = true; });
7350 angular.module('ui.bootstrap.timepicker').run(function() {!angular.$$csp().noInlineStyle && !angular.$$uibTimepickerCss && angular.element(document).find('head').prepend('<style type="text/css">.uib-time input{width:50px;}</style>'); angular.$$uibTimepickerCss = true; });
7351 angular.module('ui.bootstrap.typeahead').run(function() {!angular.$$csp().noInlineStyle && !angular.$$uibTypeaheadCss && angular.element(document).find('head').prepend('<style type="text/css">[uib-typeahead-popup].dropdown-menu{display:block;}</style>'); angular.$$uibTypeaheadCss = true; });