rails-angular-strap 2.3.0 → 2.3.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 6ca08ade997b9fb83f4092112a2291ed1aee89f8
4
- data.tar.gz: 57db25fd39a7dbcfa85541abc893da50bbbecdd7
3
+ metadata.gz: 1c8e838cb93123152a1d3f8f2a365c8fec8f815a
4
+ data.tar.gz: 6e6ff5c2e1bae4d1f82ad98c2e3ce8249a268def
5
5
  SHA512:
6
- metadata.gz: bf407af6a715b1db44a4d02df012824b675b1afa83cd7629e68d07a2d6d2c5cb1b99f2c87262a6072aacd432660cd4acbbd76091bac14c0fc1745de95f2261a2
7
- data.tar.gz: 7dcf773a32489c2693dc1009628ad942dd3d3a1ecff17773ff13dd3ce4be1f03e128155bc8e8e1eb7f690cf71caf87bc4559d34ad51996a1ffff9407acbc793b
6
+ metadata.gz: e5e608f5a91b27d900b5509817d0287d132f9212b3b2992b18616ecc15ad32cf698117d11255e5a6e6410db595ba48412a00fca9ee286d39a4840a4e5f1bfb71
7
+ data.tar.gz: b804b3ca5e3a8452314fe9c9e2f3fdb6701bea6455cd699383ac5f67cb29f4047d1381d2b10bbe8e90726e283a3099d0c79c36fad1b4e93255c1e7a3f59264c7
data/README.md CHANGED
@@ -18,4 +18,4 @@ If you desire to require minified AngularStrap files, add the following:
18
18
 
19
19
  ## Versioning
20
20
 
21
- Current version of AngularStrap - 2.3.0
21
+ Current version of AngularStrap - 2.3.1
@@ -1,3 +1,3 @@
1
1
  module AngularStrapRails
2
- VERSION = "2.3.0"
2
+ VERSION = "2.3.1"
3
3
  end
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * angular-strap
3
- * @version v2.3.0 - 2015-07-12
3
+ * @version v2.3.1 - 2015-07-19
4
4
  * @link http://mgcrea.github.io/angular-strap
5
5
  * @author Olivier Louvignes <olivier@mg-crea.com> (https://github.com/mgcrea)
6
6
  * @license MIT License, http://www.opensource.org/licenses/MIT
@@ -802,6 +802,7 @@
802
802
  };
803
803
  var _show = $datepicker.show;
804
804
  $datepicker.show = function() {
805
+ if (!isTouch && element.attr('readonly') || element.attr('disabled')) return;
805
806
  _show();
806
807
  $timeout(function() {
807
808
  if (!$datepicker.$isShown) return;
@@ -1800,13 +1801,7 @@
1800
1801
  valuesFn = $parse(match[7]);
1801
1802
  };
1802
1803
  $parseOptions.valuesFn = function(scope, controller) {
1803
- var valuesPromise;
1804
- try {
1805
- valuesPromise = valuesFn(scope, controller);
1806
- } catch (err) {
1807
- valuesPromise = [];
1808
- }
1809
- return $q.when(valuesPromise).then(function(values) {
1804
+ return $q.when(valuesFn(scope, controller)).then(function(values) {
1810
1805
  if (!angular.isArray(values)) {
1811
1806
  values = [];
1812
1807
  }
@@ -2161,6 +2156,90 @@
2161
2156
  }
2162
2157
  };
2163
2158
  } ]);
2159
+ angular.module('mgcrea.ngStrap.popover', [ 'mgcrea.ngStrap.tooltip' ]).provider('$popover', function() {
2160
+ var defaults = this.defaults = {
2161
+ animation: 'am-fade',
2162
+ customClass: '',
2163
+ container: false,
2164
+ target: false,
2165
+ placement: 'right',
2166
+ templateUrl: 'popover/popover.tpl.html',
2167
+ contentTemplate: false,
2168
+ trigger: 'click',
2169
+ keyboard: true,
2170
+ html: false,
2171
+ title: '',
2172
+ content: '',
2173
+ delay: 0,
2174
+ autoClose: false
2175
+ };
2176
+ this.$get = [ '$tooltip', function($tooltip) {
2177
+ function PopoverFactory(element, config) {
2178
+ var options = angular.extend({}, defaults, config);
2179
+ var $popover = $tooltip(element, options);
2180
+ if (options.content) {
2181
+ $popover.$scope.content = options.content;
2182
+ }
2183
+ return $popover;
2184
+ }
2185
+ return PopoverFactory;
2186
+ } ];
2187
+ }).directive('bsPopover', [ '$window', '$sce', '$popover', function($window, $sce, $popover) {
2188
+ var requestAnimationFrame = $window.requestAnimationFrame || $window.setTimeout;
2189
+ return {
2190
+ restrict: 'EAC',
2191
+ scope: true,
2192
+ link: function postLink(scope, element, attr) {
2193
+ var options = {
2194
+ scope: scope
2195
+ };
2196
+ angular.forEach([ 'template', 'templateUrl', 'controller', 'controllerAs', 'contentTemplate', 'placement', 'container', 'delay', 'trigger', 'html', 'animation', 'customClass', 'autoClose', 'id', 'prefixClass', 'prefixEvent' ], function(key) {
2197
+ if (angular.isDefined(attr[key])) options[key] = attr[key];
2198
+ });
2199
+ var falseValueRegExp = /^(false|0|)$/i;
2200
+ angular.forEach([ 'html', 'container', 'autoClose' ], function(key) {
2201
+ if (angular.isDefined(attr[key]) && falseValueRegExp.test(attr[key])) options[key] = false;
2202
+ });
2203
+ var dataTarget = element.attr('data-target');
2204
+ if (angular.isDefined(dataTarget)) {
2205
+ if (falseValueRegExp.test(dataTarget)) options.target = false; else options.target = dataTarget;
2206
+ }
2207
+ angular.forEach([ 'title', 'content' ], function(key) {
2208
+ attr[key] && attr.$observe(key, function(newValue, oldValue) {
2209
+ scope[key] = $sce.trustAsHtml(newValue);
2210
+ angular.isDefined(oldValue) && requestAnimationFrame(function() {
2211
+ popover && popover.$applyPlacement();
2212
+ });
2213
+ });
2214
+ });
2215
+ attr.bsPopover && scope.$watch(attr.bsPopover, function(newValue, oldValue) {
2216
+ if (angular.isObject(newValue)) {
2217
+ angular.extend(scope, newValue);
2218
+ } else {
2219
+ scope.content = newValue;
2220
+ }
2221
+ angular.isDefined(oldValue) && requestAnimationFrame(function() {
2222
+ popover && popover.$applyPlacement();
2223
+ });
2224
+ }, true);
2225
+ attr.bsShow && scope.$watch(attr.bsShow, function(newValue, oldValue) {
2226
+ if (!popover || !angular.isDefined(newValue)) return;
2227
+ if (angular.isString(newValue)) newValue = !!newValue.match(/true|,?(popover),?/i);
2228
+ newValue === true ? popover.show() : popover.hide();
2229
+ });
2230
+ attr.viewport && scope.$watch(attr.viewport, function(newValue) {
2231
+ if (!popover || !angular.isDefined(newValue)) return;
2232
+ popover.setViewport(newValue);
2233
+ });
2234
+ var popover = $popover(element, options);
2235
+ scope.$on('$destroy', function() {
2236
+ if (popover) popover.destroy();
2237
+ options = null;
2238
+ popover = null;
2239
+ });
2240
+ }
2241
+ };
2242
+ } ]);
2164
2243
  angular.module('mgcrea.ngStrap.scrollspy', [ 'mgcrea.ngStrap.helpers.debounce', 'mgcrea.ngStrap.helpers.dimensions' ]).provider('$scrollspy', function() {
2165
2244
  var spies = this.$$spies = {};
2166
2245
  var defaults = this.defaults = {
@@ -2595,1422 +2674,1357 @@
2595
2674
  }
2596
2675
  };
2597
2676
  } ]);
