PageSourceSearch

https://usssa.com/js/_lib/_-8_ui-bootstrap.js

js usssa.com collected 2026-09-24 09:31:30 UTC 256,869 bytes, 7,412 lines download raw bytes

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

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.