2598
- angular.module('mgcrea.ngStrap.popover', [ 'mgcrea.ngStrap.tooltip' ]).provider('$popover', function() {
2677
+ angular.module('mgcrea.ngStrap.timepicker', [ 'mgcrea.ngStrap.helpers.dateParser', 'mgcrea.ngStrap.helpers.dateFormatter', 'mgcrea.ngStrap.tooltip' ]).provider('$timepicker', function() {
2599
2678
  var defaults = this.defaults = {
2600
2679
  animation: 'am-fade',
2601
- customClass: '',
2680
+ prefixClass: 'timepicker',
2681
+ placement: 'bottom-left',
2682
+ templateUrl: 'timepicker/timepicker.tpl.html',
2683
+ trigger: 'focus',
2602
2684
  container: false,
2603
- target: false,
2604
- placement: 'right',
2605
- templateUrl: 'popover/popover.tpl.html',
2606
- contentTemplate: false,
2607
- trigger: 'click',
2608
2685
  keyboard: true,
2609
2686
  html: false,
2610
- title: '',
2611
- content: '',
2612
2687
  delay: 0,
2613
- autoClose: false
2688
+ useNative: true,
2689
+ timeType: 'date',
2690
+ timeFormat: 'shortTime',
2691
+ timezone: null,
2692
+ modelTimeFormat: null,
2693
+ autoclose: false,
2694
+ minTime: -Infinity,
2695
+ maxTime: +Infinity,
2696
+ length: 5,
2697
+ hourStep: 1,
2698
+ minuteStep: 5,
2699
+ secondStep: 5,
2700
+ roundDisplay: false,
2701
+ iconUp: 'glyphicon glyphicon-chevron-up',
2702
+ iconDown: 'glyphicon glyphicon-chevron-down',
2703
+ arrowBehavior: 'pager'
2614
2704
  };
2615
- this.$get = [ '$tooltip', function($tooltip) {
2616
- function PopoverFactory(element, config) {
2617
- var options = angular.extend({}, defaults, config);
2618
- var $popover = $tooltip(element, options);
2619
- if (options.content) {
2620
- $popover.$scope.content = options.content;
2621
- }
2622
- return $popover;
2705
+ this.$get = [ '$window', '$document', '$rootScope', '$sce', '$dateFormatter', '$tooltip', '$timeout', function($window, $document, $rootScope, $sce, $dateFormatter, $tooltip, $timeout) {
2706
+ var isNative = /(ip(a|o)d|iphone|android)/gi.test($window.navigator.userAgent);
2707
+ var isTouch = 'createTouch' in $window.document && isNative;
2708
+ if (!defaults.lang) {
2709
+ defaults.lang = $dateFormatter.getDefaultLocale();
2623
2710
  }
2624
- return PopoverFactory;
2625
- } ];
2626
- }).directive('bsPopover', [ '$window', '$sce', '$popover', function($window, $sce, $popover) {
2627
- var requestAnimationFrame = $window.requestAnimationFrame || $window.setTimeout;
2628
- return {
2629
- restrict: 'EAC',
2630
- scope: true,
2631
- link: function postLink(scope, element, attr) {
2632
- var options = {
2633
- scope: scope
2711
+ function timepickerFactory(element, controller, config) {
2712
+ var $timepicker = $tooltip(element, angular.extend({}, defaults, config));
2713
+ var parentScope = config.scope;
2714
+ var options = $timepicker.$options;
2715
+ var scope = $timepicker.$scope;
2716
+ var lang = options.lang;
2717
+ var formatDate = function(date, format, timezone) {
2718
+ return $dateFormatter.formatDate(date, format, lang, timezone);
2634
2719
  };
2635
- angular.forEach([ 'template', 'templateUrl', 'controller', 'controllerAs', 'contentTemplate', 'placement', 'container', 'delay', 'trigger', 'html', 'animation', 'customClass', 'autoClose', 'id', 'prefixClass', 'prefixEvent' ], function(key) {
2636
- if (angular.isDefined(attr[key])) options[key] = attr[key];
2637
- });
2638
- var falseValueRegExp = /^(false|0|)$/i;
2639
- angular.forEach([ 'html', 'container', 'autoClose' ], function(key) {
2640
- if (angular.isDefined(attr[key]) && falseValueRegExp.test(attr[key])) options[key] = false;
2641
- });
2642
- var dataTarget = element.attr('data-target');
2643
- if (angular.isDefined(dataTarget)) {
2644
- if (falseValueRegExp.test(dataTarget)) options.target = false; else options.target = dataTarget;
2720
+ function floorMinutes(time) {
2721
+ var coeff = 1e3 * 60 * options.minuteStep;
2722
+ return new Date(Math.floor(time.getTime() / coeff) * coeff);
2645
2723
  }
2646
- angular.forEach([ 'title', 'content' ], function(key) {
2647
- attr[key] && attr.$observe(key, function(newValue, oldValue) {
2648
- scope[key] = $sce.trustAsHtml(newValue);
2649
- angular.isDefined(oldValue) && requestAnimationFrame(function() {
2650
- popover && popover.$applyPlacement();
2724
+ var selectedIndex = 0;
2725
+ var defaultDate = options.roundDisplay ? floorMinutes(new Date()) : new Date();
2726
+ var startDate = controller.$dateValue || defaultDate;
2727
+ var viewDate = {
2728
+ hour: startDate.getHours(),
2729
+ meridian: startDate.getHours() < 12,
2730
+ minute: startDate.getMinutes(),
2731
+ second: startDate.getSeconds(),
2732
+ millisecond: startDate.getMilliseconds()
2733
+ };
2734
+ var format = $dateFormatter.getDatetimeFormat(options.timeFormat, lang);
2735
+ var hoursFormat = $dateFormatter.hoursFormat(format), timeSeparator = $dateFormatter.timeSeparator(format), minutesFormat = $dateFormatter.minutesFormat(format), secondsFormat = $dateFormatter.secondsFormat(format), showSeconds = $dateFormatter.showSeconds(format), showAM = $dateFormatter.showAM(format);
2736
+ scope.$iconUp = options.iconUp;
2737
+ scope.$iconDown = options.iconDown;
2738
+ scope.$select = function(date, index) {
2739
+ $timepicker.select(date, index);
2740
+ };
2741
+ scope.$moveIndex = function(value, index) {
2742
+ $timepicker.$moveIndex(value, index);
2743
+ };
2744
+ scope.$switchMeridian = function(date) {
2745
+ $timepicker.switchMeridian(date);
2746
+ };
2747
+ $timepicker.update = function(date) {
2748
+ if (angular.isDate(date) && !isNaN(date.getTime())) {
2749
+ $timepicker.$date = date;
2750
+ angular.extend(viewDate, {
2751
+ hour: date.getHours(),
2752
+ minute: date.getMinutes(),
2753
+ second: date.getSeconds(),
2754
+ millisecond: date.getMilliseconds()
2651
2755
  });
2652
- });
2653
- });
2654
- attr.bsPopover && scope.$watch(attr.bsPopover, function(newValue, oldValue) {
2655
- if (angular.isObject(newValue)) {
2656
- angular.extend(scope, newValue);
2657
- } else {
2658
- scope.content = newValue;
2756
+ $timepicker.$build();
2757
+ } else if (!$timepicker.$isBuilt) {
2758
+ $timepicker.$build();
2659
2759
  }
2660
- angular.isDefined(oldValue) && requestAnimationFrame(function() {
2661
- popover && popover.$applyPlacement();
2662
- });
2663
- }, true);
2664
- attr.bsShow && scope.$watch(attr.bsShow, function(newValue, oldValue) {
2665
- if (!popover || !angular.isDefined(newValue)) return;
2666
- if (angular.isString(newValue)) newValue = !!newValue.match(/true|,?(popover),?/i);
2667
- newValue === true ? popover.show() : popover.hide();
2668
- });
2669
- attr.viewport && scope.$watch(attr.viewport, function(newValue) {
2670
- if (!popover || !angular.isDefined(newValue)) return;
2671
- popover.setViewport(newValue);
2672
- });
2673
- var popover = $popover(element, options);
2674
- scope.$on('$destroy', function() {
2675
- if (popover) popover.destroy();
2676
- options = null;
2677
- popover = null;
2678
- });
2679
- }
2680
- };
2681
- } ]);
2682
- angular.module('mgcrea.ngStrap.tab', []).provider('$tab', function() {
2683
- var defaults = this.defaults = {
2684
- animation: 'am-fade',
2685
- template: 'tab/tab.tpl.html',
2686
- navClass: 'nav-tabs',
2687
- activeClass: 'active'
2688
- };
2689
- var controller = this.controller = function($scope, $element, $attrs) {
2690
- var self = this;
2691
- self.$options = angular.copy(defaults);
2692
- angular.forEach([ 'animation', 'navClass', 'activeClass' ], function(key) {
2693
- if (angular.isDefined($attrs[key])) self.$options[key] = $attrs[key];
2694
- });
2695
- $scope.$navClass = self.$options.navClass;
2696
- $scope.$activeClass = self.$options.activeClass;
2697
- self.$panes = $scope.$panes = [];
2698
- self.$activePaneChangeListeners = self.$viewChangeListeners = [];
2699
- self.$push = function(pane) {
2700
- if (angular.isUndefined(self.$panes.$active)) {
2701
- $scope.$setActive(pane.name || 0);
2702
- }
2703
- self.$panes.push(pane);
2704
- };
2705
- self.$remove = function(pane) {
2706
- var index = self.$panes.indexOf(pane);
2707
- var active = self.$panes.$active;
2708
- var activeIndex;
2709
- if (angular.isString(active)) {
2710
- activeIndex = self.$panes.map(function(pane) {
2711
- return pane.name;
2712
- }).indexOf(active);
2713
- } else {
2714
- activeIndex = self.$panes.$active;
2715
- }
2716
- self.$panes.splice(index, 1);
2717
- if (index < activeIndex) {
2718
- activeIndex--;
2719
- } else if (index === activeIndex && activeIndex === self.$panes.length) {
2720
- activeIndex--;
2721
- }
2722
- if (activeIndex >= 0 && activeIndex < self.$panes.length) {
2723
- self.$setActive(self.$panes[activeIndex].name || activeIndex);
2724
- } else {
2725
- self.$setActive();
2726
- }
2727
- };
2728
- self.$setActive = $scope.$setActive = function(value) {
2729
- self.$panes.$active = value;
2730
- self.$activePaneChangeListeners.forEach(function(fn) {
2731
- fn();
2732
- });
2733
- };
2734
- self.$isActive = $scope.$isActive = function($pane, $index) {
2735
- return self.$panes.$active === $pane.name || self.$panes.$active === $index;
2736
- };
2737
- };
2738
- this.$get = function() {
2739
- var $tab = {};
2740
- $tab.defaults = defaults;
2741
- $tab.controller = controller;
2742
- return $tab;
2743
- };
2744
- }).directive('bsTabs', [ '$window', '$animate', '$tab', '$parse', function($window, $animate, $tab, $parse) {
2745
- var defaults = $tab.defaults;
2746
- return {
2747
- require: [ '?ngModel', 'bsTabs' ],
2748
- transclude: true,
2749
- scope: true,
2750
- controller: [ '$scope', '$element', '$attrs', $tab.controller ],
2751
- templateUrl: function(element, attr) {
2752
- return attr.template || defaults.template;
2753
- },
2754
- link: function postLink(scope, element, attrs, controllers) {
2755
- var ngModelCtrl = controllers[0];
2756
- var bsTabsCtrl = controllers[1];
2757
- if (ngModelCtrl) {
2758
- bsTabsCtrl.$activePaneChangeListeners.push(function() {
2759
- ngModelCtrl.$setViewValue(bsTabsCtrl.$panes.$active);
2760
- });
2761
- ngModelCtrl.$formatters.push(function(modelValue) {
2762
- bsTabsCtrl.$setActive(modelValue);
2763
- return modelValue;
2764
- });
2765
- }
2766
- if (attrs.bsActivePane) {
2767
- var parsedBsActivePane = $parse(attrs.bsActivePane);
2768
- bsTabsCtrl.$activePaneChangeListeners.push(function() {
2769
- parsedBsActivePane.assign(scope, bsTabsCtrl.$panes.$active);
2770
- });
2771
- scope.$watch(attrs.bsActivePane, function(newValue, oldValue) {
2772
- bsTabsCtrl.$setActive(newValue);
2773
- }, true);
2774
- }
2775
- }
2776
- };
2777
- } ]).directive('bsPane', [ '$window', '$animate', '$sce', function($window, $animate, $sce) {
2778
- return {
2779
- require: [ '^?ngModel', '^bsTabs' ],
2780
- scope: true,
2781
- link: function postLink(scope, element, attrs, controllers) {
2782
- var ngModelCtrl = controllers[0];
2783
- var bsTabsCtrl = controllers[1];
2784
- element.addClass('tab-pane');
2785
- attrs.$observe('title', function(newValue, oldValue) {
2786
- scope.title = $sce.trustAsHtml(newValue);
2787
- });
2788
- scope.name = attrs.name;
2789
- if (bsTabsCtrl.$options.animation) {
2790
- element.addClass(bsTabsCtrl.$options.animation);
2791
- }
2792
- attrs.$observe('disabled', function(newValue, oldValue) {
2793
- scope.disabled = scope.$eval(newValue);
2794
- });
2795
- bsTabsCtrl.$push(scope);
2796
- scope.$on('$destroy', function() {
2797
- bsTabsCtrl.$remove(scope);
2798
- });
2799
- function render() {
2800
- var index = bsTabsCtrl.$panes.indexOf(scope);
2801
- $animate[bsTabsCtrl.$isActive(scope, index) ? 'addClass' : 'removeClass'](element, bsTabsCtrl.$options.activeClass);
2802
- }
2803
- bsTabsCtrl.$activePaneChangeListeners.push(function() {
2804
- render();
2805
- });
2806
- render();
2807
- }
2808
- };
2809
- } ]);
2810
- angular.module('mgcrea.ngStrap.tooltip', [ 'mgcrea.ngStrap.core', 'mgcrea.ngStrap.helpers.dimensions' ]).provider('$tooltip', function() {
2811
- var defaults = this.defaults = {
2812
- animation: 'am-fade',
2813
- customClass: '',
2814
- prefixClass: 'tooltip',
2815
- prefixEvent: 'tooltip',
2816
- container: false,
2817
- target: false,
2818
- placement: 'top',
2819
- templateUrl: 'tooltip/tooltip.tpl.html',
2820
- template: '',
2821
- contentTemplate: false,
2822
- trigger: 'hover focus',
2823
- keyboard: false,
2824
- html: false,
2825
- show: false,
2826
- title: '',
2827
- type: '',
2828
- delay: 0,
2829
- autoClose: false,
2830
- bsEnabled: true,
2831
- viewport: {
2832
- selector: 'body',
2833
- padding: 0
2834
- }
2835
- };
2836
- this.$get = [ '$window', '$rootScope', '$bsCompiler', '$q', '$templateCache', '$http', '$animate', '$sce', 'dimensions', '$$rAF', '$timeout', function($window, $rootScope, $bsCompiler, $q, $templateCache, $http, $animate, $sce, dimensions, $$rAF, $timeout) {
2837
- var trim = String.prototype.trim;
2838
- var isTouch = 'createTouch' in $window.document;
2839
- var htmlReplaceRegExp = /ng-bind="/gi;
2840
- var $body = angular.element($window.document);
2841
- function TooltipFactory(element, config) {
2842
- var $tooltip = {};
2843
- var options = $tooltip.$options = angular.extend({}, defaults, config);
2844
- var promise = $tooltip.$promise = $bsCompiler.compile(options);
2845
- var scope = $tooltip.$scope = options.scope && options.scope.$new() || $rootScope.$new();
2846
- var nodeName = element[0].nodeName.toLowerCase();
2847
- if (options.delay && angular.isString(options.delay)) {
2848
- var split = options.delay.split(',').map(parseFloat);
2849
- options.delay = split.length > 1 ? {
2850
- show: split[0],
2851
- hide: split[1]
2852
- } : split[0];
2853
- }
2854
- $tooltip.$id = options.id || element.attr('id') || '';
2855
- if (options.title) {
2856
- scope.title = $sce.trustAsHtml(options.title);
2857
- }
2858
- scope.$setEnabled = function(isEnabled) {
2859
- scope.$$postDigest(function() {
2860
- $tooltip.setEnabled(isEnabled);
2861
- });
2862
2760
  };
2863
- scope.$hide = function() {
2864
- scope.$$postDigest(function() {
2865
- $tooltip.hide();
2866
- });
2867
- };
2868
- scope.$show = function() {
2869
- scope.$$postDigest(function() {
2870
- $tooltip.show();
2871
- });
2872
- };
2873
- scope.$toggle = function() {
2874
- scope.$$postDigest(function() {
2875
- $tooltip.toggle();
2876
- });
2761
+ $timepicker.select = function(date, index, keep) {
2762
+ if (!controller.$dateValue || isNaN(controller.$dateValue.getTime())) controller.$dateValue = new Date(1970, 0, 1);
2763
+ if (!angular.isDate(date)) date = new Date(date);
2764
+ if (index === 0) controller.$dateValue.setHours(date.getHours()); else if (index === 1) controller.$dateValue.setMinutes(date.getMinutes()); else if (index === 2) controller.$dateValue.setSeconds(date.getSeconds());
2765
+ controller.$setViewValue(angular.copy(controller.$dateValue));
2766
+ controller.$render();
2767
+ if (options.autoclose && !keep) {
2768
+ $timeout(function() {
2769
+ $timepicker.hide(true);
2770
+ });
2771
+ }
2877
2772
  };
2878
- $tooltip.$isShown = scope.$isShown = false;
2879
- var timeout, hoverState;
2880
- var compileData, tipElement, tipContainer, tipScope;
2881
- promise.then(function(data) {
2882
- compileData = data;
2883
- $tooltip.init();
2884
- });
2885
- $tooltip.init = function() {
2886
- if (options.delay && angular.isNumber(options.delay)) {
2887
- options.delay = {
2888
- show: options.delay,
2889
- hide: options.delay
2890
- };
2773
+ $timepicker.switchMeridian = function(date) {
2774
+ if (!controller.$dateValue || isNaN(controller.$dateValue.getTime())) {
2775
+ return;
2891
2776
  }
2892
- if (options.container === 'self') {
2893
- tipContainer = element;
2894
- } else if (angular.isElement(options.container)) {
2895
- tipContainer = options.container;
2896
- } else if (options.container) {
2897
- tipContainer = findElement(options.container);
2777
+ var hours = (date || controller.$dateValue).getHours();
2778
+ controller.$dateValue.setHours(hours < 12 ? hours + 12 : hours - 12);
2779
+ controller.$setViewValue(angular.copy(controller.$dateValue));
2780
+ controller.$render();
2781
+ };
2782
+ $timepicker.$build = function() {
2783
+ var i, midIndex = scope.midIndex = parseInt(options.length / 2, 10);
2784
+ var hours = [], hour;
2785
+ for (i = 0; i < options.length; i++) {
2786
+ hour = new Date(1970, 0, 1, viewDate.hour - (midIndex - i) * options.hourStep);
2787
+ hours.push({
2788
+ date: hour,
2789
+ label: formatDate(hour, hoursFormat),
2790
+ selected: $timepicker.$date && $timepicker.$isSelected(hour, 0),
2791
+ disabled: $timepicker.$isDisabled(hour, 0)
2792
+ });
2898
2793
  }
2899
- bindTriggerEvents();
2900
- if (options.target) {
2901
- options.target = angular.isElement(options.target) ? options.target : findElement(options.target);
2794
+ var minutes = [], minute;
2795
+ for (i = 0; i < options.length; i++) {
2796
+ minute = new Date(1970, 0, 1, 0, viewDate.minute - (midIndex - i) * options.minuteStep);
2797
+ minutes.push({
2798
+ date: minute,
2799
+ label: formatDate(minute, minutesFormat),
2800
+ selected: $timepicker.$date && $timepicker.$isSelected(minute, 1),
2801
+ disabled: $timepicker.$isDisabled(minute, 1)
2802
+ });
2902
2803
  }
2903
- if (options.show) {
2904
- scope.$$postDigest(function() {
2905
- options.trigger === 'focus' ? element[0].focus() : $tooltip.show();
2804
+ var seconds = [], second;
2805
+ for (i = 0; i < options.length; i++) {
2806
+ second = new Date(1970, 0, 1, 0, 0, viewDate.second - (midIndex - i) * options.secondStep);
2807
+ seconds.push({
2808
+ date: second,
2809
+ label: formatDate(second, secondsFormat),
2810
+ selected: $timepicker.$date && $timepicker.$isSelected(second, 2),
2811
+ disabled: $timepicker.$isDisabled(second, 2)
2906
2812
  });
2907
2813
  }
2814
+ var rows = [];
2815
+ for (i = 0; i < options.length; i++) {
2816
+ if (showSeconds) {
2817
+ rows.push([ hours[i], minutes[i], seconds[i] ]);
2818
+ } else {
2819
+ rows.push([ hours[i], minutes[i] ]);
2820
+ }
2821
+ }
2822
+ scope.rows = rows;
2823
+ scope.showSeconds = showSeconds;
2824
+ scope.showAM = showAM;
2825
+ scope.isAM = ($timepicker.$date || hours[midIndex].date).getHours() < 12;
2826
+ scope.timeSeparator = timeSeparator;
2827
+ $timepicker.$isBuilt = true;
2908
2828
  };
2909
- $tooltip.destroy = function() {
2910
- unbindTriggerEvents();
2911
- destroyTipElement();
2912
- scope.$destroy();
2829
+ $timepicker.$isSelected = function(date, index) {
2830
+ if (!$timepicker.$date) return false; else if (index === 0) {
2831
+ return date.getHours() === $timepicker.$date.getHours();
2832
+ } else if (index === 1) {
2833
+ return date.getMinutes() === $timepicker.$date.getMinutes();
2834
+ } else if (index === 2) {
2835
+ return date.getSeconds() === $timepicker.$date.getSeconds();
2836
+ }
2913
2837
  };
2914
- $tooltip.enter = function() {
2915
- clearTimeout(timeout);
2916
- hoverState = 'in';
2917
- if (!options.delay || !options.delay.show) {
2918
- return $tooltip.show();
2838
+ $timepicker.$isDisabled = function(date, index) {
2839
+ var selectedTime;
2840
+ if (index === 0) {
2841
+ selectedTime = date.getTime() + viewDate.minute * 6e4 + viewDate.second * 1e3;
2842
+ } else if (index === 1) {
2843
+ selectedTime = date.getTime() + viewDate.hour * 36e5 + viewDate.second * 1e3;
2844
+ } else if (index === 2) {
2845
+ selectedTime = date.getTime() + viewDate.hour * 36e5 + viewDate.minute * 6e4;
2919
2846
  }
2920
- timeout = setTimeout(function() {
2921
- if (hoverState === 'in') $tooltip.show();
2922
- }, options.delay.show);
2847
+ return selectedTime < options.minTime * 1 || selectedTime > options.maxTime * 1;
2923
2848
  };
2924
- $tooltip.show = function() {
2925
- if (!options.bsEnabled || $tooltip.$isShown) return;
2926
- scope.$emit(options.prefixEvent + '.show.before', $tooltip);
2927
- var parent, after;
2928
- if (options.container) {
2929
- parent = tipContainer;
2930
- if (tipContainer[0].lastChild) {
2931
- after = angular.element(tipContainer[0].lastChild);
2932
- } else {
2933
- after = null;
2934
- }
2849
+ scope.$arrowAction = function(value, index) {
2850
+ if (options.arrowBehavior === 'picker') {
2851
+ $timepicker.$setTimeByStep(value, index);
2935
2852
  } else {
2936
- parent = null;
2937
- after = element;
2853
+ $timepicker.$moveIndex(value, index);
2938
2854
  }
2939
- if (tipElement) destroyTipElement();
2940
- tipScope = $tooltip.$scope.$new();
2941
- tipElement = $tooltip.$element = compileData.link(tipScope, function(clonedElement, scope) {});
2942
- tipElement.css({
2943
- top: '-9999px',
2944
- left: '-9999px',
2945
- right: 'auto',
2946
- display: 'block',
2947
- visibility: 'hidden'
2948
- });
2949
- if (options.animation) tipElement.addClass(options.animation);
2950
- if (options.type) tipElement.addClass(options.prefixClass + '-' + options.type);
2951
- if (options.customClass) tipElement.addClass(options.customClass);
2952
- after ? after.after(tipElement) : parent.prepend(tipElement);
2953
- $tooltip.$isShown = scope.$isShown = true;
2954
- safeDigest(scope);
2955
- $tooltip.$applyPlacement();
2956
- if (angular.version.minor <= 2) {
2957
- $animate.enter(tipElement, parent, after, enterAnimateCallback);
2958
- } else {
2959
- $animate.enter(tipElement, parent, after).then(enterAnimateCallback);
2855
+ };
2856
+ $timepicker.$setTimeByStep = function(value, index) {
2857
+ var newDate = new Date($timepicker.$date || startDate);
2858
+ var hours = newDate.getHours();
2859
+ var minutes = newDate.getMinutes();
2860
+ var seconds = newDate.getSeconds();
2861
+ if (index === 0) {
2862
+ newDate.setHours(hours - parseInt(options.hourStep, 10) * value);
2863
+ } else if (index === 1) {
2864
+ newDate.setMinutes(minutes - parseInt(options.minuteStep, 10) * value);
2865
+ } else if (index === 2) {
2866
+ newDate.setSeconds(seconds - parseInt(options.secondStep, 10) * value);
2960
2867
  }
2961
- safeDigest(scope);
2962
- $$rAF(function() {
2963
- if (tipElement) tipElement.css({
2964
- visibility: 'visible'
2868
+ $timepicker.select(newDate, index, true);
2869
+ };
2870
+ $timepicker.$moveIndex = function(value, index) {
2871
+ var targetDate;
2872
+ if (index === 0) {
2873
+ targetDate = new Date(1970, 0, 1, viewDate.hour + value * options.length, viewDate.minute, viewDate.second);
2874
+ angular.extend(viewDate, {
2875
+ hour: targetDate.getHours()
2965
2876
  });
2966
- });
2967
- if (options.keyboard) {
2968
- if (options.trigger !== 'focus') {
2969
- $tooltip.focus();
2877
+ } else if (index === 1) {
2878
+ targetDate = new Date(1970, 0, 1, viewDate.hour, viewDate.minute + value * options.length * options.minuteStep, viewDate.second);
2879
+ angular.extend(viewDate, {
2880
+ minute: targetDate.getMinutes()
2881
+ });
2882
+ } else if (index === 2) {
2883
+ targetDate = new Date(1970, 0, 1, viewDate.hour, viewDate.minute, viewDate.second + value * options.length * options.secondStep);
2884
+ angular.extend(viewDate, {
2885
+ second: targetDate.getSeconds()
2886
+ });
2887
+ }
2888
+ $timepicker.$build();
2889
+ };
2890
+ $timepicker.$onMouseDown = function(evt) {
2891
+ if (evt.target.nodeName.toLowerCase() !== 'input') evt.preventDefault();
2892
+ evt.stopPropagation();
2893
+ if (isTouch) {
2894
+ var targetEl = angular.element(evt.target);
2895
+ if (targetEl[0].nodeName.toLowerCase() !== 'button') {
2896
+ targetEl = targetEl.parent();
2970
2897
  }
2971
- bindKeyboardEvents();
2898
+ targetEl.triggerHandler('click');
2972
2899
  }
2973
- if (options.autoClose) {
2974
- bindAutoCloseEvents();
2900
+ };
2901
+ $timepicker.$onKeyDown = function(evt) {
2902
+ if (!/(38|37|39|40|13)/.test(evt.keyCode) || evt.shiftKey || evt.altKey) return;
2903
+ evt.preventDefault();
2904
+ evt.stopPropagation();
2905
+ if (evt.keyCode === 13) {
2906
+ $timepicker.hide(true);
2907
+ return;
2908
+ }
2909
+ var newDate = new Date($timepicker.$date);
2910
+ var hours = newDate.getHours(), hoursLength = formatDate(newDate, hoursFormat).length;
2911
+ var minutes = newDate.getMinutes(), minutesLength = formatDate(newDate, minutesFormat).length;
2912
+ var seconds = newDate.getSeconds(), secondsLength = formatDate(newDate, secondsFormat).length;
2913
+ var sepLength = 1;
2914
+ var lateralMove = /(37|39)/.test(evt.keyCode);
2915
+ var count = 2 + showSeconds * 1 + showAM * 1;
2916
+ if (lateralMove) {
2917
+ if (evt.keyCode === 37) selectedIndex = selectedIndex < 1 ? count - 1 : selectedIndex - 1; else if (evt.keyCode === 39) selectedIndex = selectedIndex < count - 1 ? selectedIndex + 1 : 0;
2918
+ }
2919
+ var selectRange = [ 0, hoursLength ];
2920
+ var incr = 0;
2921
+ if (evt.keyCode === 38) incr = -1;
2922
+ if (evt.keyCode === 40) incr = +1;
2923
+ var isSeconds = selectedIndex === 2 && showSeconds;
2924
+ var isMeridian = selectedIndex === 2 && !showSeconds || selectedIndex === 3 && showSeconds;
2925
+ if (selectedIndex === 0) {
2926
+ newDate.setHours(hours + incr * parseInt(options.hourStep, 10));
2927
+ hoursLength = formatDate(newDate, hoursFormat).length;
2928
+ selectRange = [ 0, hoursLength ];
2929
+ } else if (selectedIndex === 1) {
2930
+ newDate.setMinutes(minutes + incr * parseInt(options.minuteStep, 10));
2931
+ minutesLength = formatDate(newDate, minutesFormat).length;
2932
+ selectRange = [ hoursLength + sepLength, minutesLength ];
2933
+ } else if (isSeconds) {
2934
+ newDate.setSeconds(seconds + incr * parseInt(options.secondStep, 10));
2935
+ secondsLength = formatDate(newDate, secondsFormat).length;
2936
+ selectRange = [ hoursLength + sepLength + minutesLength + sepLength, secondsLength ];
2937
+ } else if (isMeridian) {
2938
+ if (!lateralMove) $timepicker.switchMeridian();
2939
+ selectRange = [ hoursLength + sepLength + minutesLength + sepLength + (secondsLength + sepLength) * showSeconds, 2 ];
2940
+ }
2941
+ $timepicker.select(newDate, selectedIndex, true);
2942
+ createSelection(selectRange[0], selectRange[1]);
2943
+ parentScope.$digest();
2944
+ };
2945
+ function createSelection(start, length) {
2946
+ var end = start + length;
2947
+ if (element[0].createTextRange) {
2948
+ var selRange = element[0].createTextRange();
2949
+ selRange.collapse(true);
2950
+ selRange.moveStart('character', start);
2951
+ selRange.moveEnd('character', end);
2952
+ selRange.select();
2953
+ } else if (element[0].setSelectionRange) {
2954
+ element[0].setSelectionRange(start, end);
2955
+ } else if (angular.isUndefined(element[0].selectionStart)) {
2956
+ element[0].selectionStart = start;
2957
+ element[0].selectionEnd = end;
2975
2958
  }
2976
- };
2977
- function enterAnimateCallback() {
2978
- scope.$emit(options.prefixEvent + '.show', $tooltip);
2979
2959
  }
2980
- $tooltip.leave = function() {
2981
- clearTimeout(timeout);
2982
- hoverState = 'out';
2983
- if (!options.delay || !options.delay.hide) {
2984
- return $tooltip.hide();
2960
+ function focusElement() {
2961
+ element[0].focus();
2962
+ }
2963
+ var _init = $timepicker.init;
2964
+ $timepicker.init = function() {
2965
+ if (isNative && options.useNative) {
2966
+ element.prop('type', 'time');
2967
+ element.css('-webkit-appearance', 'textfield');
2968
+ return;
2969
+ } else if (isTouch) {
2970
+ element.prop('type', 'text');
2971
+ element.attr('readonly', 'true');
2972
+ element.on('click', focusElement);
2985
2973
  }
2986
- timeout = setTimeout(function() {
2987
- if (hoverState === 'out') {
2988
- $tooltip.hide();
2989
- }
2990
- }, options.delay.hide);
2974
+ _init();
2991
2975
  };
2992
- var _blur;
2993
- var _tipToHide;
2994
- $tooltip.hide = function(blur) {
2995
- if (!$tooltip.$isShown) return;
2996
- scope.$emit(options.prefixEvent + '.hide.before', $tooltip);
2997
- _blur = blur;
2998
- _tipToHide = tipElement;
2999
- if (angular.version.minor <= 2) {
3000
- $animate.leave(tipElement, leaveAnimateCallback);
3001
- } else {
3002
- $animate.leave(tipElement).then(leaveAnimateCallback);
3003
- }
3004
- $tooltip.$isShown = scope.$isShown = false;
3005
- safeDigest(scope);
3006
- if (options.keyboard && tipElement !== null) {
3007
- unbindKeyboardEvents();
3008
- }
3009
- if (options.autoClose && tipElement !== null) {
3010
- unbindAutoCloseEvents();
2976
+ var _destroy = $timepicker.destroy;
2977
+ $timepicker.destroy = function() {
2978
+ if (isNative && options.useNative) {
2979
+ element.off('click', focusElement);
3011
2980
  }
2981
+ _destroy();
3012
2982
  };
3013
- function leaveAnimateCallback() {
3014
- scope.$emit(options.prefixEvent + '.hide', $tooltip);
3015
- if (tipElement === _tipToHide) {
3016
- if (_blur && options.trigger === 'focus') {
3017
- return element[0].blur();
2983
+ var _show = $timepicker.show;
2984
+ $timepicker.show = function() {
2985
+ if (!isTouch && element.attr('readonly') || element.attr('disabled')) return;
2986
+ _show();
2987
+ $timeout(function() {
2988
+ $timepicker.$element && $timepicker.$element.on(isTouch ? 'touchstart' : 'mousedown', $timepicker.$onMouseDown);
2989
+ if (options.keyboard) {
2990
+ element && element.on('keydown', $timepicker.$onKeyDown);
3018
2991
  }
3019
- destroyTipElement();
3020
- }
3021
- }
3022
- $tooltip.toggle = function() {
3023
- $tooltip.$isShown ? $tooltip.leave() : $tooltip.enter();
2992
+ }, 0, false);
3024
2993
  };
3025
- $tooltip.focus = function() {
3026
- tipElement[0].focus();
2994
+ var _hide = $timepicker.hide;
2995
+ $timepicker.hide = function(blur) {
2996
+ if (!$timepicker.$isShown) return;
2997
+ $timepicker.$element && $timepicker.$element.off(isTouch ? 'touchstart' : 'mousedown', $timepicker.$onMouseDown);
2998
+ if (options.keyboard) {
2999
+ element && element.off('keydown', $timepicker.$onKeyDown);
3000
+ }
3001
+ _hide(blur);
3027
3002
  };
3028
- $tooltip.setEnabled = function(isEnabled) {
3029
- options.bsEnabled = isEnabled;
3003
+ return $timepicker;
3004
+ }
3005
+ timepickerFactory.defaults = defaults;
3006
+ return timepickerFactory;
3007
+ } ];
3008
+ }).directive('bsTimepicker', [ '$window', '$parse', '$q', '$dateFormatter', '$dateParser', '$timepicker', function($window, $parse, $q, $dateFormatter, $dateParser, $timepicker) {
3009
+ var defaults = $timepicker.defaults;
3010
+ var isNative = /(ip(a|o)d|iphone|android)/gi.test($window.navigator.userAgent);
3011
+ return {
3012
+ restrict: 'EAC',
3013
+ require: 'ngModel',
3014
+ link: function postLink(scope, element, attr, controller) {
3015
+ var options = {
3016
+ scope: scope
3030
3017
  };
3031
- $tooltip.setViewport = function(viewport) {
3032
- options.viewport = viewport;
3018
+ angular.forEach([ 'template', 'templateUrl', 'controller', 'controllerAs', 'placement', 'container', 'delay', 'trigger', 'keyboard', 'html', 'animation', 'autoclose', 'timeType', 'timeFormat', 'timezone', 'modelTimeFormat', 'useNative', 'hourStep', 'minuteStep', 'secondStep', 'length', 'arrowBehavior', 'iconUp', 'iconDown', 'roundDisplay', 'id', 'prefixClass', 'prefixEvent' ], function(key) {
3019
+ if (angular.isDefined(attr[key])) options[key] = attr[key];
3020
+ });
3021
+ var falseValueRegExp = /^(false|0|)$/i;
3022
+ angular.forEach([ 'html', 'container', 'autoclose', 'useNative', 'roundDisplay' ], function(key) {
3023
+ if (angular.isDefined(attr[key]) && falseValueRegExp.test(attr[key])) options[key] = false;
3024
+ });
3025
+ attr.bsShow && scope.$watch(attr.bsShow, function(newValue, oldValue) {
3026
+ if (!timepicker || !angular.isDefined(newValue)) return;
3027
+ if (angular.isString(newValue)) newValue = !!newValue.match(/true|,?(timepicker),?/i);
3028
+ newValue === true ? timepicker.show() : timepicker.hide();
3029
+ });
3030
+ if (isNative && (options.useNative || defaults.useNative)) options.timeFormat = 'HH:mm';
3031
+ var timepicker = $timepicker(element, controller, options);
3032
+ options = timepicker.$options;
3033
+ var lang = options.lang;
3034
+ var formatDate = function(date, format, timezone) {
3035
+ return $dateFormatter.formatDate(date, format, lang, timezone);
3033
3036
  };
3034
- $tooltip.$applyPlacement = function() {
3035
- if (!tipElement) return;
3036
- var placement = options.placement, autoToken = /\s?auto?\s?/i, autoPlace = autoToken.test(placement);
3037
- if (autoPlace) {
3038
- placement = placement.replace(autoToken, '') || defaults.placement;
3037
+ var dateParser = $dateParser({
3038
+ format: options.timeFormat,
3039
+ lang: lang
3040
+ });
3041
+ angular.forEach([ 'minTime', 'maxTime' ], function(key) {
3042
+ angular.isDefined(attr[key]) && attr.$observe(key, function(newValue) {
3043
+ timepicker.$options[key] = dateParser.getTimeForAttribute(key, newValue);
3044
+ !isNaN(timepicker.$options[key]) && timepicker.$build();
3045
+ validateAgainstMinMaxTime(controller.$dateValue);
3046
+ });
3047
+ });
3048
+ scope.$watch(attr.ngModel, function(newValue, oldValue) {
3049
+ timepicker.update(controller.$dateValue);
3050
+ }, true);
3051
+ function validateAgainstMinMaxTime(parsedTime) {
3052
+ if (!angular.isDate(parsedTime)) return;
3053
+ var isMinValid = isNaN(options.minTime) || new Date(parsedTime.getTime()).setFullYear(1970, 0, 1) >= options.minTime;
3054
+ var isMaxValid = isNaN(options.maxTime) || new Date(parsedTime.getTime()).setFullYear(1970, 0, 1) <= options.maxTime;
3055
+ var isValid = isMinValid && isMaxValid;
3056
+ controller.$setValidity('date', isValid);
3057
+ controller.$setValidity('min', isMinValid);
3058
+ controller.$setValidity('max', isMaxValid);
3059
+ if (!isValid) {
3060
+ return;
3039
3061
  }
3040
- tipElement.addClass(options.placement);
3041
- var elementPosition = getPosition(), tipWidth = tipElement.prop('offsetWidth'), tipHeight = tipElement.prop('offsetHeight');
3042
- if (autoPlace) {
3043
- var originalPlacement = placement;
3044
- var container = options.container ? findElement(options.container) : element.parent();
3045
- var containerPosition = getPosition(container);
3046
- if (originalPlacement.indexOf('bottom') >= 0 && elementPosition.bottom + tipHeight > containerPosition.bottom) {
3047
- placement = originalPlacement.replace('bottom', 'top');
3048
- } else if (originalPlacement.indexOf('top') >= 0 && elementPosition.top - tipHeight < containerPosition.top) {
3049
- placement = originalPlacement.replace('top', 'bottom');
3050
- }
3051
- if ((originalPlacement === 'right' || originalPlacement === 'bottom-left' || originalPlacement === 'top-left') && elementPosition.right + tipWidth > containerPosition.width) {
3052
- placement = originalPlacement === 'right' ? 'left' : placement.replace('left', 'right');
3053
- } else if ((originalPlacement === 'left' || originalPlacement === 'bottom-right' || originalPlacement === 'top-right') && elementPosition.left - tipWidth < containerPosition.left) {
3054
- placement = originalPlacement === 'left' ? 'right' : placement.replace('right', 'left');
3055
- }
3056
- tipElement.removeClass(originalPlacement).addClass(placement);
3062
+ controller.$dateValue = parsedTime;
3063
+ }
3064
+ controller.$parsers.unshift(function(viewValue) {
3065
+ var date;
3066
+ if (!viewValue) {
3067
+ controller.$setValidity('date', true);
3068
+ return null;
3057
3069
  }
3058
- var tipPosition = getCalculatedOffset(placement, elementPosition, tipWidth, tipHeight);
3059
- applyPlacement(tipPosition, placement);
3060
- };
3061
- $tooltip.$onKeyUp = function(evt) {
3062
- if (evt.which === 27 && $tooltip.$isShown) {
3063
- $tooltip.hide();
3064
- evt.stopPropagation();
3070
+ var parsedTime = angular.isDate(viewValue) ? viewValue : dateParser.parse(viewValue, controller.$dateValue);
3071
+ if (!parsedTime || isNaN(parsedTime.getTime())) {
3072
+ controller.$setValidity('date', false);
3073
+ return undefined;
3074
+ } else {
3075
+ validateAgainstMinMaxTime(parsedTime);
3065
3076
  }
3066
- };
3067
- $tooltip.$onFocusKeyUp = function(evt) {
3068
- if (evt.which === 27) {
3069
- element[0].blur();
3070
- evt.stopPropagation();
3077
+ if (options.timeType === 'string') {
3078
+ date = dateParser.timezoneOffsetAdjust(parsedTime, options.timezone, true);
3079
+ return formatDate(date, options.modelTimeFormat || options.timeFormat);
3071
3080
  }
3081
+ date = dateParser.timezoneOffsetAdjust(controller.$dateValue, options.timezone, true);
3082
+ if (options.timeType === 'number') {
3083
+ return date.getTime();
3084
+ } else if (options.timeType === 'unix') {
3085
+ return date.getTime() / 1e3;
3086
+ } else if (options.timeType === 'iso') {
3087
+ return date.toISOString();
3088
+ } else {
3089
+ return new Date(date);
3090
+ }
3091
+ });
3092
+ controller.$formatters.push(function(modelValue) {
3093
+ var date;
3094
+ if (angular.isUndefined(modelValue) || modelValue === null) {
3095
+ date = NaN;
3096
+ } else if (angular.isDate(modelValue)) {
3097
+ date = modelValue;
3098
+ } else if (options.timeType === 'string') {
3099
+ date = dateParser.parse(modelValue, null, options.modelTimeFormat);
3100
+ } else if (options.timeType === 'unix') {
3101
+ date = new Date(modelValue * 1e3);
3102
+ } else {
3103
+ date = new Date(modelValue);
3104
+ }
3105
+ controller.$dateValue = dateParser.timezoneOffsetAdjust(date, options.timezone);
3106
+ return getTimeFormattedString();
3107
+ });
3108
+ controller.$render = function() {
3109
+ element.val(getTimeFormattedString());
3072
3110
  };
3073
- $tooltip.$onFocusElementMouseDown = function(evt) {
3074
- evt.preventDefault();
3075
- evt.stopPropagation();
3076
- $tooltip.$isShown ? element[0].blur() : element[0].focus();
3077
- };
3078
- function bindTriggerEvents() {
3079
- var triggers = options.trigger.split(' ');
3080
- angular.forEach(triggers, function(trigger) {
3081
- if (trigger === 'click') {
3082
- element.on('click', $tooltip.toggle);
3083
- } else if (trigger !== 'manual') {
3084
- element.on(trigger === 'hover' ? 'mouseenter' : 'focus', $tooltip.enter);
3085
- element.on(trigger === 'hover' ? 'mouseleave' : 'blur', $tooltip.leave);
3086
- nodeName === 'button' && trigger !== 'hover' && element.on(isTouch ? 'touchstart' : 'mousedown', $tooltip.$onFocusElementMouseDown);
3087
- }
3088
- });
3111
+ function getTimeFormattedString() {
3112
+ return !controller.$dateValue || isNaN(controller.$dateValue.getTime()) ? '' : formatDate(controller.$dateValue, options.timeFormat);
3089
3113
  }
3090
- function unbindTriggerEvents() {
3091
- var triggers = options.trigger.split(' ');
3092
- for (var i = triggers.length; i--; ) {
3093
- var trigger = triggers[i];
3094
- if (trigger === 'click') {
3095
- element.off('click', $tooltip.toggle);
3096
- } else if (trigger !== 'manual') {
3097
- element.off(trigger === 'hover' ? 'mouseenter' : 'focus', $tooltip.enter);
3098
- element.off(trigger === 'hover' ? 'mouseleave' : 'blur', $tooltip.leave);
3099
- nodeName === 'button' && trigger !== 'hover' && element.off(isTouch ? 'touchstart' : 'mousedown', $tooltip.$onFocusElementMouseDown);
3100
- }
3101
- }
3114
+ scope.$on('$destroy', function() {
3115
+ if (timepicker) timepicker.destroy();
3116
+ options = null;
3117
+ timepicker = null;
3118
+ });
3119
+ }
3120
+ };
3121
+ } ]);
3122
+ angular.module('mgcrea.ngStrap.tab', []).provider('$tab', function() {
3123
+ var defaults = this.defaults = {
3124
+ animation: 'am-fade',
3125
+ template: 'tab/tab.tpl.html',
3126
+ navClass: 'nav-tabs',
3127
+ activeClass: 'active'
3128
+ };
3129
+ var controller = this.controller = function($scope, $element, $attrs) {
3130
+ var self = this;
3131
+ self.$options = angular.copy(defaults);
3132
+ angular.forEach([ 'animation', 'navClass', 'activeClass' ], function(key) {
3133
+ if (angular.isDefined($attrs[key])) self.$options[key] = $attrs[key];
3134
+ });
3135
+ $scope.$navClass = self.$options.navClass;
3136
+ $scope.$activeClass = self.$options.activeClass;
3137
+ self.$panes = $scope.$panes = [];
3138
+ self.$activePaneChangeListeners = self.$viewChangeListeners = [];
3139
+ self.$push = function(pane) {
3140
+ if (angular.isUndefined(self.$panes.$active)) {
3141
+ $scope.$setActive(pane.name || 0);
3102
3142
  }
3103
- function bindKeyboardEvents() {
3104
- if (options.trigger !== 'focus') {
3105
- tipElement.on('keyup', $tooltip.$onKeyUp);
3106
- } else {
3107
- element.on('keyup', $tooltip.$onFocusKeyUp);
3108
- }
3143
+ self.$panes.push(pane);
3144
+ };
3145
+ self.$remove = function(pane) {
3146
+ var index = self.$panes.indexOf(pane);
3147
+ var active = self.$panes.$active;
3148
+ var activeIndex;
3149
+ if (angular.isString(active)) {
3150
+ activeIndex = self.$panes.map(function(pane) {
3151
+ return pane.name;
3152
+ }).indexOf(active);
3153
+ } else {
3154
+ activeIndex = self.$panes.$active;
3109
3155
  }
3110
- function unbindKeyboardEvents() {
3111
- if (options.trigger !== 'focus') {
3112
- tipElement.off('keyup', $tooltip.$onKeyUp);
3113
- } else {
3114
- element.off('keyup', $tooltip.$onFocusKeyUp);
3115
- }
3156
+ self.$panes.splice(index, 1);
3157
+ if (index < activeIndex) {
3158
+ activeIndex--;
3159
+ } else if (index === activeIndex && activeIndex === self.$panes.length) {
3160
+ activeIndex--;
3116
3161
  }
3117
- var _autoCloseEventsBinded = false;
3118
- function bindAutoCloseEvents() {
3119
- $timeout(function() {
3120
- tipElement.on('click', stopEventPropagation);
3121
- $body.on('click', $tooltip.hide);
3122
- _autoCloseEventsBinded = true;
3123
- }, 0, false);
3162
+ if (activeIndex >= 0 && activeIndex < self.$panes.length) {
3163
+ self.$setActive(self.$panes[activeIndex].name || activeIndex);
3164
+ } else {
3165
+ self.$setActive();
3124
3166
  }
3125
- function unbindAutoCloseEvents() {
3126
- if (_autoCloseEventsBinded) {
3127
- tipElement.off('click', stopEventPropagation);
3128
- $body.off('click', $tooltip.hide);
3129
- _autoCloseEventsBinded = false;
3130
- }
3167
+ };
3168
+ self.$setActive = $scope.$setActive = function(value) {
3169
+ self.$panes.$active = value;
3170
+ self.$activePaneChangeListeners.forEach(function(fn) {
3171
+ fn();
3172
+ });
3173
+ };
3174
+ self.$isActive = $scope.$isActive = function($pane, $index) {
3175
+ return self.$panes.$active === $pane.name || self.$panes.$active === $index;
3176
+ };
3177
+ };
3178
+ this.$get = function() {
3179
+ var $tab = {};
3180
+ $tab.defaults = defaults;
3181
+ $tab.controller = controller;
3182
+ return $tab;
3183
+ };
3184
+ }).directive('bsTabs', [ '$window', '$animate', '$tab', '$parse', function($window, $animate, $tab, $parse) {
3185
+ var defaults = $tab.defaults;
3186
+ return {
3187
+ require: [ '?ngModel', 'bsTabs' ],
3188
+ transclude: true,
3189
+ scope: true,
3190
+ controller: [ '$scope', '$element', '$attrs', $tab.controller ],
3191
+ templateUrl: function(element, attr) {
3192
+ return attr.template || defaults.template;
3193
+ },
3194
+ link: function postLink(scope, element, attrs, controllers) {
3195
+ var ngModelCtrl = controllers[0];
3196
+ var bsTabsCtrl = controllers[1];
3197
+ if (ngModelCtrl) {
3198
+ bsTabsCtrl.$activePaneChangeListeners.push(function() {
3199
+ ngModelCtrl.$setViewValue(bsTabsCtrl.$panes.$active);
3200
+ });
3201
+ ngModelCtrl.$formatters.push(function(modelValue) {
3202
+ bsTabsCtrl.$setActive(modelValue);
3203
+ return modelValue;
3204
+ });
3131
3205
  }
3132
- function stopEventPropagation(event) {
3133
- event.stopPropagation();
3206
+ if (attrs.bsActivePane) {
3207
+ var parsedBsActivePane = $parse(attrs.bsActivePane);
3208
+ bsTabsCtrl.$activePaneChangeListeners.push(function() {
3209
+ parsedBsActivePane.assign(scope, bsTabsCtrl.$panes.$active);
3210
+ });
3211
+ scope.$watch(attrs.bsActivePane, function(newValue, oldValue) {
3212
+ bsTabsCtrl.$setActive(newValue);
3213
+ }, true);
3134
3214
  }
3135
- function getPosition($element) {
3136
- $element = $element || (options.target || element);
3137
- var el = $element[0], isBody = el.tagName === 'BODY';
3138
- var elRect = el.getBoundingClientRect();
3139
- var rect = {};
3140
- for (var p in elRect) {
3141
- rect[p] = elRect[p];
3142
- }
3143
- if (rect.width === null) {
3144
- rect = angular.extend({}, rect, {
3145
- width: elRect.right - elRect.left,
3146
- height: elRect.bottom - elRect.top
3147
- });
3148
- }
3149
- var elOffset = isBody ? {
3150
- top: 0,
3151
- left: 0
3152
- } : dimensions.offset(el), scroll = {
3153
- scroll: isBody ? document.documentElement.scrollTop || document.body.scrollTop : $element.prop('scrollTop') || 0
3154
- }, outerDims = isBody ? {
3155
- width: document.documentElement.clientWidth,
3156
- height: $window.innerHeight
3157
- } : null;
3158
- return angular.extend({}, rect, scroll, outerDims, elOffset);
3215
+ }
3216
+ };
3217
+ } ]).directive('bsPane', [ '$window', '$animate', '$sce', function($window, $animate, $sce) {
3218
+ return {
3219
+ require: [ '^?ngModel', '^bsTabs' ],
3220
+ scope: true,
3221
+ link: function postLink(scope, element, attrs, controllers) {
3222
+ var ngModelCtrl = controllers[0];
3223
+ var bsTabsCtrl = controllers[1];
3224
+ element.addClass('tab-pane');
3225
+ attrs.$observe('title', function(newValue, oldValue) {
3226
+ scope.title = $sce.trustAsHtml(newValue);
3227
+ });
3228
+ scope.name = attrs.name;
3229
+ if (bsTabsCtrl.$options.animation) {
3230
+ element.addClass(bsTabsCtrl.$options.animation);
3159
3231
  }
3160
- function getCalculatedOffset(placement, position, actualWidth, actualHeight) {
3161
- var offset;
3162
- var split = placement.split('-');
3163
- switch (split[0]) {
3164
- case 'right':
3165
- offset = {
3166
- top: position.top + position.height / 2 - actualHeight / 2,
3167
- left: position.left + position.width
3168
- };
3169
- break;
3170
-
3171
- case 'bottom':
3172
- offset = {
3173
- top: position.top + position.height,
3174
- left: position.left + position.width / 2 - actualWidth / 2
3175
- };
3176
- break;
3177
-
3178
- case 'left':
3179
- offset = {
3180
- top: position.top + position.height / 2 - actualHeight / 2,
3181
- left: position.left - actualWidth
3182
- };
3183
- break;
3184
-
3185
- default:
3186
- offset = {
3187
- top: position.top - actualHeight,
3188
- left: position.left + position.width / 2 - actualWidth / 2
3189
- };
3190
- break;
3191
- }
3192
- if (!split[1]) {
3193
- return offset;
3194
- }
3195
- if (split[0] === 'top' || split[0] === 'bottom') {
3196
- switch (split[1]) {
3197
- case 'left':
3198
- offset.left = position.left;
3199
- break;
3200
-
3201
- case 'right':
3202
- offset.left = position.left + position.width - actualWidth;
3203
- }
3204
- } else if (split[0] === 'left' || split[0] === 'right') {
3205
- switch (split[1]) {
3206
- case 'top':
3207
- offset.top = position.top - actualHeight;
3208
- break;
3209
-
3210
- case 'bottom':
3211
- offset.top = position.top + position.height;
3212
- }
3213
- }
3214
- return offset;
3232
+ attrs.$observe('disabled', function(newValue, oldValue) {
3233
+ scope.disabled = scope.$eval(newValue);
3234
+ });
3235
+ bsTabsCtrl.$push(scope);
3236
+ scope.$on('$destroy', function() {
3237
+ bsTabsCtrl.$remove(scope);
3238
+ });
3239
+ function render() {
3240
+ var index = bsTabsCtrl.$panes.indexOf(scope);
3241
+ $animate[bsTabsCtrl.$isActive(scope, index) ? 'addClass' : 'removeClass'](element, bsTabsCtrl.$options.activeClass);
3215
3242
  }
3216
- function applyPlacement(offset, placement) {
3217
- var tip = tipElement[0], width = tip.offsetWidth, height = tip.offsetHeight;
3218
- var marginTop = parseInt(dimensions.css(tip, 'margin-top'), 10), marginLeft = parseInt(dimensions.css(tip, 'margin-left'), 10);
3219
- if (isNaN(marginTop)) marginTop = 0;
3220
- if (isNaN(marginLeft)) marginLeft = 0;
3221
- offset.top = offset.top + marginTop;
3222
- offset.left = offset.left + marginLeft;
3223
- dimensions.setOffset(tip, angular.extend({
3224
- using: function(props) {
3225
- tipElement.css({
3226
- top: Math.round(props.top) + 'px',
3227
- left: Math.round(props.left) + 'px',
3228
- right: ''
3229
- });
3230
- }
3231
- }, offset), 0);
3232
- var actualWidth = tip.offsetWidth, actualHeight = tip.offsetHeight;
3233
- if (placement === 'top' && actualHeight !== height) {
3234
- offset.top = offset.top + height - actualHeight;
3235
- }
3236
- if (/top-left|top-right|bottom-left|bottom-right/.test(placement)) return;
3237
- var delta = getViewportAdjustedDelta(placement, offset, actualWidth, actualHeight);
3238
- if (delta.left) {
3239
- offset.left += delta.left;
3240
- } else {
3241
- offset.top += delta.top;
3243
+ bsTabsCtrl.$activePaneChangeListeners.push(function() {
3244
+ render();
3245
+ });
3246
+ render();
3247
+ }
3248
+ };
3249
+ } ]);
3250
+ angular.module('mgcrea.ngStrap.typeahead', [ 'mgcrea.ngStrap.tooltip', 'mgcrea.ngStrap.helpers.parseOptions' ]).provider('$typeahead', function() {
3251
+ var defaults = this.defaults = {
3252
+ animation: 'am-fade',
3253
+ prefixClass: 'typeahead',
3254
+ prefixEvent: '$typeahead',
3255
+ placement: 'bottom-left',
3256
+ templateUrl: 'typeahead/typeahead.tpl.html',
3257
+ trigger: 'focus',
3258
+ container: false,
3259
+ keyboard: true,
3260
+ html: false,
3261
+ delay: 0,
3262
+ minLength: 1,
3263
+ filter: 'bsAsyncFilter',
3264
+ limit: 6,
3265
+ autoSelect: false,
3266
+ comparator: '',
3267
+ trimValue: true
3268
+ };
3269
+ this.$get = [ '$window', '$rootScope', '$tooltip', '$$rAF', '$timeout', function($window, $rootScope, $tooltip, $$rAF, $timeout) {
3270
+ var bodyEl = angular.element($window.document.body);
3271
+ function TypeaheadFactory(element, controller, config) {
3272
+ var $typeahead = {};
3273
+ var options = angular.extend({}, defaults, config);
3274
+ $typeahead = $tooltip(element, options);
3275
+ var parentScope = config.scope;
3276
+ var scope = $typeahead.$scope;
3277
+ scope.$resetMatches = function() {
3278
+ scope.$matches = [];
3279
+ scope.$activeIndex = options.autoSelect ? 0 : -1;
3280
+ };
3281
+ scope.$resetMatches();
3282
+ scope.$activate = function(index) {
3283
+ scope.$$postDigest(function() {
3284
+ $typeahead.activate(index);
3285
+ });
3286
+ };
3287
+ scope.$select = function(index, evt) {
3288
+ scope.$$postDigest(function() {
3289
+ $typeahead.select(index);
3290
+ });
3291
+ };
3292
+ scope.$isVisible = function() {
3293
+ return $typeahead.$isVisible();
3294
+ };
3295
+ $typeahead.update = function(matches) {
3296
+ scope.$matches = matches;
3297
+ if (scope.$activeIndex >= matches.length) {
3298
+ scope.$activeIndex = options.autoSelect ? 0 : -1;
3242
3299
  }
3243
- dimensions.setOffset(tip, offset);
3244
- if (/top|right|bottom|left/.test(placement)) {
3245
- var isVertical = /top|bottom/.test(placement), arrowDelta = isVertical ? delta.left * 2 - width + actualWidth : delta.top * 2 - height + actualHeight, arrowOffsetPosition = isVertical ? 'offsetWidth' : 'offsetHeight';
3246
- replaceArrow(arrowDelta, tip[arrowOffsetPosition], isVertical);
3300
+ safeDigest(scope);
3301
+ $$rAF($typeahead.$applyPlacement);
3302
+ };
3303
+ $typeahead.activate = function(index) {
3304
+ scope.$activeIndex = index;
3305
+ };
3306
+ $typeahead.select = function(index) {
3307
+ if (index === -1) return;
3308
+ var value = scope.$matches[index].value;
3309
+ controller.$setViewValue(value);
3310
+ controller.$render();
3311
+ scope.$resetMatches();
3312
+ if (parentScope) parentScope.$digest();
3313
+ scope.$emit(options.prefixEvent + '.select', value, index, $typeahead);
3314
+ };
3315
+ $typeahead.$isVisible = function() {
3316
+ if (!options.minLength || !controller) {
3317
+ return !!scope.$matches.length;
3247
3318
  }
3248
- }
3249
- function getViewportAdjustedDelta(placement, position, actualWidth, actualHeight) {
3250
- var delta = {
3251
- top: 0,
3252
- left: 0
3253
- }, $viewport = options.viewport && findElement(options.viewport.selector || options.viewport);
3254
- if (!$viewport) {
3255
- return delta;
3319
+ return scope.$matches.length && angular.isString(controller.$viewValue) && controller.$viewValue.length >= options.minLength;
3320
+ };
3321
+ $typeahead.$getIndex = function(value) {
3322
+ var l = scope.$matches.length, i = l;
3323
+ if (!l) return;
3324
+ for (i = l; i--; ) {
3325
+ if (scope.$matches[i].value === value) break;
3256
3326
  }
3257
- var viewportPadding = options.viewport && options.viewport.padding || 0, viewportDimensions = getPosition($viewport);
3258
- if (/right|left/.test(placement)) {
3259
- var topEdgeOffset = position.top - viewportPadding - viewportDimensions.scroll, bottomEdgeOffset = position.top + viewportPadding - viewportDimensions.scroll + actualHeight;
3260
- if (topEdgeOffset < viewportDimensions.top) {
3261
- delta.top = viewportDimensions.top - topEdgeOffset;
3262
- } else if (bottomEdgeOffset > viewportDimensions.top + viewportDimensions.height) {
3263
- delta.top = viewportDimensions.top + viewportDimensions.height - bottomEdgeOffset;
3264
- }
3265
- } else {
3266
- var leftEdgeOffset = position.left - viewportPadding, rightEdgeOffset = position.left + viewportPadding + actualWidth;
3267
- if (leftEdgeOffset < viewportDimensions.left) {
3268
- delta.left = viewportDimensions.left - leftEdgeOffset;
3269
- } else if (rightEdgeOffset > viewportDimensions.width) {
3270
- delta.left = viewportDimensions.left + viewportDimensions.width - rightEdgeOffset;
3271
- }
3327
+ if (i < 0) return;
3328
+ return i;
3329
+ };
3330
+ $typeahead.$onMouseDown = function(evt) {
3331
+ evt.preventDefault();
3332
+ evt.stopPropagation();
3333
+ };
3334
+ $typeahead.$onKeyDown = function(evt) {
3335
+ if (!/(38|40|13)/.test(evt.keyCode)) return;
3336
+ if ($typeahead.$isVisible() && !(evt.keyCode === 13 && scope.$activeIndex === -1)) {
3337
+ evt.preventDefault();
3338
+ evt.stopPropagation();
3272
3339
  }
3273
- return delta;
3274
- }
3275
- function replaceArrow(delta, dimension, isHorizontal) {
3276
- var $arrow = findElement('.tooltip-arrow, .arrow', tipElement[0]);
3277
- $arrow.css(isHorizontal ? 'left' : 'top', 50 * (1 - delta / dimension) + '%').css(isHorizontal ? 'top' : 'left', '');
3278
- }
3279
- function destroyTipElement() {
3280
- clearTimeout(timeout);
3281
- if ($tooltip.$isShown && tipElement !== null) {
3282
- if (options.autoClose) {
3283
- unbindAutoCloseEvents();
3284
- }
3340
+ if (evt.keyCode === 13 && scope.$matches.length) {
3341
+ $typeahead.select(scope.$activeIndex);
3342
+ } else if (evt.keyCode === 38 && scope.$activeIndex > 0) scope.$activeIndex--; else if (evt.keyCode === 40 && scope.$activeIndex < scope.$matches.length - 1) scope.$activeIndex++; else if (angular.isUndefined(scope.$activeIndex)) scope.$activeIndex = 0;
3343
+ scope.$digest();
3344
+ };
3345
+ var show = $typeahead.show;
3346
+ $typeahead.show = function() {
3347
+ show();
3348
+ $timeout(function() {
3349
+ $typeahead.$element && $typeahead.$element.on('mousedown', $typeahead.$onMouseDown);
3285
3350
  if (options.keyboard) {
3286
- unbindKeyboardEvents();
3351
+ element && element.on('keydown', $typeahead.$onKeyDown);
3287
3352
  }
3353
+ }, 0, false);
3354
+ };
3355
+ var hide = $typeahead.hide;
3356
+ $typeahead.hide = function() {
3357
+ $typeahead.$element && $typeahead.$element.off('mousedown', $typeahead.$onMouseDown);
3358
+ if (options.keyboard) {
3359
+ element && element.off('keydown', $typeahead.$onKeyDown);
3288
3360
  }
3289
- if (tipScope) {
3290
- tipScope.$destroy();
3291
- tipScope = null;
3292
- }
3293
- if (tipElement) {
3294
- tipElement.remove();
3295
- tipElement = $tooltip.$element = null;
3296
- }
3297
- }
3298
- return $tooltip;
3361
+ if (!options.autoSelect) $typeahead.activate(-1);
3362
+ hide();
3363
+ };
3364
+ return $typeahead;
3299
3365
  }
3300
3366
  function safeDigest(scope) {
3301
3367
  scope.$$phase || scope.$root && scope.$root.$$phase || scope.$digest();
3302
3368
  }
3303
- function findElement(query, element) {
3304
- return angular.element((element || document).querySelectorAll(query));
3305
- }
3306
- var fetchPromises = {};
3307
- function fetchTemplate(template) {
3308
- if (fetchPromises[template]) return fetchPromises[template];
3309
- return fetchPromises[template] = $http.get(template, {
3310
- cache: $templateCache
3311
- }).then(function(res) {
3312
- return res.data;
3369
+ TypeaheadFactory.defaults = defaults;
3370
+ return TypeaheadFactory;
3371
+ } ];
3372
+ }).filter('bsAsyncFilter', [ '$filter', function($filter) {
3373
+ return function(array, expression, comparator) {
3374
+ if (array && angular.isFunction(array.then)) {
3375
+ return array.then(function(results) {
3376
+ return $filter('filter')(results, expression, comparator);
3313
3377
  });
3378
+ } else {
3379
+ return $filter('filter')(array, expression, comparator);
3314
3380
  }
3315
- return TooltipFactory;
3316
- } ];
3317
- }).directive('bsTooltip', [ '$window', '$location', '$sce', '$tooltip', '$$rAF', function($window, $location, $sce, $tooltip, $$rAF) {
3381
+ };
3382
+ } ]).directive('bsTypeahead', [ '$window', '$parse', '$q', '$typeahead', '$parseOptions', function($window, $parse, $q, $typeahead, $parseOptions) {
3383
+ var defaults = $typeahead.defaults;
3318
3384
  return {
3319
3385
  restrict: 'EAC',
3320
- scope: true,
3321
- link: function postLink(scope, element, attr, transclusion) {
3386
+ require: 'ngModel',
3387
+ link: function postLink(scope, element, attr, controller) {
3322
3388
  var options = {
3323
3389
  scope: scope
3324
3390
  };
3325
- angular.forEach([ 'template', 'templateUrl', 'controller', 'controllerAs', 'contentTemplate', 'placement', 'container', 'delay', 'trigger', 'html', 'animation', 'backdropAnimation', 'type', 'customClass', 'id' ], function(key) {
3391
+ angular.forEach([ 'template', 'templateUrl', 'controller', 'controllerAs', 'placement', 'container', 'delay', 'trigger', 'keyboard', 'html', 'animation', 'filter', 'limit', 'minLength', 'watchOptions', 'selectMode', 'autoSelect', 'comparator', 'id', 'prefixEvent', 'prefixClass' ], function(key) {
3326
3392
  if (angular.isDefined(attr[key])) options[key] = attr[key];
3327
3393
  });
3328
3394
  var falseValueRegExp = /^(false|0|)$/i;
3329
- angular.forEach([ 'html', 'container' ], function(key) {
3395
+ angular.forEach([ 'html', 'container', 'trimValue' ], function(key) {
3330
3396
  if (angular.isDefined(attr[key]) && falseValueRegExp.test(attr[key])) options[key] = false;
3331
3397
  });
3332
- var dataTarget = element.attr('data-target');
3333
- if (angular.isDefined(dataTarget)) {
3334
- if (falseValueRegExp.test(dataTarget)) options.target = false; else options.target = dataTarget;
3335
- }
3336
- if (!scope.hasOwnProperty('title')) {
3337
- scope.title = '';
3338
- }
3339
- attr.$observe('title', function(newValue) {
3340
- if (angular.isDefined(newValue) || !scope.hasOwnProperty('title')) {
3341
- var oldValue = scope.title;
3342
- scope.title = $sce.trustAsHtml(newValue);
3343
- angular.isDefined(oldValue) && $$rAF(function() {
3344
- tooltip && tooltip.$applyPlacement();
3398
+ element.attr('autocomplete', 'false');
3399
+ var filter = options.filter || defaults.filter;
3400
+ var limit = options.limit || defaults.limit;
3401
+ var comparator = options.comparator || defaults.comparator;
3402
+ var bsOptions = attr.bsOptions;
3403
+ if (filter) bsOptions += ' | ' + filter + ':$viewValue';
3404
+ if (comparator) bsOptions += ':' + comparator;
3405
+ if (limit) bsOptions += ' | limitTo:' + limit;
3406
+ var parsedOptions = $parseOptions(bsOptions);
3407
+ var typeahead = $typeahead(element, controller, options);
3408
+ if (options.watchOptions) {
3409
+ var watchedOptions = parsedOptions.$match[7].replace(/\|.+/, '').replace(/\(.*\)/g, '').trim();
3410
+ scope.$watchCollection(watchedOptions, function(newValue, oldValue) {
3411
+ parsedOptions.valuesFn(scope, controller).then(function(values) {
3412
+ typeahead.update(values);
3413
+ controller.$render();
3345
3414
  });
3346
- }
3347
- });
3348
- attr.bsTooltip && scope.$watch(attr.bsTooltip, function(newValue, oldValue) {
3349
- if (angular.isObject(newValue)) {
3350
- angular.extend(scope, newValue);
3351
- } else {
3352
- scope.title = newValue;
3353
- }
3354
- angular.isDefined(oldValue) && $$rAF(function() {
3355
- tooltip && tooltip.$applyPlacement();
3356
3415
  });
3357
- }, true);
3358
- attr.bsShow && scope.$watch(attr.bsShow, function(newValue, oldValue) {
3359
- if (!tooltip || !angular.isDefined(newValue)) return;
3360
- if (angular.isString(newValue)) newValue = !!newValue.match(/true|,?(tooltip),?/i);
3361
- newValue === true ? tooltip.show() : tooltip.hide();
3362
- });
3363
- attr.bsEnabled && scope.$watch(attr.bsEnabled, function(newValue, oldValue) {
3364
- if (!tooltip || !angular.isDefined(newValue)) return;
3365
- if (angular.isString(newValue)) newValue = !!newValue.match(/true|1|,?(tooltip),?/i);
3366
- newValue === false ? tooltip.setEnabled(false) : tooltip.setEnabled(true);
3416
+ }
3417
+ scope.$watch(attr.ngModel, function(newValue, oldValue) {
3418
+ scope.$modelValue = newValue;
3419
+ parsedOptions.valuesFn(scope, controller).then(function(values) {
3420
+ if (options.selectMode && !values.length && newValue.length > 0) {
3421
+ controller.$setViewValue(controller.$viewValue.substring(0, controller.$viewValue.length - 1));
3422
+ return;
3423
+ }
3424
+ if (values.length > limit) values = values.slice(0, limit);
3425
+ var isVisible = typeahead.$isVisible();
3426
+ isVisible && typeahead.update(values);
3427
+ if (values.length === 1 && values[0].value === newValue) return;
3428
+ !isVisible && typeahead.update(values);
3429
+ controller.$render();
3430
+ });
3367
3431
  });
3368
- attr.viewport && scope.$watch(attr.viewport, function(newValue) {
3369
- if (!tooltip || !angular.isDefined(newValue)) return;
3370
- tooltip.setViewport(newValue);
3432
+ controller.$formatters.push(function(modelValue) {
3433
+ var displayValue = parsedOptions.displayValue(modelValue);
3434
+ if (displayValue) {
3435
+ return displayValue;
3436
+ }
3437
+ if (modelValue && typeof modelValue !== 'object') {
3438
+ return modelValue;
3439
+ }
3440
+ return '';
3371
3441
  });
3372
- var tooltip = $tooltip(element, options);
3442
+ controller.$render = function() {
3443
+ if (controller.$isEmpty(controller.$viewValue)) {
3444
+ return element.val('');
3445
+ }
3446
+ var index = typeahead.$getIndex(controller.$modelValue);
3447
+ var selected = angular.isDefined(index) ? typeahead.$scope.$matches[index].label : controller.$viewValue;
3448
+ selected = angular.isObject(selected) ? parsedOptions.displayValue(selected) : selected;
3449
+ var value = selected ? selected.toString().replace(/<(?:.|\n)*?>/gm, '') : '';
3450
+ element.val(options.trimValue === false ? value : value.trim());
3451
+ };
3373
3452
  scope.$on('$destroy', function() {
3374
- if (tooltip) tooltip.destroy();
3453
+ if (typeahead) typeahead.destroy();
3375
3454
  options = null;
3376
- tooltip = null;
3455
+ typeahead = null;
3377
3456
  });
3378
3457
  }
3379
3458
  };
3380
3459
  } ]);
3381
- angular.module('mgcrea.ngStrap.timepicker', [ 'mgcrea.ngStrap.helpers.dateParser', 'mgcrea.ngStrap.helpers.dateFormatter', 'mgcrea.ngStrap.tooltip' ]).provider('$timepicker', function() {
3460
+ angular.module('mgcrea.ngStrap.tooltip', [ 'mgcrea.ngStrap.core', 'mgcrea.ngStrap.helpers.dimensions' ]).provider('$tooltip', function() {
3382
3461
  var defaults = this.defaults = {
3383
3462
  animation: 'am-fade',
3384
- prefixClass: 'timepicker',
3385
- placement: 'bottom-left',
3386
- templateUrl: 'timepicker/timepicker.tpl.html',
3387
- trigger: 'focus',
3463
+ customClass: '',
3464
+ prefixClass: 'tooltip',
3465
+ prefixEvent: 'tooltip',
3388
3466
  container: false,
3389
- keyboard: true,
3467
+ target: false,
3468
+ placement: 'top',
3469
+ templateUrl: 'tooltip/tooltip.tpl.html',
3470
+ template: '',
3471
+ contentTemplate: false,
3472
+ trigger: 'hover focus',
3473
+ keyboard: false,
3390
3474
  html: false,
3475
+ show: false,
3476
+ title: '',
3477
+ type: '',
3391
3478
  delay: 0,
3392
- useNative: true,
3393
- timeType: 'date',
3394
- timeFormat: 'shortTime',
3395
- timezone: null,
3396
- modelTimeFormat: null,
3397
- autoclose: false,
3398
- minTime: -Infinity,
3399
- maxTime: +Infinity,
3400
- length: 5,
3401
- hourStep: 1,
3402
- minuteStep: 5,
3403
- secondStep: 5,
3404
- roundDisplay: false,
3405
- iconUp: 'glyphicon glyphicon-chevron-up',
3406
- iconDown: 'glyphicon glyphicon-chevron-down',
3407
- arrowBehavior: 'pager'
3479
+ autoClose: false,
3480
+ bsEnabled: true,
3481
+ viewport: {
3482
+ selector: 'body',
3483
+ padding: 0
3484
+ }
3408
3485
  };
3409
- this.$get = [ '$window', '$document', '$rootScope', '$sce', '$dateFormatter', '$tooltip', '$timeout', function($window, $document, $rootScope, $sce, $dateFormatter, $tooltip, $timeout) {
3410
- var bodyEl = angular.element($window.document.body);
3411
- var isNative = /(ip(a|o)d|iphone|android)/gi.test($window.navigator.userAgent);
3412
- var isTouch = 'createTouch' in $window.document && isNative;
3413
- if (!defaults.lang) defaults.lang = $dateFormatter.getDefaultLocale();
3414
- function timepickerFactory(element, controller, config) {
3415
- var $timepicker = $tooltip(element, angular.extend({}, defaults, config));
3416
- var parentScope = config.scope;
3417
- var options = $timepicker.$options;
3418
- var scope = $timepicker.$scope;
3419
- var lang = options.lang;
3420
- var formatDate = function(date, format, timezone) {
3421
- return $dateFormatter.formatDate(date, format, lang, timezone);
3422
- };
3423
- function floorMinutes(time) {
3424
- var coeff = 1e3 * 60 * options.minuteStep;
3425
- return new Date(Math.floor(time.getTime() / coeff) * coeff);
3486
+ this.$get = [ '$window', '$rootScope', '$bsCompiler', '$q', '$templateCache', '$http', '$animate', '$sce', 'dimensions', '$$rAF', '$timeout', function($window, $rootScope, $bsCompiler, $q, $templateCache, $http, $animate, $sce, dimensions, $$rAF, $timeout) {
3487
+ var trim = String.prototype.trim;
3488
+ var isTouch = 'createTouch' in $window.document;
3489
+ var htmlReplaceRegExp = /ng-bind="/gi;
3490
+ var $body = angular.element($window.document);
3491
+ function TooltipFactory(element, config) {
3492
+ var $tooltip = {};
3493
+ var options = $tooltip.$options = angular.extend({}, defaults, config);
3494
+ var promise = $tooltip.$promise = $bsCompiler.compile(options);
3495
+ var scope = $tooltip.$scope = options.scope && options.scope.$new() || $rootScope.$new();
3496
+ var nodeName = element[0].nodeName.toLowerCase();
3497
+ if (options.delay && angular.isString(options.delay)) {
3498
+ var split = options.delay.split(',').map(parseFloat);
3499
+ options.delay = split.length > 1 ? {
3500
+ show: split[0],
3501
+ hide: split[1]
3502
+ } : split[0];
3426
3503
  }
3427
- var selectedIndex = 0;
3428
- var defaultDate = options.roundDisplay ? floorMinutes(new Date()) : new Date();
3429
- var startDate = controller.$dateValue || defaultDate;
3430
- var viewDate = {
3431
- hour: startDate.getHours(),
3432
- meridian: startDate.getHours() < 12,
3433
- minute: startDate.getMinutes(),
3434
- second: startDate.getSeconds(),
3435
- millisecond: startDate.getMilliseconds()
3436
- };
3437
- var format = $dateFormatter.getDatetimeFormat(options.timeFormat, lang);
3438
- var hoursFormat = $dateFormatter.hoursFormat(format), timeSeparator = $dateFormatter.timeSeparator(format), minutesFormat = $dateFormatter.minutesFormat(format), secondsFormat = $dateFormatter.secondsFormat(format), showSeconds = $dateFormatter.showSeconds(format), showAM = $dateFormatter.showAM(format);
3439
- scope.$iconUp = options.iconUp;
3440
- scope.$iconDown = options.iconDown;
3441
- scope.$select = function(date, index) {
3442
- $timepicker.select(date, index);
3504
+ $tooltip.$id = options.id || element.attr('id') || '';
3505
+ if (options.title) {
3506
+ scope.title = $sce.trustAsHtml(options.title);
3507
+ }
3508
+ scope.$setEnabled = function(isEnabled) {
3509
+ scope.$$postDigest(function() {
3510
+ $tooltip.setEnabled(isEnabled);
3511
+ });
3443
3512
  };
3444
- scope.$moveIndex = function(value, index) {
3445
- $timepicker.$moveIndex(value, index);
3513
+ scope.$hide = function() {
3514
+ scope.$$postDigest(function() {
3515
+ $tooltip.hide();
3516
+ });
3446
3517
  };
3447
- scope.$switchMeridian = function(date) {
3448
- $timepicker.switchMeridian(date);
3518
+ scope.$show = function() {
3519
+ scope.$$postDigest(function() {
3520
+ $tooltip.show();
3521
+ });
3449
3522
  };
3450
- $timepicker.update = function(date) {
3451
- if (angular.isDate(date) && !isNaN(date.getTime())) {
3452
- $timepicker.$date = date;
3453
- angular.extend(viewDate, {
3454
- hour: date.getHours(),
3455
- minute: date.getMinutes(),
3456
- second: date.getSeconds(),
3457
- millisecond: date.getMilliseconds()
3458
- });
3459
- $timepicker.$build();
3460
- } else if (!$timepicker.$isBuilt) {
3461
- $timepicker.$build();
3462
- }
3523
+ scope.$toggle = function() {
3524
+ scope.$$postDigest(function() {
3525
+ $tooltip.toggle();
3526
+ });
3463
3527
  };
3464
- $timepicker.select = function(date, index, keep) {
3465
- if (!controller.$dateValue || isNaN(controller.$dateValue.getTime())) controller.$dateValue = new Date(1970, 0, 1);
3466
- if (!angular.isDate(date)) date = new Date(date);
3467
- if (index === 0) controller.$dateValue.setHours(date.getHours()); else if (index === 1) controller.$dateValue.setMinutes(date.getMinutes()); else if (index === 2) controller.$dateValue.setSeconds(date.getSeconds());
3468
- controller.$setViewValue(angular.copy(controller.$dateValue));
3469
- controller.$render();
3470
- if (options.autoclose && !keep) {
3471
- $timeout(function() {
3472
- $timepicker.hide(true);
3473
- });
3528
+ $tooltip.$isShown = scope.$isShown = false;
3529
+ var timeout, hoverState;
3530
+ var compileData, tipElement, tipContainer, tipScope;
3531
+ promise.then(function(data) {
3532
+ compileData = data;
3533
+ $tooltip.init();
3534
+ });
3535
+ $tooltip.init = function() {
3536
+ if (options.delay && angular.isNumber(options.delay)) {
3537
+ options.delay = {
3538
+ show: options.delay,
3539
+ hide: options.delay
3540
+ };
3474
3541
  }
3475
- };
3476
- $timepicker.switchMeridian = function(date) {
3477
- if (!controller.$dateValue || isNaN(controller.$dateValue.getTime())) {
3478
- return;
3542
+ if (options.container === 'self') {
3543
+ tipContainer = element;
3544
+ } else if (angular.isElement(options.container)) {
3545
+ tipContainer = options.container;
3546
+ } else if (options.container) {
3547
+ tipContainer = findElement(options.container);
3479
3548
  }
3480
- var hours = (date || controller.$dateValue).getHours();
3481
- controller.$dateValue.setHours(hours < 12 ? hours + 12 : hours - 12);
3482
- controller.$setViewValue(angular.copy(controller.$dateValue));
3483
- controller.$render();
3484
- };
3485
- $timepicker.$build = function() {
3486
- var i, midIndex = scope.midIndex = parseInt(options.length / 2, 10);
3487
- var hours = [], hour;
3488
- for (i = 0; i < options.length; i++) {
3489
- hour = new Date(1970, 0, 1, viewDate.hour - (midIndex - i) * options.hourStep);
3490
- hours.push({
3491
- date: hour,
3492
- label: formatDate(hour, hoursFormat),
3493
- selected: $timepicker.$date && $timepicker.$isSelected(hour, 0),
3494
- disabled: $timepicker.$isDisabled(hour, 0)
3495
- });
3549
+ bindTriggerEvents();
3550
+ if (options.target) {
3551
+ options.target = angular.isElement(options.target) ? options.target : findElement(options.target);
3496
3552
  }
3497
- var minutes = [], minute;
3498
- for (i = 0; i < options.length; i++) {
3499
- minute = new Date(1970, 0, 1, 0, viewDate.minute - (midIndex - i) * options.minuteStep);
3500
- minutes.push({
3501
- date: minute,
3502
- label: formatDate(minute, minutesFormat),
3503
- selected: $timepicker.$date && $timepicker.$isSelected(minute, 1),
3504
- disabled: $timepicker.$isDisabled(minute, 1)
3553
+ if (options.show) {
3554
+ scope.$$postDigest(function() {
3555
+ options.trigger === 'focus' ? element[0].focus() : $tooltip.show();
3505
3556
  });
3506
3557
  }
3507
- var seconds = [], second;
3508
- for (i = 0; i < options.length; i++) {
3509
- second = new Date(1970, 0, 1, 0, 0, viewDate.second - (midIndex - i) * options.secondStep);
3510
- seconds.push({
3511
- date: second,
3512
- label: formatDate(second, secondsFormat),
3513
- selected: $timepicker.$date && $timepicker.$isSelected(second, 2),
3514
- disabled: $timepicker.$isDisabled(second, 2)
3515
- });
3558
+ };
3559
+ $tooltip.destroy = function() {
3560
+ unbindTriggerEvents();
3561
+ destroyTipElement();
3562
+ scope.$destroy();
3563
+ };
3564
+ $tooltip.enter = function() {
3565
+ clearTimeout(timeout);
3566
+ hoverState = 'in';
3567
+ if (!options.delay || !options.delay.show) {
3568
+ return $tooltip.show();
3516
3569
  }
3517
- var rows = [];
3518
- for (i = 0; i < options.length; i++) {
3519
- if (showSeconds) {
3520
- rows.push([ hours[i], minutes[i], seconds[i] ]);
3570
+ timeout = setTimeout(function() {
3571
+ if (hoverState === 'in') $tooltip.show();
3572
+ }, options.delay.show);
3573
+ };
3574
+ $tooltip.show = function() {
3575
+ if (!options.bsEnabled || $tooltip.$isShown) return;
3576
+ scope.$emit(options.prefixEvent + '.show.before', $tooltip);
3577
+ var parent, after;
3578
+ if (options.container) {
3579
+ parent = tipContainer;
3580
+ if (tipContainer[0].lastChild) {
3581
+ after = angular.element(tipContainer[0].lastChild);
3521
3582
  } else {
3522
- rows.push([ hours[i], minutes[i] ]);
3583
+ after = null;
3584
+ }
3585
+ } else {
3586
+ parent = null;
3587
+ after = element;
3588
+ }
3589
+ if (tipElement) destroyTipElement();
3590
+ tipScope = $tooltip.$scope.$new();
3591
+ tipElement = $tooltip.$element = compileData.link(tipScope, function(clonedElement, scope) {});
3592
+ tipElement.css({
3593
+ top: '-9999px',
3594
+ left: '-9999px',
3595
+ right: 'auto',
3596
+ display: 'block',
3597
+ visibility: 'hidden'
3598
+ });
3599
+ if (options.animation) tipElement.addClass(options.animation);
3600
+ if (options.type) tipElement.addClass(options.prefixClass + '-' + options.type);
3601
+ if (options.customClass) tipElement.addClass(options.customClass);
3602
+ after ? after.after(tipElement) : parent.prepend(tipElement);
3603
+ $tooltip.$isShown = scope.$isShown = true;
3604
+ safeDigest(scope);
3605
+ $tooltip.$applyPlacement();
3606
+ if (angular.version.minor <= 2) {
3607
+ $animate.enter(tipElement, parent, after, enterAnimateCallback);
3608
+ } else {
3609
+ $animate.enter(tipElement, parent, after).then(enterAnimateCallback);
3610
+ }
3611
+ safeDigest(scope);
3612
+ $$rAF(function() {
3613
+ if (tipElement) tipElement.css({
3614
+ visibility: 'visible'
3615
+ });
3616
+ });
3617
+ if (options.keyboard) {
3618
+ if (options.trigger !== 'focus') {
3619
+ $tooltip.focus();
3523
3620
  }
3621
+ bindKeyboardEvents();
3524
3622
  }
3525
- scope.rows = rows;
3526
- scope.showSeconds = showSeconds;
3527
- scope.showAM = showAM;
3528
- scope.isAM = ($timepicker.$date || hours[midIndex].date).getHours() < 12;
3529
- scope.timeSeparator = timeSeparator;
3530
- $timepicker.$isBuilt = true;
3531
- };
3532
- $timepicker.$isSelected = function(date, index) {
3533
- if (!$timepicker.$date) return false; else if (index === 0) {
3534
- return date.getHours() === $timepicker.$date.getHours();
3535
- } else if (index === 1) {
3536
- return date.getMinutes() === $timepicker.$date.getMinutes();
3537
- } else if (index === 2) {
3538
- return date.getSeconds() === $timepicker.$date.getSeconds();
3623
+ if (options.autoClose) {
3624
+ bindAutoCloseEvents();
3539
3625
  }
3540
3626
  };
3541
- $timepicker.$isDisabled = function(date, index) {
3542
- var selectedTime;
3543
- if (index === 0) {
3544
- selectedTime = date.getTime() + viewDate.minute * 6e4 + viewDate.second * 1e3;
3545
- } else if (index === 1) {
3546
- selectedTime = date.getTime() + viewDate.hour * 36e5 + viewDate.second * 1e3;
3547
- } else if (index === 2) {
3548
- selectedTime = date.getTime() + viewDate.hour * 36e5 + viewDate.minute * 6e4;
3627
+ function enterAnimateCallback() {
3628
+ scope.$emit(options.prefixEvent + '.show', $tooltip);
3629
+ }
3630
+ $tooltip.leave = function() {
3631
+ clearTimeout(timeout);
3632
+ hoverState = 'out';
3633
+ if (!options.delay || !options.delay.hide) {
3634
+ return $tooltip.hide();
3549
3635
  }
3550
- return selectedTime < options.minTime * 1 || selectedTime > options.maxTime * 1;
3636
+ timeout = setTimeout(function() {
3637
+ if (hoverState === 'out') {
3638
+ $tooltip.hide();
3639
+ }
3640
+ }, options.delay.hide);
3551
3641
  };
3552
- scope.$arrowAction = function(value, index) {
3553
- if (options.arrowBehavior === 'picker') {
3554
- $timepicker.$setTimeByStep(value, index);
3642
+ var _blur;
3643
+ var _tipToHide;
3644
+ $tooltip.hide = function(blur) {
3645
+ if (!$tooltip.$isShown) return;
3646
+ scope.$emit(options.prefixEvent + '.hide.before', $tooltip);
3647
+ _blur = blur;
3648
+ _tipToHide = tipElement;
3649
+ if (angular.version.minor <= 2) {
3650
+ $animate.leave(tipElement, leaveAnimateCallback);
3555
3651
  } else {
3556
- $timepicker.$moveIndex(value, index);
3652
+ $animate.leave(tipElement).then(leaveAnimateCallback);
3557
3653
  }
3558
- };
3559
- $timepicker.$setTimeByStep = function(value, index) {
3560
- var newDate = new Date($timepicker.$date);
3561
- var hours = newDate.getHours(), hoursLength = formatDate(newDate, hoursFormat).length;
3562
- var minutes = newDate.getMinutes(), minutesLength = formatDate(newDate, minutesFormat).length;
3563
- var seconds = newDate.getSeconds(), secondsLength = formatDate(newDate, secondsFormat).length;
3564
- if (index === 0) {
3565
- newDate.setHours(hours - parseInt(options.hourStep, 10) * value);
3566
- } else if (index === 1) {
3567
- newDate.setMinutes(minutes - parseInt(options.minuteStep, 10) * value);
3568
- } else if (index === 2) {
3569
- newDate.setSeconds(seconds - parseInt(options.secondStep, 10) * value);
3654
+ $tooltip.$isShown = scope.$isShown = false;
3655
+ safeDigest(scope);
3656
+ if (options.keyboard && tipElement !== null) {
3657
+ unbindKeyboardEvents();
3570
3658
  }
3571
- $timepicker.select(newDate, index, true);
3572
- };
3573
- $timepicker.$moveIndex = function(value, index) {
3574
- var targetDate;
3575
- if (index === 0) {
3576
- targetDate = new Date(1970, 0, 1, viewDate.hour + value * options.length, viewDate.minute, viewDate.second);
3577
- angular.extend(viewDate, {
3578
- hour: targetDate.getHours()
3579
- });
3580
- } else if (index === 1) {
3581
- targetDate = new Date(1970, 0, 1, viewDate.hour, viewDate.minute + value * options.length * options.minuteStep, viewDate.second);
3582
- angular.extend(viewDate, {
3583
- minute: targetDate.getMinutes()
3584
- });
3585
- } else if (index === 2) {
3586
- targetDate = new Date(1970, 0, 1, viewDate.hour, viewDate.minute, viewDate.second + value * options.length * options.secondStep);
3587
- angular.extend(viewDate, {
3588
- second: targetDate.getSeconds()
3589
- });
3659
+ if (options.autoClose && tipElement !== null) {
3660
+ unbindAutoCloseEvents();
3590
3661
  }
3591
- $timepicker.$build();
3592
3662
  };
3593
- $timepicker.$onMouseDown = function(evt) {
3594
- if (evt.target.nodeName.toLowerCase() !== 'input') evt.preventDefault();
3595
- evt.stopPropagation();
3596
- if (isTouch) {
3597
- var targetEl = angular.element(evt.target);
3598
- if (targetEl[0].nodeName.toLowerCase() !== 'button') {
3599
- targetEl = targetEl.parent();
3663
+ function leaveAnimateCallback() {
3664
+ scope.$emit(options.prefixEvent + '.hide', $tooltip);
3665
+ if (tipElement === _tipToHide) {
3666
+ if (_blur && options.trigger === 'focus') {
3667
+ return element[0].blur();
3600
3668
  }
3601
- targetEl.triggerHandler('click');
3669
+ destroyTipElement();
3602
3670
  }
3671
+ }
3672
+ $tooltip.toggle = function() {
3673
+ $tooltip.$isShown ? $tooltip.leave() : $tooltip.enter();
3603
3674
  };
3604
- $timepicker.$onKeyDown = function(evt) {
3605
- if (!/(38|37|39|40|13)/.test(evt.keyCode) || evt.shiftKey || evt.altKey) return;
3606
- evt.preventDefault();
3607
- evt.stopPropagation();
3608
- if (evt.keyCode === 13) return $timepicker.hide(true);
3609
- var newDate = new Date($timepicker.$date);
3610
- var hours = newDate.getHours(), hoursLength = formatDate(newDate, hoursFormat).length;
3611
- var minutes = newDate.getMinutes(), minutesLength = formatDate(newDate, minutesFormat).length;
3612
- var seconds = newDate.getSeconds(), secondsLength = formatDate(newDate, secondsFormat).length;
3613
- var sepLength = 1;
3614
- var lateralMove = /(37|39)/.test(evt.keyCode);
3615
- var count = 2 + showSeconds * 1 + showAM * 1;
3616
- if (lateralMove) {
3617
- if (evt.keyCode === 37) selectedIndex = selectedIndex < 1 ? count - 1 : selectedIndex - 1; else if (evt.keyCode === 39) selectedIndex = selectedIndex < count - 1 ? selectedIndex + 1 : 0;
3618
- }
3619
- var selectRange = [ 0, hoursLength ];
3620
- var incr = 0;
3621
- if (evt.keyCode === 38) incr = -1;
3622
- if (evt.keyCode === 40) incr = +1;
3623
- var isSeconds = selectedIndex === 2 && showSeconds;
3624
- var isMeridian = selectedIndex === 2 && !showSeconds || selectedIndex === 3 && showSeconds;
3625
- if (selectedIndex === 0) {
3626
- newDate.setHours(hours + incr * parseInt(options.hourStep, 10));
3627
- hoursLength = formatDate(newDate, hoursFormat).length;
3628
- selectRange = [ 0, hoursLength ];
3629
- } else if (selectedIndex === 1) {
3630
- newDate.setMinutes(minutes + incr * parseInt(options.minuteStep, 10));
3631
- minutesLength = formatDate(newDate, minutesFormat).length;
3632
- selectRange = [ hoursLength + sepLength, minutesLength ];
3633
- } else if (isSeconds) {
3634
- newDate.setSeconds(seconds + incr * parseInt(options.secondStep, 10));
3635
- secondsLength = formatDate(newDate, secondsFormat).length;
3636
- selectRange = [ hoursLength + sepLength + minutesLength + sepLength, secondsLength ];
3637
- } else if (isMeridian) {
3638
- if (!lateralMove) $timepicker.switchMeridian();
3639
- selectRange = [ hoursLength + sepLength + minutesLength + sepLength + (secondsLength + sepLength) * showSeconds, 2 ];
3640
- }
3641
- $timepicker.select(newDate, selectedIndex, true);
3642
- createSelection(selectRange[0], selectRange[1]);
3643
- parentScope.$digest();
3675
+ $tooltip.focus = function() {
3676
+ tipElement[0].focus();
3644
3677
  };
3645
- function createSelection(start, length) {
3646
- var end = start + length;
3647
- if (element[0].createTextRange) {
3648
- var selRange = element[0].createTextRange();
3649
- selRange.collapse(true);
3650
- selRange.moveStart('character', start);
3651
- selRange.moveEnd('character', end);
3652
- selRange.select();
3653
- } else if (element[0].setSelectionRange) {
3654
- element[0].setSelectionRange(start, end);
3655
- } else if (angular.isUndefined(element[0].selectionStart)) {
3656
- element[0].selectionStart = start;
3657
- element[0].selectionEnd = end;
3658
- }
3659
- }
3660
- function focusElement() {
3661
- element[0].focus();
3662
- }
3663
- var _init = $timepicker.init;
3664
- $timepicker.init = function() {
3665
- if (isNative && options.useNative) {
3666
- element.prop('type', 'time');
3667
- element.css('-webkit-appearance', 'textfield');
3668
- return;
3669
- } else if (isTouch) {
3670
- element.prop('type', 'text');
3671
- element.attr('readonly', 'true');
3672
- element.on('click', focusElement);
3673
- }
3674
- _init();
3678
+ $tooltip.setEnabled = function(isEnabled) {
3679
+ options.bsEnabled = isEnabled;
3675
3680
  };
3676
- var _destroy = $timepicker.destroy;
3677
- $timepicker.destroy = function() {
3678
- if (isNative && options.useNative) {
3679
- element.off('click', focusElement);
3680
- }
3681
- _destroy();
3681
+ $tooltip.setViewport = function(viewport) {
3682
+ options.viewport = viewport;
3682
3683
  };
3683
- var _show = $timepicker.show;
3684
- $timepicker.show = function() {
3685
- _show();
3686
- $timeout(function() {
3687
- $timepicker.$element && $timepicker.$element.on(isTouch ? 'touchstart' : 'mousedown', $timepicker.$onMouseDown);
3688
- if (options.keyboard) {
3689
- element && element.on('keydown', $timepicker.$onKeyDown);
3684
+ $tooltip.$applyPlacement = function() {
3685
+ if (!tipElement) return;
3686
+ var placement = options.placement, autoToken = /\s?auto?\s?/i, autoPlace = autoToken.test(placement);
3687
+ if (autoPlace) {
3688
+ placement = placement.replace(autoToken, '') || defaults.placement;
3689
+ }
3690
+ tipElement.addClass(options.placement);
3691
+ var elementPosition = getPosition(), tipWidth = tipElement.prop('offsetWidth'), tipHeight = tipElement.prop('offsetHeight');
3692
+ $tooltip.$viewport = options.viewport && findElement(options.viewport.selector || options.viewport);
3693
+ if (autoPlace) {
3694
+ var originalPlacement = placement;
3695
+ var viewportPosition = getPosition($tooltip.$viewport);
3696
+ if (originalPlacement.indexOf('bottom') >= 0 && elementPosition.bottom + tipHeight > viewportPosition.bottom) {
3697
+ placement = originalPlacement.replace('bottom', 'top');
3698
+ } else if (originalPlacement.indexOf('top') >= 0 && elementPosition.top - tipHeight < viewportPosition.top) {
3699
+ placement = originalPlacement.replace('top', 'bottom');
3690
3700
  }
3691
- }, 0, false);
3701
+ if ((originalPlacement === 'right' || originalPlacement === 'bottom-left' || originalPlacement === 'top-left') && elementPosition.right + tipWidth > viewportPosition.width) {
3702
+ placement = originalPlacement === 'right' ? 'left' : placement.replace('left', 'right');
3703
+ } else if ((originalPlacement === 'left' || originalPlacement === 'bottom-right' || originalPlacement === 'top-right') && elementPosition.left - tipWidth < viewportPosition.left) {
3704
+ placement = originalPlacement === 'left' ? 'right' : placement.replace('right', 'left');
3705
+ }
3706
+ tipElement.removeClass(originalPlacement).addClass(placement);
3707
+ }
3708
+ var tipPosition = getCalculatedOffset(placement, elementPosition, tipWidth, tipHeight);
3709
+ applyPlacement(tipPosition, placement);
3692
3710
  };
3693
- var _hide = $timepicker.hide;
3694
- $timepicker.hide = function(blur) {
3695
- if (!$timepicker.$isShown) return;
3696
- $timepicker.$element && $timepicker.$element.off(isTouch ? 'touchstart' : 'mousedown', $timepicker.$onMouseDown);
3697
- if (options.keyboard) {
3698
- element && element.off('keydown', $timepicker.$onKeyDown);
3711
+ $tooltip.$onKeyUp = function(evt) {
3712
+ if (evt.which === 27 && $tooltip.$isShown) {
3713
+ $tooltip.hide();
3714
+ evt.stopPropagation();
3699
3715
  }
3700
- _hide(blur);
3701
3716
  };
3702
- return $timepicker;
3703
- }
3704
- timepickerFactory.defaults = defaults;
3705
- return timepickerFactory;
3706
- } ];
3707
- }).directive('bsTimepicker', [ '$window', '$parse', '$q', '$dateFormatter', '$dateParser', '$timepicker', function($window, $parse, $q, $dateFormatter, $dateParser, $timepicker) {
3708
- var defaults = $timepicker.defaults;
3709
- var isNative = /(ip(a|o)d|iphone|android)/gi.test($window.navigator.userAgent);
3710
- var requestAnimationFrame = $window.requestAnimationFrame || $window.setTimeout;
3711
- return {
3712
- restrict: 'EAC',
3713
- require: 'ngModel',
3714
- link: function postLink(scope, element, attr, controller) {
3715
- var options = {
3716
- scope: scope
3717
+ $tooltip.$onFocusKeyUp = function(evt) {
3718
+ if (evt.which === 27) {
3719
+ element[0].blur();
3720
+ evt.stopPropagation();
3721
+ }
3717
3722
  };
3718
- angular.forEach([ 'template', 'templateUrl', 'controller', 'controllerAs', 'placement', 'container', 'delay', 'trigger', 'keyboard', 'html', 'animation', 'autoclose', 'timeType', 'timeFormat', 'timezone', 'modelTimeFormat', 'useNative', 'hourStep', 'minuteStep', 'secondStep', 'length', 'arrowBehavior', 'iconUp', 'iconDown', 'roundDisplay', 'id', 'prefixClass', 'prefixEvent' ], function(key) {
3719
- if (angular.isDefined(attr[key])) options[key] = attr[key];
3720
- });
3721
- var falseValueRegExp = /^(false|0|)$/i;
3722
- angular.forEach([ 'html', 'container', 'autoclose', 'useNative', 'roundDisplay' ], function(key) {
3723
- if (angular.isDefined(attr[key]) && falseValueRegExp.test(attr[key])) options[key] = false;
3724
- });
3725
- attr.bsShow && scope.$watch(attr.bsShow, function(newValue, oldValue) {
3726
- if (!timepicker || !angular.isDefined(newValue)) return;
3727
- if (angular.isString(newValue)) newValue = !!newValue.match(/true|,?(timepicker),?/i);
3728
- newValue === true ? timepicker.show() : timepicker.hide();
3729
- });
3730
- if (isNative && (options.useNative || defaults.useNative)) options.timeFormat = 'HH:mm';
3731
- var timepicker = $timepicker(element, controller, options);
3732
- options = timepicker.$options;
3733
- var lang = options.lang;
3734
- var formatDate = function(date, format, timezone) {
3735
- return $dateFormatter.formatDate(date, format, lang, timezone);
3723
+ $tooltip.$onFocusElementMouseDown = function(evt) {
3724
+ evt.preventDefault();
3725
+ evt.stopPropagation();
3726
+ $tooltip.$isShown ? element[0].blur() : element[0].focus();
3736
3727
  };
3737
- var dateParser = $dateParser({
3738
- format: options.timeFormat,
3739
- lang: lang
3740
- });
3741
- angular.forEach([ 'minTime', 'maxTime' ], function(key) {
3742
- angular.isDefined(attr[key]) && attr.$observe(key, function(newValue) {
3743
- timepicker.$options[key] = dateParser.getTimeForAttribute(key, newValue);
3744
- !isNaN(timepicker.$options[key]) && timepicker.$build();
3745
- validateAgainstMinMaxTime(controller.$dateValue);
3728
+ function bindTriggerEvents() {
3729
+ var triggers = options.trigger.split(' ');
3730
+ angular.forEach(triggers, function(trigger) {
3731
+ if (trigger === 'click') {
3732
+ element.on('click', $tooltip.toggle);
3733
+ } else if (trigger !== 'manual') {
3734
+ element.on(trigger === 'hover' ? 'mouseenter' : 'focus', $tooltip.enter);
3735
+ element.on(trigger === 'hover' ? 'mouseleave' : 'blur', $tooltip.leave);
3736
+ nodeName === 'button' && trigger !== 'hover' && element.on(isTouch ? 'touchstart' : 'mousedown', $tooltip.$onFocusElementMouseDown);
3737
+ }
3746
3738
  });
3747
- });
3748
- scope.$watch(attr.ngModel, function(newValue, oldValue) {
3749
- timepicker.update(controller.$dateValue);
3750
- }, true);
3751
- function validateAgainstMinMaxTime(parsedTime) {
3752
- if (!angular.isDate(parsedTime)) return;
3753
- var isMinValid = isNaN(options.minTime) || new Date(parsedTime.getTime()).setFullYear(1970, 0, 1) >= options.minTime;
3754
- var isMaxValid = isNaN(options.maxTime) || new Date(parsedTime.getTime()).setFullYear(1970, 0, 1) <= options.maxTime;
3755
- var isValid = isMinValid && isMaxValid;
3756
- controller.$setValidity('date', isValid);
3757
- controller.$setValidity('min', isMinValid);
3758
- controller.$setValidity('max', isMaxValid);
3759
- if (!isValid) {
3760
- return;
3739
+ }
3740
+ function unbindTriggerEvents() {
3741
+ var triggers = options.trigger.split(' ');
3742
+ for (var i = triggers.length; i--; ) {
3743
+ var trigger = triggers[i];
3744
+ if (trigger === 'click') {
3745
+ element.off('click', $tooltip.toggle);
3746
+ } else if (trigger !== 'manual') {
3747
+ element.off(trigger === 'hover' ? 'mouseenter' : 'focus', $tooltip.enter);
3748
+ element.off(trigger === 'hover' ? 'mouseleave' : 'blur', $tooltip.leave);
3749
+ nodeName === 'button' && trigger !== 'hover' && element.off(isTouch ? 'touchstart' : 'mousedown', $tooltip.$onFocusElementMouseDown);
3750
+ }
3761
3751
  }
3762
- controller.$dateValue = parsedTime;
3763
3752
  }
3764
- controller.$parsers.unshift(function(viewValue) {
3765
- var date;
3766
- if (!viewValue) {
3767
- controller.$setValidity('date', true);
3768
- return null;
3753
+ function bindKeyboardEvents() {
3754
+ if (options.trigger !== 'focus') {
3755
+ tipElement.on('keyup', $tooltip.$onKeyUp);
3756
+ } else {
3757
+ element.on('keyup', $tooltip.$onFocusKeyUp);
3769
3758
  }
3770
- var parsedTime = angular.isDate(viewValue) ? viewValue : dateParser.parse(viewValue, controller.$dateValue);
3771
- if (!parsedTime || isNaN(parsedTime.getTime())) {
3772
- controller.$setValidity('date', false);
3773
- return;
3759
+ }
3760
+ function unbindKeyboardEvents() {
3761
+ if (options.trigger !== 'focus') {
3762
+ tipElement.off('keyup', $tooltip.$onKeyUp);
3774
3763
  } else {
3775
- validateAgainstMinMaxTime(parsedTime);
3764
+ element.off('keyup', $tooltip.$onFocusKeyUp);
3776
3765
  }
3777
- if (options.timeType === 'string') {
3778
- date = dateParser.timezoneOffsetAdjust(parsedTime, options.timezone, true);
3779
- return formatDate(date, options.modelTimeFormat || options.timeFormat);
3766
+ }
3767
+ var _autoCloseEventsBinded = false;
3768
+ function bindAutoCloseEvents() {
3769
+ $timeout(function() {
3770
+ tipElement.on('click', stopEventPropagation);
3771
+ $body.on('click', $tooltip.hide);
3772
+ _autoCloseEventsBinded = true;
3773
+ }, 0, false);
3774
+ }
3775
+ function unbindAutoCloseEvents() {
3776
+ if (_autoCloseEventsBinded) {
3777
+ tipElement.off('click', stopEventPropagation);
3778
+ $body.off('click', $tooltip.hide);
3779
+ _autoCloseEventsBinded = false;
3780
3780
  }
3781
- date = dateParser.timezoneOffsetAdjust(controller.$dateValue, options.timezone, true);
3782
- if (options.timeType === 'number') {
3783
- return date.getTime();
3784
- } else if (options.timeType === 'unix') {
3785
- return date.getTime() / 1e3;
3786
- } else if (options.timeType === 'iso') {
3787
- return date.toISOString();
3788
- } else {
3789
- return new Date(date);
3781
+ }
3782
+ function stopEventPropagation(event) {
3783
+ event.stopPropagation();
3784
+ }
3785
+ function getPosition($element) {
3786
+ $element = $element || (options.target || element);
3787
+ var el = $element[0], isBody = el.tagName === 'BODY';
3788
+ var elRect = el.getBoundingClientRect();
3789
+ var rect = {};
3790
+ for (var p in elRect) {
3791
+ rect[p] = elRect[p];
3790
3792
  }
3791
- });
3792
- controller.$formatters.push(function(modelValue) {
3793
- var date;
3794
- if (angular.isUndefined(modelValue) || modelValue === null) {
3795
- date = NaN;
3796
- } else if (angular.isDate(modelValue)) {
3797
- date = modelValue;
3798
- } else if (options.timeType === 'string') {
3799
- date = dateParser.parse(modelValue, null, options.modelTimeFormat);
3800
- } else if (options.timeType === 'unix') {
3801
- date = new Date(modelValue * 1e3);
3802
- } else {
3803
- date = new Date(modelValue);
3793
+ if (rect.width === null) {
3794
+ rect = angular.extend({}, rect, {
3795
+ width: elRect.right - elRect.left,
3796
+ height: elRect.bottom - elRect.top
3797
+ });
3804
3798
  }
3805
- controller.$dateValue = dateParser.timezoneOffsetAdjust(date, options.timezone);
3806
- return getTimeFormattedString();
3807
- });
3808
- controller.$render = function() {
3809
- element.val(getTimeFormattedString());
3810
- };
3811
- function getTimeFormattedString() {
3812
- return !controller.$dateValue || isNaN(controller.$dateValue.getTime()) ? '' : formatDate(controller.$dateValue, options.timeFormat);
3799
+ var elOffset = isBody ? {
3800
+ top: 0,
3801
+ left: 0
3802
+ } : dimensions.offset(el), scroll = {
3803
+ scroll: isBody ? document.documentElement.scrollTop || document.body.scrollTop : $element.prop('scrollTop') || 0
3804
+ }, outerDims = isBody ? {
3805
+ width: document.documentElement.clientWidth,
3806
+ height: $window.innerHeight
3807
+ } : null;
3808
+ return angular.extend({}, rect, scroll, outerDims, elOffset);
3813
3809
  }
3814
- scope.$on('$destroy', function() {
3815
- if (timepicker) timepicker.destroy();
3816
- options = null;
3817
- timepicker = null;
3818
- });
3819
- }
3820
- };
3821
- } ]);
3822
- angular.module('mgcrea.ngStrap.typeahead', [ 'mgcrea.ngStrap.tooltip', 'mgcrea.ngStrap.helpers.parseOptions' ]).provider('$typeahead', function() {
3823
- var defaults = this.defaults = {
3824
- animation: 'am-fade',
3825
- prefixClass: 'typeahead',
3826
- prefixEvent: '$typeahead',
3827
- placement: 'bottom-left',
3828
- templateUrl: 'typeahead/typeahead.tpl.html',
3829
- trigger: 'focus',
3830
- container: false,
3831
- keyboard: true,
3832
- html: false,
3833
- delay: 0,
3834
- minLength: 1,
3835
- filter: 'filter',
3836
- limit: 6,
3837
- autoSelect: false,
3838
- comparator: '',
3839
- trimValue: true
3840
- };
3841
- this.$get = [ '$window', '$rootScope', '$tooltip', '$$rAF', '$timeout', function($window, $rootScope, $tooltip, $$rAF, $timeout) {
3842
- var bodyEl = angular.element($window.document.body);
3843
- function TypeaheadFactory(element, controller, config) {
3844
- var $typeahead = {};
3845
- var options = angular.extend({}, defaults, config);
3846
- $typeahead = $tooltip(element, options);
3847
- var parentScope = config.scope;
3848
- var scope = $typeahead.$scope;
3849
- scope.$resetMatches = function() {
3850
- scope.$matches = [];
3851
- scope.$activeIndex = options.autoSelect ? 0 : -1;
3852
- };
3853
- scope.$resetMatches();
3854
- scope.$activate = function(index) {
3855
- scope.$$postDigest(function() {
3856
- $typeahead.activate(index);
3857
- });
3858
- };
3859
- scope.$select = function(index, evt) {
3860
- scope.$$postDigest(function() {
3861
- $typeahead.select(index);
3862
- });
3863
- };
3864
- scope.$isVisible = function() {
3865
- return $typeahead.$isVisible();
3866
- };
3867
- $typeahead.update = function(matches) {
3868
- scope.$matches = matches;
3869
- if (scope.$activeIndex >= matches.length) {
3870
- scope.$activeIndex = options.autoSelect ? 0 : -1;
3810
+ function getCalculatedOffset(placement, position, actualWidth, actualHeight) {
3811
+ var offset;
3812
+ var split = placement.split('-');
3813
+ switch (split[0]) {
3814
+ case 'right':
3815
+ offset = {
3816
+ top: position.top + position.height / 2 - actualHeight / 2,
3817
+ left: position.left + position.width
3818
+ };
3819
+ break;
3820
+
3821
+ case 'bottom':
3822
+ offset = {
3823
+ top: position.top + position.height,
3824
+ left: position.left + position.width / 2 - actualWidth / 2
3825
+ };
3826
+ break;
3827
+
3828
+ case 'left':
3829
+ offset = {
3830
+ top: position.top + position.height / 2 - actualHeight / 2,
3831
+ left: position.left - actualWidth
3832
+ };
3833
+ break;
3834
+
3835
+ default:
3836
+ offset = {
3837
+ top: position.top - actualHeight,
3838
+ left: position.left + position.width / 2 - actualWidth / 2
3839
+ };
3840
+ break;
3841
+ }
3842
+ if (!split[1]) {
3843
+ return offset;
3844
+ }
3845
+ if (split[0] === 'top' || split[0] === 'bottom') {
3846
+ switch (split[1]) {
3847
+ case 'left':
3848
+ offset.left = position.left;
3849
+ break;
3850
+
3851
+ case 'right':
3852
+ offset.left = position.left + position.width - actualWidth;
3853
+ }
3854
+ } else if (split[0] === 'left' || split[0] === 'right') {
3855
+ switch (split[1]) {
3856
+ case 'top':
3857
+ offset.top = position.top - actualHeight;
3858
+ break;
3859
+
3860
+ case 'bottom':
3861
+ offset.top = position.top + position.height;
3862
+ }
3871
3863
  }
3872
- safeDigest(scope);
3873
- $$rAF($typeahead.$applyPlacement);
3874
- };
3875
- $typeahead.activate = function(index) {
3876
- scope.$activeIndex = index;
3877
- };
3878
- $typeahead.select = function(index) {
3879
- if (index === -1) return;
3880
- var value = scope.$matches[index].value;
3881
- controller.$setViewValue(value);
3882
- controller.$render();
3883
- scope.$resetMatches();
3884
- if (parentScope) parentScope.$digest();
3885
- scope.$emit(options.prefixEvent + '.select', value, index, $typeahead);
3886
- };
3887
- $typeahead.$isVisible = function() {
3888
- if (!options.minLength || !controller) {
3889
- return !!scope.$matches.length;
3864
+ return offset;
3865
+ }
3866
+ function applyPlacement(offset, placement) {
3867
+ var tip = tipElement[0], width = tip.offsetWidth, height = tip.offsetHeight;
3868
+ var marginTop = parseInt(dimensions.css(tip, 'margin-top'), 10), marginLeft = parseInt(dimensions.css(tip, 'margin-left'), 10);
3869
+ if (isNaN(marginTop)) marginTop = 0;
3870
+ if (isNaN(marginLeft)) marginLeft = 0;
3871
+ offset.top = offset.top + marginTop;
3872
+ offset.left = offset.left + marginLeft;
3873
+ dimensions.setOffset(tip, angular.extend({
3874
+ using: function(props) {
3875
+ tipElement.css({
3876
+ top: Math.round(props.top) + 'px',
3877
+ left: Math.round(props.left) + 'px',
3878
+ right: ''
3879
+ });
3880
+ }
3881
+ }, offset), 0);
3882
+ var actualWidth = tip.offsetWidth, actualHeight = tip.offsetHeight;
3883
+ if (placement === 'top' && actualHeight !== height) {
3884
+ offset.top = offset.top + height - actualHeight;
3890
3885
  }
3891
- return scope.$matches.length && angular.isString(controller.$viewValue) && controller.$viewValue.length >= options.minLength;
3892
- };
3893
- $typeahead.$getIndex = function(value) {
3894
- var l = scope.$matches.length, i = l;
3895
- if (!l) return;
3896
- for (i = l; i--; ) {
3897
- if (scope.$matches[i].value === value) break;
3886
+ if (/top-left|top-right|bottom-left|bottom-right/.test(placement)) return;
3887
+ var delta = getViewportAdjustedDelta(placement, offset, actualWidth, actualHeight);
3888
+ if (delta.left) {
3889
+ offset.left += delta.left;
3890
+ } else {
3891
+ offset.top += delta.top;
3898
3892
  }
3899
- if (i < 0) return;
3900
- return i;
3901
- };
3902
- $typeahead.$onMouseDown = function(evt) {
3903
- evt.preventDefault();
3904
- evt.stopPropagation();
3905
- };
3906
- $typeahead.$onKeyDown = function(evt) {
3907
- if (!/(38|40|13)/.test(evt.keyCode)) return;
3908
- if ($typeahead.$isVisible() && !(evt.keyCode === 13 && scope.$activeIndex === -1)) {
3909
- evt.preventDefault();
3910
- evt.stopPropagation();
3893
+ dimensions.setOffset(tip, offset);
3894
+ if (/top|right|bottom|left/.test(placement)) {
3895
+ var isVertical = /top|bottom/.test(placement), arrowDelta = isVertical ? delta.left * 2 - width + actualWidth : delta.top * 2 - height + actualHeight, arrowOffsetPosition = isVertical ? 'offsetWidth' : 'offsetHeight';
3896
+ replaceArrow(arrowDelta, tip[arrowOffsetPosition], isVertical);
3911
3897
  }
3912
- if (evt.keyCode === 13 && scope.$matches.length) {
3913
- $typeahead.select(scope.$activeIndex);
3914
- } else if (evt.keyCode === 38 && scope.$activeIndex > 0) scope.$activeIndex--; else if (evt.keyCode === 40 && scope.$activeIndex < scope.$matches.length - 1) scope.$activeIndex++; else if (angular.isUndefined(scope.$activeIndex)) scope.$activeIndex = 0;
3915
- scope.$digest();
3916
- };
3917
- var show = $typeahead.show;
3918
- $typeahead.show = function() {
3919
- show();
3920
- $timeout(function() {
3921
- $typeahead.$element && $typeahead.$element.on('mousedown', $typeahead.$onMouseDown);
3898
+ }
3899
+ function getViewportAdjustedDelta(placement, position, actualWidth, actualHeight) {
3900
+ var delta = {
3901
+ top: 0,
3902
+ left: 0
3903
+ };
3904
+ if (!$tooltip.$viewport) return delta;
3905
+ var viewportPadding = options.viewport && options.viewport.padding || 0;
3906
+ var viewportDimensions = getPosition($tooltip.$viewport);
3907
+ if (/right|left/.test(placement)) {
3908
+ var topEdgeOffset = position.top - viewportPadding - viewportDimensions.scroll;
3909
+ var bottomEdgeOffset = position.top + viewportPadding - viewportDimensions.scroll + actualHeight;
3910
+ if (topEdgeOffset < viewportDimensions.top) {
3911
+ delta.top = viewportDimensions.top - topEdgeOffset;
3912
+ } else if (bottomEdgeOffset > viewportDimensions.top + viewportDimensions.height) {
3913
+ delta.top = viewportDimensions.top + viewportDimensions.height - bottomEdgeOffset;
3914
+ }
3915
+ } else {
3916
+ var leftEdgeOffset = position.left - viewportPadding;
3917
+ var rightEdgeOffset = position.left + viewportPadding + actualWidth;
3918
+ if (leftEdgeOffset < viewportDimensions.left) {
3919
+ delta.left = viewportDimensions.left - leftEdgeOffset;
3920
+ } else if (rightEdgeOffset > viewportDimensions.right) {
3921
+ delta.left = viewportDimensions.left + viewportDimensions.width - rightEdgeOffset;
3922
+ }
3923
+ }
3924
+ return delta;
3925
+ }
3926
+ function replaceArrow(delta, dimension, isHorizontal) {
3927
+ var $arrow = findElement('.tooltip-arrow, .arrow', tipElement[0]);
3928
+ $arrow.css(isHorizontal ? 'left' : 'top', 50 * (1 - delta / dimension) + '%').css(isHorizontal ? 'top' : 'left', '');
3929
+ }
3930
+ function destroyTipElement() {
3931
+ clearTimeout(timeout);
3932
+ if ($tooltip.$isShown && tipElement !== null) {
3933
+ if (options.autoClose) {
3934
+ unbindAutoCloseEvents();
3935
+ }
3922
3936
  if (options.keyboard) {
3923
- element && element.on('keydown', $typeahead.$onKeyDown);
3937
+ unbindKeyboardEvents();
3924
3938
  }
3925
- }, 0, false);
3926
- };
3927
- var hide = $typeahead.hide;
3928
- $typeahead.hide = function() {
3929
- $typeahead.$element && $typeahead.$element.off('mousedown', $typeahead.$onMouseDown);
3930
- if (options.keyboard) {
3931
- element && element.off('keydown', $typeahead.$onKeyDown);
3932
3939
  }
3933
- if (!options.autoSelect) $typeahead.activate(-1);
3934
- hide();
3935
- };
3936
- return $typeahead;
3940
+ if (tipScope) {
3941
+ tipScope.$destroy();
3942
+ tipScope = null;
3943
+ }
3944
+ if (tipElement) {
3945
+ tipElement.remove();
3946
+ tipElement = $tooltip.$element = null;
3947
+ }
3948
+ }
3949
+ return $tooltip;
3937
3950
  }
3938
3951
  function safeDigest(scope) {
3939
3952
  scope.$$phase || scope.$root && scope.$root.$$phase || scope.$digest();
3940
3953
  }
3941
- TypeaheadFactory.defaults = defaults;
3942
- return TypeaheadFactory;
3954
+ function findElement(query, element) {
3955
+ return angular.element((element || document).querySelectorAll(query));
3956
+ }
3957
+ var fetchPromises = {};
3958
+ function fetchTemplate(template) {
3959
+ if (fetchPromises[template]) return fetchPromises[template];
3960
+ return fetchPromises[template] = $http.get(template, {
3961
+ cache: $templateCache
3962
+ }).then(function(res) {
3963
+ return res.data;
3964
+ });
3965
+ }
3966
+ return TooltipFactory;
3943
3967
  } ];
3944
- }).directive('bsTypeahead', [ '$window', '$parse', '$q', '$typeahead', '$parseOptions', function($window, $parse, $q, $typeahead, $parseOptions) {
3945
- var defaults = $typeahead.defaults;
3968
+ }).directive('bsTooltip', [ '$window', '$location', '$sce', '$tooltip', '$$rAF', function($window, $location, $sce, $tooltip, $$rAF) {
3946
3969
  return {
3947
3970
  restrict: 'EAC',
3948
- require: 'ngModel',
3949
- link: function postLink(scope, element, attr, controller) {
3971
+ scope: true,
3972
+ link: function postLink(scope, element, attr, transclusion) {
3950
3973
  var options = {
3951
3974
  scope: scope
3952
3975
  };
3953
- angular.forEach([ 'template', 'templateUrl', 'controller', 'controllerAs', 'placement', 'container', 'delay', 'trigger', 'keyboard', 'html', 'animation', 'filter', 'limit', 'minLength', 'watchOptions', 'selectMode', 'autoSelect', 'comparator', 'id', 'prefixEvent', 'prefixClass' ], function(key) {
3976
+ angular.forEach([ 'template', 'templateUrl', 'controller', 'controllerAs', 'contentTemplate', 'placement', 'container', 'delay', 'trigger', 'html', 'animation', 'backdropAnimation', 'type', 'customClass', 'id' ], function(key) {
3954
3977
  if (angular.isDefined(attr[key])) options[key] = attr[key];
3955
3978
  });
3956
3979
  var falseValueRegExp = /^(false|0|)$/i;
3957
- angular.forEach([ 'html', 'container', 'trimValue' ], function(key) {
3980
+ angular.forEach([ 'html', 'container' ], function(key) {
3958
3981
  if (angular.isDefined(attr[key]) && falseValueRegExp.test(attr[key])) options[key] = false;
3959
3982
  });
3960
- element.attr('autocomplete', 'false');
3961
- var filter = options.filter || defaults.filter;
3962
- var limit = options.limit || defaults.limit;
3963
- var comparator = options.comparator || defaults.comparator;
3964
- var bsOptions = attr.bsOptions;
3965
- if (filter) bsOptions += ' | ' + filter + ':$viewValue';
3966
- if (comparator) bsOptions += ':' + comparator;
3967
- if (limit) bsOptions += ' | limitTo:' + limit;
3968
- var parsedOptions = $parseOptions(bsOptions);
3969
- var typeahead = $typeahead(element, controller, options);
3970
- if (options.watchOptions) {
3971
- var watchedOptions = parsedOptions.$match[7].replace(/\|.+/, '').replace(/\(.*\)/g, '').trim();
3972
- scope.$watchCollection(watchedOptions, function(newValue, oldValue) {
3973
- parsedOptions.valuesFn(scope, controller).then(function(values) {
3974
- typeahead.update(values);
3975
- controller.$render();
3976
- });
3977
- });
3983
+ var dataTarget = element.attr('data-target');
3984
+ if (angular.isDefined(dataTarget)) {
3985
+ if (falseValueRegExp.test(dataTarget)) options.target = false; else options.target = dataTarget;
3978
3986
  }
3979
- scope.$watch(attr.ngModel, function(newValue, oldValue) {
3980
- scope.$modelValue = newValue;
3981
- parsedOptions.valuesFn(scope, controller).then(function(values) {
3982
- if (options.selectMode && !values.length && newValue.length > 0) {
3983
- controller.$setViewValue(controller.$viewValue.substring(0, controller.$viewValue.length - 1));
3984
- return;
3985
- }
3986
- if (values.length > limit) values = values.slice(0, limit);
3987
- var isVisible = typeahead.$isVisible();
3988
- isVisible && typeahead.update(values);
3989
- if (values.length === 1 && values[0].value === newValue) return;
3990
- !isVisible && typeahead.update(values);
3991
- controller.$render();
3992
- });
3987
+ if (!scope.hasOwnProperty('title')) {
3988
+ scope.title = '';
3989
+ }
3990
+ attr.$observe('title', function(newValue) {
3991
+ if (angular.isDefined(newValue) || !scope.hasOwnProperty('title')) {
3992
+ var oldValue = scope.title;
3993
+ scope.title = $sce.trustAsHtml(newValue);
3994
+ angular.isDefined(oldValue) && $$rAF(function() {
3995
+ tooltip && tooltip.$applyPlacement();
3996
+ });
3997
+ }
3993
3998
  });
3994
- controller.$formatters.push(function(modelValue) {
3995
- var displayValue = parsedOptions.displayValue(modelValue);
3996
- if (displayValue) return displayValue;
3997
- if (modelValue && typeof modelValue !== 'object') {
3998
- return modelValue;
3999
+ attr.bsTooltip && scope.$watch(attr.bsTooltip, function(newValue, oldValue) {
4000
+ if (angular.isObject(newValue)) {
4001
+ angular.extend(scope, newValue);
4002
+ } else {
4003
+ scope.title = newValue;
3999
4004
  }
4000
- return '';
4005
+ angular.isDefined(oldValue) && $$rAF(function() {
4006
+ tooltip && tooltip.$applyPlacement();
4007
+ });
4008
+ }, true);
4009
+ attr.bsShow && scope.$watch(attr.bsShow, function(newValue, oldValue) {
4010
+ if (!tooltip || !angular.isDefined(newValue)) return;
4011
+ if (angular.isString(newValue)) newValue = !!newValue.match(/true|,?(tooltip),?/i);
4012
+ newValue === true ? tooltip.show() : tooltip.hide();
4001
4013
  });
4002
- controller.$render = function() {
4003
- if (controller.$isEmpty(controller.$viewValue)) return element.val('');
4004
- var index = typeahead.$getIndex(controller.$modelValue);
4005
- var selected = angular.isDefined(index) ? typeahead.$scope.$matches[index].label : controller.$viewValue;
4006
- selected = angular.isObject(selected) ? parsedOptions.displayValue(selected) : selected;
4007
- var value = selected ? selected.toString().replace(/<(?:.|\n)*?>/gm, '') : '';
4008
- element.val(options.trimValue === false ? value : value.trim());
4009
- };
4014
+ attr.bsEnabled && scope.$watch(attr.bsEnabled, function(newValue, oldValue) {
4015
+ if (!tooltip || !angular.isDefined(newValue)) return;
4016
+ if (angular.isString(newValue)) newValue = !!newValue.match(/true|1|,?(tooltip),?/i);
4017
+ newValue === false ? tooltip.setEnabled(false) : tooltip.setEnabled(true);
4018
+ });
4019
+ attr.viewport && scope.$watch(attr.viewport, function(newValue) {
4020
+ if (!tooltip || !angular.isDefined(newValue)) return;
4021
+ tooltip.setViewport(newValue);
4022
+ });
4023
+ var tooltip = $tooltip(element, options);
4010
4024
  scope.$on('$destroy', function() {
4011
- if (typeahead) typeahead.destroy();
4025
+ if (tooltip) tooltip.destroy();
4012
4026
  options = null;
4013
- typeahead = null;
4027
+ tooltip = null;
4014
4028
  });
4015
4029
  }
4016
4030
  };