rails-angular-strap 2.2.4 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: f0cf5183956d9649fbad3fb56bf9daf07aa2adb8
4
- data.tar.gz: 4ed70de6da32890f92ab44bae0b7fe2a54f121ed
3
+ metadata.gz: 6ca08ade997b9fb83f4092112a2291ed1aee89f8
4
+ data.tar.gz: 57db25fd39a7dbcfa85541abc893da50bbbecdd7
5
5
  SHA512:
6
- metadata.gz: a490f753fa7a66d8a55cd6b0ed872f3e41e68e471c3e2899f4e71e50aa8406ebc97689973cc42a290e8d52264b65d1a08d76763768c37e77fc558cdf6b848ebc
7
- data.tar.gz: d122914afee30813eb486e434e4f69d2a6fe968867083e79a7a7a670c3f904f5f75f88120d6851be8716ebc7c01c66a201aefa9630ba5786eef8095de278d73f
6
+ metadata.gz: bf407af6a715b1db44a4d02df012824b675b1afa83cd7629e68d07a2d6d2c5cb1b99f2c87262a6072aacd432660cd4acbbd76091bac14c0fc1745de95f2261a2
7
+ data.tar.gz: 7dcf773a32489c2693dc1009628ad942dd3d3a1ecff17773ff13dd3ce4be1f03e128155bc8e8e1eb7f690cf71caf87bc4559d34ad51996a1ffff9407acbc793b
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.2.4
21
+ Current version of AngularStrap - 2.3.0
@@ -1,3 +1,3 @@
1
1
  module AngularStrapRails
2
- VERSION = "2.2.4"
2
+ VERSION = "2.3.0"
3
3
  end
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * angular-strap
3
- * @version v2.2.4 - 2015-05-28
3
+ * @version v2.3.0 - 2015-07-12
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
@@ -188,7 +188,7 @@
188
188
  prefixClass: 'alert',
189
189
  prefixEvent: 'alert',
190
190
  placement: null,
191
- template: 'alert/alert.tpl.html',
191
+ templateUrl: 'alert/alert.tpl.html',
192
192
  container: false,
193
193
  element: null,
194
194
  backdrop: false,
@@ -231,7 +231,7 @@
231
231
  element: element,
232
232
  show: false
233
233
  };
234
- angular.forEach([ 'template', 'placement', 'keyboard', 'html', 'container', 'animation', 'duration', 'dismissable' ], function(key) {
234
+ angular.forEach([ 'template', 'templateUrl', 'controller', 'controllerAs', 'placement', 'keyboard', 'html', 'container', 'animation', 'duration', 'dismissable' ], function(key) {
235
235
  if (angular.isDefined(attr[key])) options[key] = attr[key];
236
236
  });
237
237
  var falseValueRegExp = /^(false|0|)$/i;
@@ -269,7 +269,7 @@
269
269
  prefixClass: 'aside',
270
270
  prefixEvent: 'aside',
271
271
  placement: 'right',
272
- template: 'aside/aside.tpl.html',
272
+ templateUrl: 'aside/aside.tpl.html',
273
273
  contentTemplate: false,
274
274
  container: false,
275
275
  element: null,
@@ -298,7 +298,7 @@
298
298
  element: element,
299
299
  show: false
300
300
  };
301
- angular.forEach([ 'template', 'contentTemplate', 'placement', 'backdrop', 'keyboard', 'html', 'container', 'animation' ], function(key) {
301
+ angular.forEach([ 'template', 'templateUrl', 'controller', 'controllerAs', 'contentTemplate', 'placement', 'backdrop', 'keyboard', 'html', 'container', 'animation' ], function(key) {
302
302
  if (angular.isDefined(attr[key])) options[key] = attr[key];
303
303
  });
304
304
  var falseValueRegExp = /^(false|0|)$/i;
@@ -327,6 +327,125 @@
327
327
  }
328
328
  };
329
329
  } ]);
330
+ angular.module('mgcrea.ngStrap.button', []).provider('$button', function() {
331
+ var defaults = this.defaults = {
332
+ activeClass: 'active',
333
+ toggleEvent: 'click'
334
+ };
335
+ this.$get = function() {
336
+ return {
337
+ defaults: defaults
338
+ };
339
+ };
340
+ }).directive('bsCheckboxGroup', function() {
341
+ return {
342
+ restrict: 'A',
343
+ require: 'ngModel',
344
+ compile: function postLink(element, attr) {
345
+ element.attr('data-toggle', 'buttons');
346
+ element.removeAttr('ng-model');
347
+ var children = element[0].querySelectorAll('input[type="checkbox"]');
348
+ angular.forEach(children, function(child) {
349
+ var childEl = angular.element(child);
350
+ childEl.attr('bs-checkbox', '');
351
+ childEl.attr('ng-model', attr.ngModel + '.' + childEl.attr('value'));
352
+ });
353
+ }
354
+ };
355
+ }).directive('bsCheckbox', [ '$button', '$$rAF', function($button, $$rAF) {
356
+ var defaults = $button.defaults;
357
+ var constantValueRegExp = /^(true|false|\d+)$/;
358
+ return {
359
+ restrict: 'A',
360
+ require: 'ngModel',
361
+ link: function postLink(scope, element, attr, controller) {
362
+ var options = defaults;
363
+ var isInput = element[0].nodeName === 'INPUT';
364
+ var activeElement = isInput ? element.parent() : element;
365
+ var trueValue = angular.isDefined(attr.trueValue) ? attr.trueValue : true;
366
+ if (constantValueRegExp.test(attr.trueValue)) {
367
+ trueValue = scope.$eval(attr.trueValue);
368
+ }
369
+ var falseValue = angular.isDefined(attr.falseValue) ? attr.falseValue : false;
370
+ if (constantValueRegExp.test(attr.falseValue)) {
371
+ falseValue = scope.$eval(attr.falseValue);
372
+ }
373
+ var hasExoticValues = typeof trueValue !== 'boolean' || typeof falseValue !== 'boolean';
374
+ if (hasExoticValues) {
375
+ controller.$parsers.push(function(viewValue) {
376
+ return viewValue ? trueValue : falseValue;
377
+ });
378
+ controller.$formatters.push(function(modelValue) {
379
+ return angular.equals(modelValue, trueValue);
380
+ });
381
+ scope.$watch(attr.ngModel, function(newValue, oldValue) {
382
+ controller.$render();
383
+ });
384
+ }
385
+ controller.$render = function() {
386
+ var isActive = angular.equals(controller.$modelValue, trueValue);
387
+ $$rAF(function() {
388
+ if (isInput) element[0].checked = isActive;
389
+ activeElement.toggleClass(options.activeClass, isActive);
390
+ });
391
+ };
392
+ element.bind(options.toggleEvent, function() {
393
+ scope.$apply(function() {
394
+ if (!isInput) {
395
+ controller.$setViewValue(!activeElement.hasClass('active'));
396
+ }
397
+ if (!hasExoticValues) {
398
+ controller.$render();
399
+ }
400
+ });
401
+ });
402
+ }
403
+ };
404
+ } ]).directive('bsRadioGroup', function() {
405
+ return {
406
+ restrict: 'A',
407
+ require: 'ngModel',
408
+ compile: function postLink(element, attr) {
409
+ element.attr('data-toggle', 'buttons');
410
+ element.removeAttr('ng-model');
411
+ var children = element[0].querySelectorAll('input[type="radio"]');
412
+ angular.forEach(children, function(child) {
413
+ angular.element(child).attr('bs-radio', '');
414
+ angular.element(child).attr('ng-model', attr.ngModel);
415
+ });
416
+ }
417
+ };
418
+ }).directive('bsRadio', [ '$button', '$$rAF', function($button, $$rAF) {
419
+ var defaults = $button.defaults;
420
+ var constantValueRegExp = /^(true|false|\d+)$/;
421
+ return {
422
+ restrict: 'A',
423
+ require: 'ngModel',
424
+ link: function postLink(scope, element, attr, controller) {
425
+ var options = defaults;
426
+ var isInput = element[0].nodeName === 'INPUT';
427
+ var activeElement = isInput ? element.parent() : element;
428
+ var value;
429
+ attr.$observe('value', function(v) {
430
+ value = constantValueRegExp.test(v) ? scope.$eval(v) : v;
431
+ controller.$render();
432
+ });
433
+ controller.$render = function() {
434
+ var isActive = angular.equals(controller.$modelValue, value);
435
+ $$rAF(function() {
436
+ if (isInput) element[0].checked = isActive;
437
+ activeElement.toggleClass(options.activeClass, isActive);
438
+ });
439
+ };
440
+ element.bind(options.toggleEvent, function() {
441
+ scope.$apply(function() {
442
+ controller.$setViewValue(value);
443
+ controller.$render();
444
+ });
445
+ });
446
+ }
447
+ };
448
+ } ]);
330
449
  angular.module('mgcrea.ngStrap.collapse', []).provider('$collapse', function() {
331
450
  var defaults = this.defaults = {
332
451
  animation: 'am-collapse',
@@ -343,7 +462,9 @@
343
462
  });
344
463
  var falseValueRegExp = /^(false|0|)$/i;
345
464
  angular.forEach([ 'disallowToggle', 'startCollapsed', 'allowMultiple' ], function(key) {
346
- if (angular.isDefined($attrs[key]) && falseValueRegExp.test($attrs[key])) self.$options[key] = false;
465
+ if (angular.isDefined($attrs[key]) && falseValueRegExp.test($attrs[key])) {
466
+ self.$options[key] = false;
467
+ }
347
468
  });
348
469
  self.$toggles = [];
349
470
  self.$targets = [];
@@ -463,7 +584,7 @@
463
584
  bsCollapseCtrl.$unregisterToggle(element);
464
585
  });
465
586
  element.on('click', function() {
466
- var index = attrs.bsCollapseToggle || bsCollapseCtrl.$toggles.indexOf(element);
587
+ var index = attrs.bsCollapseToggle && attrs.bsCollapseToggle !== 'bs-collapse-toggle' ? attrs.bsCollapseToggle : bsCollapseCtrl.$toggles.indexOf(element);
467
588
  bsCollapseCtrl.$setActive(index * 1);
468
589
  scope.$apply();
469
590
  });
@@ -508,7 +629,7 @@
508
629
  animation: 'am-fade',
509
630
  prefixClass: 'datepicker',
510
631
  placement: 'bottom-left',
511
- template: 'datepicker/datepicker.tpl.html',
632
+ templateUrl: 'datepicker/datepicker.tpl.html',
512
633
  trigger: 'focus',
513
634
  container: false,
514
635
  keyboard: true,
@@ -712,10 +833,9 @@
712
833
  require: 'ngModel',
713
834
  link: function postLink(scope, element, attr, controller) {
714
835
  var options = {
715
- scope: scope,
716
- controller: controller
836
+ scope: scope
717
837
  };
718
- angular.forEach([ 'placement', 'container', 'delay', 'trigger', 'html', 'animation', 'template', 'autoclose', 'dateType', 'dateFormat', 'timezone', 'modelDateFormat', 'dayFormat', 'strictFormat', 'startWeek', 'startDate', 'useNative', 'lang', 'startView', 'minView', 'iconLeft', 'iconRight', 'daysOfWeekDisabled', 'id', 'prefixClass', 'prefixEvent' ], function(key) {
838
+ angular.forEach([ 'template', 'templateUrl', 'controller', 'controllerAs', 'placement', 'container', 'delay', 'trigger', 'html', 'animation', 'autoclose', 'dateType', 'dateFormat', 'timezone', 'modelDateFormat', 'dayFormat', 'strictFormat', 'startWeek', 'startDate', 'useNative', 'lang', 'startView', 'minView', 'iconLeft', 'iconRight', 'daysOfWeekDisabled', 'id', 'prefixClass', 'prefixEvent' ], function(key) {
719
839
  if (angular.isDefined(attr[key])) options[key] = attr[key];
720
840
  });
721
841
  var falseValueRegExp = /^(false|0|)$/i;
@@ -1059,7 +1179,7 @@
1059
1179
  prefixClass: 'dropdown',
1060
1180
  prefixEvent: 'dropdown',
1061
1181
  placement: 'bottom-left',
1062
- template: 'dropdown/dropdown.tpl.html',
1182
+ templateUrl: 'dropdown/dropdown.tpl.html',
1063
1183
  trigger: 'click',
1064
1184
  container: false,
1065
1185
  keyboard: true,
@@ -1126,7 +1246,7 @@
1126
1246
  var options = {
1127
1247
  scope: scope
1128
1248
  };
1129
- angular.forEach([ 'placement', 'container', 'delay', 'trigger', 'keyboard', 'html', 'animation', 'template', 'id' ], function(key) {
1249
+ angular.forEach([ 'template', 'templateUrl', 'controller', 'controllerAs', 'placement', 'container', 'delay', 'trigger', 'keyboard', 'html', 'animation', 'id' ], function(key) {
1130
1250
  if (angular.isDefined(attr[key])) options[key] = attr[key];
1131
1251
  });
1132
1252
  var falseValueRegExp = /^(false|0|)$/i;
@@ -1150,125 +1270,86 @@
1150
1270
  }
1151
1271
  };
1152
1272
  } ]);
1153
- angular.module('mgcrea.ngStrap.button', []).provider('$button', function() {
1154
- var defaults = this.defaults = {
1155
- activeClass: 'active',
1156
- toggleEvent: 'click'
1157
- };
1158
- this.$get = function() {
1159
- return {
1160
- defaults: defaults
1161
- };
1162
- };
1163
- }).directive('bsCheckboxGroup', function() {
1164
- return {
1165
- restrict: 'A',
1166
- require: 'ngModel',
1167
- compile: function postLink(element, attr) {
1168
- element.attr('data-toggle', 'buttons');
1169
- element.removeAttr('ng-model');
1170
- var children = element[0].querySelectorAll('input[type="checkbox"]');
1171
- angular.forEach(children, function(child) {
1172
- var childEl = angular.element(child);
1173
- childEl.attr('bs-checkbox', '');
1174
- childEl.attr('ng-model', attr.ngModel + '.' + childEl.attr('value'));
1175
- });
1273
+ angular.module('mgcrea.ngStrap.core', []).service('$bsCompiler', bsCompilerService);
1274
+ function bsCompilerService($q, $http, $injector, $compile, $controller, $templateCache) {
1275
+ this.compile = function(options) {
1276
+ if (options.template && /\.html$/.test(options.template)) {
1277
+ console.warn('Deprecated use of `template` option to pass a file. Please use the `templateUrl` option instead.');
1278
+ options.templateUrl = options.template;
1279
+ options.template = '';
1176
1280
  }
1177
- };
1178
- }).directive('bsCheckbox', [ '$button', '$$rAF', function($button, $$rAF) {
1179
- var defaults = $button.defaults;
1180
- var constantValueRegExp = /^(true|false|\d+)$/;
1181
- return {
1182
- restrict: 'A',
1183
- require: 'ngModel',
1184
- link: function postLink(scope, element, attr, controller) {
1185
- var options = defaults;
1186
- var isInput = element[0].nodeName === 'INPUT';
1187
- var activeElement = isInput ? element.parent() : element;
1188
- var trueValue = angular.isDefined(attr.trueValue) ? attr.trueValue : true;
1189
- if (constantValueRegExp.test(attr.trueValue)) {
1190
- trueValue = scope.$eval(attr.trueValue);
1191
- }
1192
- var falseValue = angular.isDefined(attr.falseValue) ? attr.falseValue : false;
1193
- if (constantValueRegExp.test(attr.falseValue)) {
1194
- falseValue = scope.$eval(attr.falseValue);
1195
- }
1196
- var hasExoticValues = typeof trueValue !== 'boolean' || typeof falseValue !== 'boolean';
1197
- if (hasExoticValues) {
1198
- controller.$parsers.push(function(viewValue) {
1199
- return viewValue ? trueValue : falseValue;
1200
- });
1201
- controller.$formatters.push(function(modelValue) {
1202
- return angular.equals(modelValue, trueValue);
1203
- });
1204
- scope.$watch(attr.ngModel, function(newValue, oldValue) {
1205
- controller.$render();
1206
- });
1281
+ var templateUrl = options.templateUrl;
1282
+ var template = options.template || '';
1283
+ var controller = options.controller;
1284
+ var controllerAs = options.controllerAs;
1285
+ var resolve = angular.copy(options.resolve || {});
1286
+ var locals = angular.copy(options.locals || {});
1287
+ var transformTemplate = options.transformTemplate || angular.identity;
1288
+ var bindToController = options.bindToController;
1289
+ angular.forEach(resolve, function(value, key) {
1290
+ if (angular.isString(value)) {
1291
+ resolve[key] = $injector.get(value);
1292
+ } else {
1293
+ resolve[key] = $injector.invoke(value);
1207
1294
  }
1208
- controller.$render = function() {
1209
- var isActive = angular.equals(controller.$modelValue, trueValue);
1210
- $$rAF(function() {
1211
- if (isInput) element[0].checked = isActive;
1212
- activeElement.toggleClass(options.activeClass, isActive);
1213
- });
1214
- };
1215
- element.bind(options.toggleEvent, function() {
1216
- scope.$apply(function() {
1217
- if (!isInput) {
1218
- controller.$setViewValue(!activeElement.hasClass('active'));
1219
- }
1220
- if (!hasExoticValues) {
1221
- controller.$render();
1222
- }
1223
- });
1224
- });
1295
+ });
1296
+ angular.extend(resolve, locals);
1297
+ if (templateUrl) {
1298
+ resolve.$template = fetchTemplate(templateUrl);
1299
+ } else {
1300
+ resolve.$template = $q.when(template);
1225
1301
  }
1226
- };
1227
- } ]).directive('bsRadioGroup', function() {
1228
- return {
1229
- restrict: 'A',
1230
- require: 'ngModel',
1231
- compile: function postLink(element, attr) {
1232
- element.attr('data-toggle', 'buttons');
1233
- element.removeAttr('ng-model');
1234
- var children = element[0].querySelectorAll('input[type="radio"]');
1235
- angular.forEach(children, function(child) {
1236
- angular.element(child).attr('bs-radio', '');
1237
- angular.element(child).attr('ng-model', attr.ngModel);
1302
+ if (options.contentTemplate) {
1303
+ resolve.$template = $q.all([ resolve.$template, fetchTemplate(options.contentTemplate) ]).then(function(templates) {
1304
+ var templateEl = angular.element(templates[0]);
1305
+ var contentEl = findElement('[ng-bind="content"]', templateEl[0]).removeAttr('ng-bind').html(templates[1]);
1306
+ if (!options.templateUrl) contentEl.next().remove();
1307
+ return templateEl[0].outerHTML;
1238
1308
  });
1239
1309
  }
1240
- };
1241
- }).directive('bsRadio', [ '$button', '$$rAF', function($button, $$rAF) {
1242
- var defaults = $button.defaults;
1243
- var constantValueRegExp = /^(true|false|\d+)$/;
1244
- return {
1245
- restrict: 'A',
1246
- require: 'ngModel',
1247
- link: function postLink(scope, element, attr, controller) {
1248
- var options = defaults;
1249
- var isInput = element[0].nodeName === 'INPUT';
1250
- var activeElement = isInput ? element.parent() : element;
1251
- var value;
1252
- attr.$observe('value', function(v) {
1253
- value = constantValueRegExp.test(v) ? scope.$eval(v) : v;
1254
- controller.$render();
1255
- });
1256
- controller.$render = function() {
1257
- var isActive = angular.equals(controller.$modelValue, value);
1258
- $$rAF(function() {
1259
- if (isInput) element[0].checked = isActive;
1260
- activeElement.toggleClass(options.activeClass, isActive);
1261
- });
1310
+ return $q.all(resolve).then(function(locals) {
1311
+ var template = transformTemplate(locals.$template);
1312
+ if (options.html) {
1313
+ template = template.replace(/ng-bind="/gi, 'ng-bind-html="');
1314
+ }
1315
+ var element = angular.element('<div>').html(template.trim()).contents();
1316
+ var linkFn = $compile(element);
1317
+ return {
1318
+ locals: locals,
1319
+ element: element,
1320
+ link: function link(scope) {
1321
+ locals.$scope = scope;
1322
+ if (controller) {
1323
+ var invokeCtrl = $controller(controller, locals, true);
1324
+ if (bindToController) {
1325
+ angular.extend(invokeCtrl.instance, locals);
1326
+ }
1327
+ var ctrl = angular.isObject(invokeCtrl) ? invokeCtrl : invokeCtrl();
1328
+ element.data('$ngControllerController', ctrl);
1329
+ element.children().data('$ngControllerController', ctrl);
1330
+ if (controllerAs) {
1331
+ scope[controllerAs] = ctrl;
1332
+ }
1333
+ }
1334
+ return linkFn.apply(null, arguments);
1335
+ }
1262
1336
  };
1263
- element.bind(options.toggleEvent, function() {
1264
- scope.$apply(function() {
1265
- controller.$setViewValue(value);
1266
- controller.$render();
1267
- });
1268
- });
1269
- }
1337
+ });
1270
1338
  };
1271
- } ]);
1339
+ function findElement(query, element) {
1340
+ return angular.element((element || document).querySelectorAll(query));
1341
+ }
1342
+ var fetchPromises = {};
1343
+ function fetchTemplate(template) {
1344
+ if (fetchPromises[template]) return fetchPromises[template];
1345
+ return fetchPromises[template] = $http.get(template, {
1346
+ cache: $templateCache
1347
+ }).then(function(res) {
1348
+ return res.data;
1349
+ });
1350
+ }
1351
+ }
1352
+ bsCompilerService.$inject = [ '$q', '$http', '$injector', '$compile', '$controller', '$templateCache' ];
1272
1353
  angular.module('mgcrea.ngStrap.helpers.dateFormatter', []).service('$dateFormatter', [ '$locale', 'dateFilter', function($locale, dateFilter) {
1273
1354
  this.getDefaultLocale = function() {
1274
1355
  return $locale.id;
@@ -1430,7 +1511,9 @@
1430
1511
  yy: function(value) {
1431
1512
  return this.setFullYear(2e3 + 1 * value);
1432
1513
  },
1433
- y: proto.setFullYear
1514
+ y: function(value) {
1515
+ return 1 * value <= 50 && value.length === 2 ? this.setFullYear(2e3 + 1 * value) : this.setFullYear(1 * value);
1516
+ }
1434
1517
  };
1435
1518
  var regex, setMap;
1436
1519
  $dateParser.init = function() {
@@ -1717,8 +1800,17 @@
1717
1800
  valuesFn = $parse(match[7]);
1718
1801
  };
1719
1802
  $parseOptions.valuesFn = function(scope, controller) {
1720
- return $q.when(valuesFn(scope, controller)).then(function(values) {
1721
- $parseOptions.$values = values ? parseValues(values, scope) : {};
1803
+ var valuesPromise;
1804
+ try {
1805
+ valuesPromise = valuesFn(scope, controller);
1806
+ } catch (err) {
1807
+ valuesPromise = [];
1808
+ }
1809
+ return $q.when(valuesPromise).then(function(values) {
1810
+ if (!angular.isArray(values)) {
1811
+ values = [];
1812
+ }
1813
+ $parseOptions.$values = values.length ? parseValues(values, scope) : [];
1722
1814
  return $parseOptions.$values;
1723
1815
  });
1724
1816
  };
@@ -1764,14 +1856,15 @@
1764
1856
  raf.supported = rafSupported;
1765
1857
  return raf;
1766
1858
  } ]);
1767
- angular.module('mgcrea.ngStrap.modal', [ 'mgcrea.ngStrap.helpers.dimensions' ]).provider('$modal', function() {
1859
+ angular.module('mgcrea.ngStrap.modal', [ 'mgcrea.ngStrap.core', 'mgcrea.ngStrap.helpers.dimensions' ]).provider('$modal', function() {
1768
1860
  var defaults = this.defaults = {
1769
1861
  animation: 'am-fade',
1770
1862
  backdropAnimation: 'am-fade',
1771
1863
  prefixClass: 'modal',
1772
1864
  prefixEvent: 'modal',
1773
1865
  placement: 'top',
1774
- template: 'modal/modal.tpl.html',
1866
+ templateUrl: 'modal/modal.tpl.html',
1867
+ template: '',
1775
1868
  contentTemplate: false,
1776
1869
  container: false,
1777
1870
  element: null,
@@ -1780,16 +1873,15 @@
1780
1873
  html: false,
1781
1874
  show: true
1782
1875
  };
1783
- this.$get = [ '$window', '$rootScope', '$compile', '$q', '$templateCache', '$http', '$animate', '$timeout', '$sce', 'dimensions', function($window, $rootScope, $compile, $q, $templateCache, $http, $animate, $timeout, $sce, dimensions) {
1876
+ this.$get = [ '$window', '$rootScope', '$bsCompiler', '$q', '$templateCache', '$http', '$animate', '$timeout', '$sce', 'dimensions', function($window, $rootScope, $bsCompiler, $q, $templateCache, $http, $animate, $timeout, $sce, dimensions) {
1784
1877
  var forEach = angular.forEach;
1785
1878
  var trim = String.prototype.trim;
1786
1879
  var requestAnimationFrame = $window.requestAnimationFrame || $window.setTimeout;
1787
1880
  var bodyElement = angular.element($window.document.body);
1788
- var htmlReplaceRegExp = /ng-bind="/gi;
1789
1881
  function ModalFactory(config) {
1790
1882
  var $modal = {};
1791
1883
  var options = $modal.$options = angular.extend({}, defaults, config);
1792
- $modal.$promise = fetchTemplate(options.template);
1884
+ var promise = $modal.$promise = $bsCompiler.compile(options);
1793
1885
  var scope = $modal.$scope = options.scope && options.scope.$new() || $rootScope.$new();
1794
1886
  if (!options.element && !options.container) {
1795
1887
  options.container = 'body';
@@ -1814,17 +1906,7 @@
1814
1906
  });
1815
1907
  };
1816
1908
  $modal.$isShown = scope.$isShown = false;
1817
- if (options.contentTemplate) {
1818
- $modal.$promise = $modal.$promise.then(function(template) {
1819
- var templateEl = angular.element(template);
1820
- return fetchTemplate(options.contentTemplate).then(function(contentTemplate) {
1821
- var contentEl = findElement('[ng-bind="content"]', templateEl[0]).removeAttr('ng-bind').html(contentTemplate);
1822
- if (!config.template) contentEl.next().remove();
1823
- return templateEl[0].outerHTML;
1824
- });
1825
- });
1826
- }
1827
- var modalLinker, modalElement;
1909
+ var compileData, modalElement, modalScope;
1828
1910
  var backdropElement = angular.element('<div class="' + options.prefixClass + '-backdrop"/>');
1829
1911
  backdropElement.css({
1830
1912
  position: 'fixed',
@@ -1834,11 +1916,8 @@
1834
1916
  right: '0px',
1835
1917
  'z-index': 1038
1836
1918
  });
1837
- $modal.$promise.then(function(template) {
1838
- if (angular.isObject(template)) template = template.data;
1839
- if (options.html) template = template.replace(htmlReplaceRegExp, 'ng-bind-html="');
1840
- template = trim.apply(template);
1841
- modalLinker = $compile(template);
1919
+ promise.then(function(data) {
1920
+ compileData = data;
1842
1921
  $modal.init();
1843
1922
  });
1844
1923
  $modal.init = function() {
@@ -1849,10 +1928,7 @@
1849
1928
  }
1850
1929
  };
1851
1930
  $modal.destroy = function() {
1852
- if (modalElement) {
1853
- modalElement.remove();
1854
- modalElement = null;
1855
- }
1931
+ destroyModalElement();
1856
1932
  if (backdropElement) {
1857
1933
  backdropElement.remove();
1858
1934
  backdropElement = null;
@@ -1874,7 +1950,9 @@
1874
1950
  after = options.element;
1875
1951
  }
1876
1952
  }
1877
- modalElement = $modal.$element = modalLinker(scope, function(clonedElement, scope) {});
1953
+ if (modalElement) destroyModalElement();
1954
+ modalScope = $modal.$scope.$new();
1955
+ modalElement = $modal.$element = compileData.link(modalScope, function(clonedElement, scope) {});
1878
1956
  if (scope.$emit(options.prefixEvent + '.show.before', $modal).defaultPrevented) {
1879
1957
  return;
1880
1958
  }
@@ -1905,14 +1983,8 @@
1905
1983
  if (options.animation) {
1906
1984
  bodyElement.addClass(options.prefixClass + '-with-' + options.animation);
1907
1985
  }
1908
- if (options.backdrop) {
1909
- modalElement.on('click', hideOnBackdropClick);
1910
- backdropElement.on('click', hideOnBackdropClick);
1911
- backdropElement.on('wheel', preventEventDefault);
1912
- }
1913
- if (options.keyboard) {
1914
- modalElement.on('keyup', $modal.$onKeyUp);
1915
- }
1986
+ bindBackdropEvents();
1987
+ bindKeyboardEvents();
1916
1988
  };
1917
1989
  function enterAnimateCallback() {
1918
1990
  scope.$emit(options.prefixEvent + '.show', $modal);
@@ -1932,14 +2004,8 @@
1932
2004
  }
1933
2005
  $modal.$isShown = scope.$isShown = false;
1934
2006
  safeDigest(scope);
1935
- if (options.backdrop) {
1936
- modalElement.off('click', hideOnBackdropClick);
1937
- backdropElement.off('click', hideOnBackdropClick);
1938
- backdropElement.off('wheel', preventEventDefault);
1939
- }
1940
- if (options.keyboard) {
1941
- modalElement.off('keyup', $modal.$onKeyUp);
1942
- }
2007
+ unbindBackdropEvents();
2008
+ unbindKeyboardEvents();
1943
2009
  };
1944
2010
  function leaveAnimateCallback() {
1945
2011
  scope.$emit(options.prefixEvent + '.hide', $modal);
@@ -1960,6 +2026,30 @@
1960
2026
  evt.stopPropagation();
1961
2027
  }
1962
2028
  };
2029
+ function bindBackdropEvents() {
2030
+ if (options.backdrop) {
2031
+ modalElement.on('click', hideOnBackdropClick);
2032
+ backdropElement.on('click', hideOnBackdropClick);
2033
+ backdropElement.on('wheel', preventEventDefault);
2034
+ }
2035
+ }
2036
+ function unbindBackdropEvents() {
2037
+ if (options.backdrop) {
2038
+ modalElement.off('click', hideOnBackdropClick);
2039
+ backdropElement.off('click', hideOnBackdropClick);
2040
+ backdropElement.off('wheel', preventEventDefault);
2041
+ }
2042
+ }
2043
+ function bindKeyboardEvents() {
2044
+ if (options.keyboard) {
2045
+ modalElement.on('keyup', $modal.$onKeyUp);
2046
+ }
2047
+ }
2048
+ function unbindKeyboardEvents() {
2049
+ if (options.keyboard) {
2050
+ modalElement.off('keyup', $modal.$onKeyUp);
2051
+ }
2052
+ }
1963
2053
  function hideOnBackdropClick(evt) {
1964
2054
  if (evt.target !== evt.currentTarget) return;
1965
2055
  options.backdrop === 'static' ? $modal.focus() : $modal.hide();
@@ -1967,6 +2057,20 @@
1967
2057
  function preventEventDefault(evt) {
1968
2058
  evt.preventDefault();
1969
2059
  }
2060
+ function destroyModalElement() {
2061
+ if ($modal.$isShown && modalElement !== null) {
2062
+ unbindBackdropEvents();
2063
+ unbindKeyboardEvents();
2064
+ }
2065
+ if (modalScope) {
2066
+ modalScope.$destroy();
2067
+ modalScope = null;
2068
+ }
2069
+ if (modalElement) {
2070
+ modalElement.remove();
2071
+ modalElement = $modal.$element = null;
2072
+ }
2073
+ }
1970
2074
  return $modal;
1971
2075
  }
1972
2076
  function safeDigest(scope) {
@@ -1975,15 +2079,6 @@
1975
2079
  function findElement(query, element) {
1976
2080
  return angular.element((element || document).querySelectorAll(query));
1977
2081
  }
1978
- var fetchPromises = {};
1979
- function fetchTemplate(template) {
1980
- if (fetchPromises[template]) return fetchPromises[template];
1981
- return fetchPromises[template] = $http.get(template, {
1982
- cache: $templateCache
1983
- }).then(function(res) {
1984
- return res.data;
1985
- });
1986
- }
1987
2082
  return ModalFactory;
1988
2083
  } ];
1989
2084
  }).directive('bsModal', [ '$window', '$sce', '$modal', function($window, $sce, $modal) {
@@ -1996,7 +2091,7 @@
1996
2091
  element: element,
1997
2092
  show: false
1998
2093
  };
1999
- angular.forEach([ 'template', 'contentTemplate', 'placement', 'backdrop', 'keyboard', 'html', 'container', 'animation', 'id', 'prefixEvent', 'prefixClass' ], function(key) {
2094
+ angular.forEach([ 'template', 'templateUrl', 'controller', 'controllerAs', 'contentTemplate', 'controller', 'placement', 'backdrop', 'keyboard', 'html', 'container', 'animation', 'id', 'prefixEvent', 'prefixClass' ], function(key) {
2000
2095
  if (angular.isDefined(attr[key])) options[key] = attr[key];
2001
2096
  });
2002
2097
  var falseValueRegExp = /^(false|0|)$/i;
@@ -2066,90 +2161,6 @@
2066
2161
  }
2067
2162
  };
2068
2163
  } ]);
2069
- angular.module('mgcrea.ngStrap.popover', [ 'mgcrea.ngStrap.tooltip' ]).provider('$popover', function() {
2070
- var defaults = this.defaults = {
2071
- animation: 'am-fade',
2072
- customClass: '',
2073
- container: false,
2074
- target: false,
2075
- placement: 'right',
2076
- template: 'popover/popover.tpl.html',
2077
- contentTemplate: false,
2078
- trigger: 'click',
2079
- keyboard: true,
2080
- html: false,
2081
- title: '',
2082
- content: '',
2083
- delay: 0,
2084
- autoClose: false
2085
- };
2086
- this.$get = [ '$tooltip', function($tooltip) {
2087
- function PopoverFactory(element, config) {
2088
- var options = angular.extend({}, defaults, config);
2089
- var $popover = $tooltip(element, options);
2090
- if (options.content) {
2091
- $popover.$scope.content = options.content;
2092
- }
2093
- return $popover;
2094
- }
2095
- return PopoverFactory;
2096
- } ];
2097
- }).directive('bsPopover', [ '$window', '$sce', '$popover', function($window, $sce, $popover) {
2098
- var requestAnimationFrame = $window.requestAnimationFrame || $window.setTimeout;
2099
- return {
2100
- restrict: 'EAC',
2101
- scope: true,
2102
- link: function postLink(scope, element, attr) {
2103
- var options = {
2104
- scope: scope
2105
- };
2106
- angular.forEach([ 'template', 'contentTemplate', 'placement', 'container', 'delay', 'trigger', 'html', 'animation', 'customClass', 'autoClose', 'id', 'prefixClass', 'prefixEvent' ], function(key) {
2107
- if (angular.isDefined(attr[key])) options[key] = attr[key];
2108
- });
2109
- var falseValueRegExp = /^(false|0|)$/i;
2110
- angular.forEach([ 'html', 'container', 'autoClose' ], function(key) {
2111
- if (angular.isDefined(attr[key]) && falseValueRegExp.test(attr[key])) options[key] = false;
2112
- });
2113
- var dataTarget = element.attr('data-target');
2114
- if (angular.isDefined(dataTarget)) {
2115
- if (falseValueRegExp.test(dataTarget)) options.target = false; else options.target = dataTarget;
2116
- }
2117
- angular.forEach([ 'title', 'content' ], function(key) {
2118
- attr[key] && attr.$observe(key, function(newValue, oldValue) {
2119
- scope[key] = $sce.trustAsHtml(newValue);
2120
- angular.isDefined(oldValue) && requestAnimationFrame(function() {
2121
- popover && popover.$applyPlacement();
2122
- });
2123
- });
2124
- });
2125
- attr.bsPopover && scope.$watch(attr.bsPopover, function(newValue, oldValue) {
2126
- if (angular.isObject(newValue)) {
2127
- angular.extend(scope, newValue);
2128
- } else {
2129
- scope.content = newValue;
2130
- }
2131
- angular.isDefined(oldValue) && requestAnimationFrame(function() {
2132
- popover && popover.$applyPlacement();
2133
- });
2134
- }, true);
2135
- attr.bsShow && scope.$watch(attr.bsShow, function(newValue, oldValue) {
2136
- if (!popover || !angular.isDefined(newValue)) return;
2137
- if (angular.isString(newValue)) newValue = !!newValue.match(/true|,?(popover),?/i);
2138
- newValue === true ? popover.show() : popover.hide();
2139
- });
2140
- attr.viewport && scope.$watch(attr.viewport, function(newValue) {
2141
- if (!popover || !angular.isDefined(newValue)) return;
2142
- popover.setViewport(newValue);
2143
- });
2144
- var popover = $popover(element, options);
2145
- scope.$on('$destroy', function() {
2146
- if (popover) popover.destroy();
2147
- options = null;
2148
- popover = null;
2149
- });
2150
- }
2151
- };
2152
- } ]);
2153
2164
  angular.module('mgcrea.ngStrap.scrollspy', [ 'mgcrea.ngStrap.helpers.debounce', 'mgcrea.ngStrap.helpers.dimensions' ]).provider('$scrollspy', function() {
2154
2165
  var spies = this.$$spies = {};
2155
2166
  var defaults = this.defaults = {
@@ -2329,7 +2340,7 @@
2329
2340
  prefixClass: 'select',
2330
2341
  prefixEvent: '$select',
2331
2342
  placement: 'bottom-left',
2332
- template: 'select/select.tpl.html',
2343
+ templateUrl: 'select/select.tpl.html',
2333
2344
  trigger: 'focus',
2334
2345
  container: false,
2335
2346
  keyboard: true,
@@ -2523,7 +2534,7 @@
2523
2534
  scope: scope,
2524
2535
  placeholder: defaults.placeholder
2525
2536
  };
2526
- angular.forEach([ 'placement', 'container', 'delay', 'trigger', 'keyboard', 'html', 'animation', 'template', 'placeholder', 'allNoneButtons', 'maxLength', 'maxLengthHtml', 'allText', 'noneText', 'iconCheckmark', 'autoClose', 'id', 'sort', 'caretHtml', 'prefixClass', 'prefixEvent' ], function(key) {
2537
+ angular.forEach([ 'template', 'templateUrl', 'controller', 'controllerAs', 'placement', 'container', 'delay', 'trigger', 'keyboard', 'html', 'animation', 'placeholder', 'allNoneButtons', 'maxLength', 'maxLengthHtml', 'allText', 'noneText', 'iconCheckmark', 'autoClose', 'id', 'sort', 'caretHtml', 'prefixClass', 'prefixEvent' ], function(key) {
2527
2538
  if (angular.isDefined(attr[key])) options[key] = attr[key];
2528
2539
  });
2529
2540
  var falseValueRegExp = /^(false|0|)$/i;
@@ -2584,6 +2595,90 @@
2584
2595
  }
2585
2596
  };
2586
2597
  } ]);
2598
+ angular.module('mgcrea.ngStrap.popover', [ 'mgcrea.ngStrap.tooltip' ]).provider('$popover', function() {
2599
+ var defaults = this.defaults = {
2600
+ animation: 'am-fade',
2601
+ customClass: '',
2602
+ container: false,
2603
+ target: false,
2604
+ placement: 'right',
2605
+ templateUrl: 'popover/popover.tpl.html',
2606
+ contentTemplate: false,
2607
+ trigger: 'click',
2608
+ keyboard: true,
2609
+ html: false,
2610
+ title: '',
2611
+ content: '',
2612
+ delay: 0,
2613
+ autoClose: false
2614
+ };
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;
2623
+ }
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
2634
+ };
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;
2645
+ }
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();
2651
+ });
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;
2659
+ }
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
+ } ]);
2587
2682
  angular.module('mgcrea.ngStrap.tab', []).provider('$tab', function() {
2588
2683
  var defaults = this.defaults = {
2589
2684
  animation: 'am-fade',
@@ -2712,1029 +2807,1014 @@
2712
2807
  }
2713
2808
  };
2714
2809
  } ]);
2715
- angular.module('mgcrea.ngStrap.timepicker', [ 'mgcrea.ngStrap.helpers.dateParser', 'mgcrea.ngStrap.helpers.dateFormatter', 'mgcrea.ngStrap.tooltip' ]).provider('$timepicker', function() {
2810
+ angular.module('mgcrea.ngStrap.tooltip', [ 'mgcrea.ngStrap.core', 'mgcrea.ngStrap.helpers.dimensions' ]).provider('$tooltip', function() {
2716
2811
  var defaults = this.defaults = {
2717
2812
  animation: 'am-fade',
2718
- prefixClass: 'timepicker',
2719
- placement: 'bottom-left',
2720
- template: 'timepicker/timepicker.tpl.html',
2721
- trigger: 'focus',
2813
+ customClass: '',
2814
+ prefixClass: 'tooltip',
2815
+ prefixEvent: 'tooltip',
2722
2816
  container: false,
2723
- keyboard: true,
2817
+ target: false,
2818
+ placement: 'top',
2819
+ templateUrl: 'tooltip/tooltip.tpl.html',
2820
+ template: '',
2821
+ contentTemplate: false,
2822
+ trigger: 'hover focus',
2823
+ keyboard: false,
2724
2824
  html: false,
2825
+ show: false,
2826
+ title: '',
2827
+ type: '',
2725
2828
  delay: 0,
2726
- useNative: true,
2727
- timeType: 'date',
2728
- timeFormat: 'shortTime',
2729
- timezone: null,
2730
- modelTimeFormat: null,
2731
- autoclose: false,
2732
- minTime: -Infinity,
2733
- maxTime: +Infinity,
2734
- length: 5,
2735
- hourStep: 1,
2736
- minuteStep: 5,
2737
- secondStep: 5,
2738
- roundDisplay: false,
2739
- iconUp: 'glyphicon glyphicon-chevron-up',
2740
- iconDown: 'glyphicon glyphicon-chevron-down',
2741
- arrowBehavior: 'pager'
2742
- };
2743
- this.$get = [ '$window', '$document', '$rootScope', '$sce', '$dateFormatter', '$tooltip', '$timeout', function($window, $document, $rootScope, $sce, $dateFormatter, $tooltip, $timeout) {
2744
- var bodyEl = angular.element($window.document.body);
2745
- var isNative = /(ip(a|o)d|iphone|android)/gi.test($window.navigator.userAgent);
2746
- var isTouch = 'createTouch' in $window.document && isNative;
2747
- if (!defaults.lang) defaults.lang = $dateFormatter.getDefaultLocale();
2748
- function timepickerFactory(element, controller, config) {
2749
- var $timepicker = $tooltip(element, angular.extend({}, defaults, config));
2750
- var parentScope = config.scope;
2751
- var options = $timepicker.$options;
2752
- var scope = $timepicker.$scope;
2753
- var lang = options.lang;
2754
- var formatDate = function(date, format, timezone) {
2755
- return $dateFormatter.formatDate(date, format, lang, timezone);
2756
- };
2757
- function floorMinutes(time) {
2758
- var coeff = 1e3 * 60 * options.minuteStep;
2759
- return new Date(Math.floor(time.getTime() / coeff) * coeff);
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];
2760
2853
  }
2761
- var selectedIndex = 0;
2762
- var defaultDate = options.roundDisplay ? floorMinutes(new Date()) : new Date();
2763
- var startDate = controller.$dateValue || defaultDate;
2764
- var viewDate = {
2765
- hour: startDate.getHours(),
2766
- meridian: startDate.getHours() < 12,
2767
- minute: startDate.getMinutes(),
2768
- second: startDate.getSeconds(),
2769
- millisecond: startDate.getMilliseconds()
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
+ });
2770
2862
  };
2771
- var format = $dateFormatter.getDatetimeFormat(options.timeFormat, lang);
2772
- 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);
2773
- scope.$iconUp = options.iconUp;
2774
- scope.$iconDown = options.iconDown;
2775
- scope.$select = function(date, index) {
2776
- $timepicker.select(date, index);
2863
+ scope.$hide = function() {
2864
+ scope.$$postDigest(function() {
2865
+ $tooltip.hide();
2866
+ });
2777
2867
  };
2778
- scope.$moveIndex = function(value, index) {
2779
- $timepicker.$moveIndex(value, index);
2868
+ scope.$show = function() {
2869
+ scope.$$postDigest(function() {
2870
+ $tooltip.show();
2871
+ });
2780
2872
  };
2781
- scope.$switchMeridian = function(date) {
2782
- $timepicker.switchMeridian(date);
2873
+ scope.$toggle = function() {
2874
+ scope.$$postDigest(function() {
2875
+ $tooltip.toggle();
2876
+ });
2783
2877
  };
2784
- $timepicker.update = function(date) {
2785
- if (angular.isDate(date) && !isNaN(date.getTime())) {
2786
- $timepicker.$date = date;
2787
- angular.extend(viewDate, {
2788
- hour: date.getHours(),
2789
- minute: date.getMinutes(),
2790
- second: date.getSeconds(),
2791
- millisecond: date.getMilliseconds()
2792
- });
2793
- $timepicker.$build();
2794
- } else if (!$timepicker.$isBuilt) {
2795
- $timepicker.$build();
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
+ };
2796
2891
  }
2797
- };
2798
- $timepicker.select = function(date, index, keep) {
2799
- if (!controller.$dateValue || isNaN(controller.$dateValue.getTime())) controller.$dateValue = new Date(1970, 0, 1);
2800
- if (!angular.isDate(date)) date = new Date(date);
2801
- 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());
2802
- controller.$setViewValue(angular.copy(controller.$dateValue));
2803
- controller.$render();
2804
- if (options.autoclose && !keep) {
2805
- $timeout(function() {
2806
- $timepicker.hide(true);
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);
2898
+ }
2899
+ bindTriggerEvents();
2900
+ if (options.target) {
2901
+ options.target = angular.isElement(options.target) ? options.target : findElement(options.target);
2902
+ }
2903
+ if (options.show) {
2904
+ scope.$$postDigest(function() {
2905
+ options.trigger === 'focus' ? element[0].focus() : $tooltip.show();
2807
2906
  });
2808
2907
  }
2809
2908
  };
2810
- $timepicker.switchMeridian = function(date) {
2811
- if (!controller.$dateValue || isNaN(controller.$dateValue.getTime())) {
2812
- return;
2909
+ $tooltip.destroy = function() {
2910
+ unbindTriggerEvents();
2911
+ destroyTipElement();
2912
+ scope.$destroy();
2913
+ };
2914
+ $tooltip.enter = function() {
2915
+ clearTimeout(timeout);
2916
+ hoverState = 'in';
2917
+ if (!options.delay || !options.delay.show) {
2918
+ return $tooltip.show();
2813
2919
  }
2814
- var hours = (date || controller.$dateValue).getHours();
2815
- controller.$dateValue.setHours(hours < 12 ? hours + 12 : hours - 12);
2816
- controller.$setViewValue(angular.copy(controller.$dateValue));
2817
- controller.$render();
2920
+ timeout = setTimeout(function() {
2921
+ if (hoverState === 'in') $tooltip.show();
2922
+ }, options.delay.show);
2818
2923
  };
2819
- $timepicker.$build = function() {
2820
- var i, midIndex = scope.midIndex = parseInt(options.length / 2, 10);
2821
- var hours = [], hour;
2822
- for (i = 0; i < options.length; i++) {
2823
- hour = new Date(1970, 0, 1, viewDate.hour - (midIndex - i) * options.hourStep);
2824
- hours.push({
2825
- date: hour,
2826
- label: formatDate(hour, hoursFormat),
2827
- selected: $timepicker.$date && $timepicker.$isSelected(hour, 0),
2828
- disabled: $timepicker.$isDisabled(hour, 0)
2829
- });
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
+ }
2935
+ } else {
2936
+ parent = null;
2937
+ after = element;
2830
2938
  }
2831
- var minutes = [], minute;
2832
- for (i = 0; i < options.length; i++) {
2833
- minute = new Date(1970, 0, 1, 0, viewDate.minute - (midIndex - i) * options.minuteStep);
2834
- minutes.push({
2835
- date: minute,
2836
- label: formatDate(minute, minutesFormat),
2837
- selected: $timepicker.$date && $timepicker.$isSelected(minute, 1),
2838
- disabled: $timepicker.$isDisabled(minute, 1)
2839
- });
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);
2840
2960
  }
2841
- var seconds = [], second;
2842
- for (i = 0; i < options.length; i++) {
2843
- second = new Date(1970, 0, 1, 0, 0, viewDate.second - (midIndex - i) * options.secondStep);
2844
- seconds.push({
2845
- date: second,
2846
- label: formatDate(second, secondsFormat),
2847
- selected: $timepicker.$date && $timepicker.$isSelected(second, 2),
2848
- disabled: $timepicker.$isDisabled(second, 2)
2961
+ safeDigest(scope);
2962
+ $$rAF(function() {
2963
+ if (tipElement) tipElement.css({
2964
+ visibility: 'visible'
2849
2965
  });
2850
- }
2851
- var rows = [];
2852
- for (i = 0; i < options.length; i++) {
2853
- if (showSeconds) {
2854
- rows.push([ hours[i], minutes[i], seconds[i] ]);
2855
- } else {
2856
- rows.push([ hours[i], minutes[i] ]);
2966
+ });
2967
+ if (options.keyboard) {
2968
+ if (options.trigger !== 'focus') {
2969
+ $tooltip.focus();
2857
2970
  }
2971
+ bindKeyboardEvents();
2858
2972
  }
2859
- scope.rows = rows;
2860
- scope.showSeconds = showSeconds;
2861
- scope.showAM = showAM;
2862
- scope.isAM = ($timepicker.$date || hours[midIndex].date).getHours() < 12;
2863
- scope.timeSeparator = timeSeparator;
2864
- $timepicker.$isBuilt = true;
2865
- };
2866
- $timepicker.$isSelected = function(date, index) {
2867
- if (!$timepicker.$date) return false; else if (index === 0) {
2868
- return date.getHours() === $timepicker.$date.getHours();
2869
- } else if (index === 1) {
2870
- return date.getMinutes() === $timepicker.$date.getMinutes();
2871
- } else if (index === 2) {
2872
- return date.getSeconds() === $timepicker.$date.getSeconds();
2973
+ if (options.autoClose) {
2974
+ bindAutoCloseEvents();
2873
2975
  }
2874
2976
  };
2875
- $timepicker.$isDisabled = function(date, index) {
2876
- var selectedTime;
2877
- if (index === 0) {
2878
- selectedTime = date.getTime() + viewDate.minute * 6e4 + viewDate.second * 1e3;
2879
- } else if (index === 1) {
2880
- selectedTime = date.getTime() + viewDate.hour * 36e5 + viewDate.second * 1e3;
2881
- } else if (index === 2) {
2882
- selectedTime = date.getTime() + viewDate.hour * 36e5 + viewDate.minute * 6e4;
2977
+ function enterAnimateCallback() {
2978
+ scope.$emit(options.prefixEvent + '.show', $tooltip);
2979
+ }
2980
+ $tooltip.leave = function() {
2981
+ clearTimeout(timeout);
2982
+ hoverState = 'out';
2983
+ if (!options.delay || !options.delay.hide) {
2984
+ return $tooltip.hide();
2883
2985
  }
2884
- return selectedTime < options.minTime * 1 || selectedTime > options.maxTime * 1;
2885
- };
2886
- scope.$arrowAction = function(value, index) {
2887
- if (options.arrowBehavior === 'picker') {
2888
- $timepicker.$setTimeByStep(value, index);
2986
+ timeout = setTimeout(function() {
2987
+ if (hoverState === 'out') {
2988
+ $tooltip.hide();
2989
+ }
2990
+ }, options.delay.hide);
2991
+ };
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);
2889
3001
  } else {
2890
- $timepicker.$moveIndex(value, index);
3002
+ $animate.leave(tipElement).then(leaveAnimateCallback);
2891
3003
  }
2892
- };
2893
- $timepicker.$setTimeByStep = function(value, index) {
2894
- var newDate = new Date($timepicker.$date);
2895
- var hours = newDate.getHours(), hoursLength = formatDate(newDate, hoursFormat).length;
2896
- var minutes = newDate.getMinutes(), minutesLength = formatDate(newDate, minutesFormat).length;
2897
- var seconds = newDate.getSeconds(), secondsLength = formatDate(newDate, secondsFormat).length;
2898
- if (index === 0) {
2899
- newDate.setHours(hours - parseInt(options.hourStep, 10) * value);
2900
- } else if (index === 1) {
2901
- newDate.setMinutes(minutes - parseInt(options.minuteStep, 10) * value);
2902
- } else if (index === 2) {
2903
- newDate.setSeconds(seconds - parseInt(options.secondStep, 10) * value);
3004
+ $tooltip.$isShown = scope.$isShown = false;
3005
+ safeDigest(scope);
3006
+ if (options.keyboard && tipElement !== null) {
3007
+ unbindKeyboardEvents();
2904
3008
  }
2905
- $timepicker.select(newDate, index, true);
2906
- };
2907
- $timepicker.$moveIndex = function(value, index) {
2908
- var targetDate;
2909
- if (index === 0) {
2910
- targetDate = new Date(1970, 0, 1, viewDate.hour + value * options.length, viewDate.minute, viewDate.second);
2911
- angular.extend(viewDate, {
2912
- hour: targetDate.getHours()
2913
- });
2914
- } else if (index === 1) {
2915
- targetDate = new Date(1970, 0, 1, viewDate.hour, viewDate.minute + value * options.length * options.minuteStep, viewDate.second);
2916
- angular.extend(viewDate, {
2917
- minute: targetDate.getMinutes()
2918
- });
2919
- } else if (index === 2) {
2920
- targetDate = new Date(1970, 0, 1, viewDate.hour, viewDate.minute, viewDate.second + value * options.length * options.secondStep);
2921
- angular.extend(viewDate, {
2922
- second: targetDate.getSeconds()
2923
- });
3009
+ if (options.autoClose && tipElement !== null) {
3010
+ unbindAutoCloseEvents();
2924
3011
  }
2925
- $timepicker.$build();
2926
3012
  };
2927
- $timepicker.$onMouseDown = function(evt) {
2928
- if (evt.target.nodeName.toLowerCase() !== 'input') evt.preventDefault();
2929
- evt.stopPropagation();
2930
- if (isTouch) {
2931
- var targetEl = angular.element(evt.target);
2932
- if (targetEl[0].nodeName.toLowerCase() !== 'button') {
2933
- targetEl = targetEl.parent();
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();
2934
3018
  }
2935
- targetEl.triggerHandler('click');
3019
+ destroyTipElement();
2936
3020
  }
3021
+ }
3022
+ $tooltip.toggle = function() {
3023
+ $tooltip.$isShown ? $tooltip.leave() : $tooltip.enter();
2937
3024
  };
2938
- $timepicker.$onKeyDown = function(evt) {
2939
- if (!/(38|37|39|40|13)/.test(evt.keyCode) || evt.shiftKey || evt.altKey) return;
2940
- evt.preventDefault();
2941
- evt.stopPropagation();
2942
- if (evt.keyCode === 13) return $timepicker.hide(true);
2943
- var newDate = new Date($timepicker.$date);
2944
- var hours = newDate.getHours(), hoursLength = formatDate(newDate, hoursFormat).length;
2945
- var minutes = newDate.getMinutes(), minutesLength = formatDate(newDate, minutesFormat).length;
2946
- var seconds = newDate.getSeconds(), secondsLength = formatDate(newDate, secondsFormat).length;
2947
- var sepLength = 1;
2948
- var lateralMove = /(37|39)/.test(evt.keyCode);
2949
- var count = 2 + showSeconds * 1 + showAM * 1;
2950
- if (lateralMove) {
2951
- if (evt.keyCode === 37) selectedIndex = selectedIndex < 1 ? count - 1 : selectedIndex - 1; else if (evt.keyCode === 39) selectedIndex = selectedIndex < count - 1 ? selectedIndex + 1 : 0;
3025
+ $tooltip.focus = function() {
3026
+ tipElement[0].focus();
3027
+ };
3028
+ $tooltip.setEnabled = function(isEnabled) {
3029
+ options.bsEnabled = isEnabled;
3030
+ };
3031
+ $tooltip.setViewport = function(viewport) {
3032
+ options.viewport = viewport;
3033
+ };
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;
2952
3039
  }
2953
- var selectRange = [ 0, hoursLength ];
2954
- var incr = 0;
2955
- if (evt.keyCode === 38) incr = -1;
2956
- if (evt.keyCode === 40) incr = +1;
2957
- var isSeconds = selectedIndex === 2 && showSeconds;
2958
- var isMeridian = selectedIndex === 2 && !showSeconds || selectedIndex === 3 && showSeconds;
2959
- if (selectedIndex === 0) {
2960
- newDate.setHours(hours + incr * parseInt(options.hourStep, 10));
2961
- hoursLength = formatDate(newDate, hoursFormat).length;
2962
- selectRange = [ 0, hoursLength ];
2963
- } else if (selectedIndex === 1) {
2964
- newDate.setMinutes(minutes + incr * parseInt(options.minuteStep, 10));
2965
- minutesLength = formatDate(newDate, minutesFormat).length;
2966
- selectRange = [ hoursLength + sepLength, minutesLength ];
2967
- } else if (isSeconds) {
2968
- newDate.setSeconds(seconds + incr * parseInt(options.secondStep, 10));
2969
- secondsLength = formatDate(newDate, secondsFormat).length;
2970
- selectRange = [ hoursLength + sepLength + minutesLength + sepLength, secondsLength ];
2971
- } else if (isMeridian) {
2972
- if (!lateralMove) $timepicker.switchMeridian();
2973
- selectRange = [ hoursLength + sepLength + minutesLength + sepLength + (secondsLength + sepLength) * showSeconds, 2 ];
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);
2974
3057
  }
2975
- $timepicker.select(newDate, selectedIndex, true);
2976
- createSelection(selectRange[0], selectRange[1]);
2977
- parentScope.$digest();
3058
+ var tipPosition = getCalculatedOffset(placement, elementPosition, tipWidth, tipHeight);
3059
+ applyPlacement(tipPosition, placement);
2978
3060
  };
2979
- function createSelection(start, length) {
2980
- var end = start + length;
2981
- if (element[0].createTextRange) {
2982
- var selRange = element[0].createTextRange();
2983
- selRange.collapse(true);
2984
- selRange.moveStart('character', start);
2985
- selRange.moveEnd('character', end);
2986
- selRange.select();
2987
- } else if (element[0].setSelectionRange) {
2988
- element[0].setSelectionRange(start, end);
2989
- } else if (angular.isUndefined(element[0].selectionStart)) {
2990
- element[0].selectionStart = start;
2991
- element[0].selectionEnd = end;
3061
+ $tooltip.$onKeyUp = function(evt) {
3062
+ if (evt.which === 27 && $tooltip.$isShown) {
3063
+ $tooltip.hide();
3064
+ evt.stopPropagation();
2992
3065
  }
3066
+ };
3067
+ $tooltip.$onFocusKeyUp = function(evt) {
3068
+ if (evt.which === 27) {
3069
+ element[0].blur();
3070
+ evt.stopPropagation();
3071
+ }
3072
+ };
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
+ });
2993
3089
  }
2994
- function focusElement() {
2995
- element[0].focus();
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
+ }
2996
3102
  }
2997
- var _init = $timepicker.init;
2998
- $timepicker.init = function() {
2999
- if (isNative && options.useNative) {
3000
- element.prop('type', 'time');
3001
- element.css('-webkit-appearance', 'textfield');
3002
- return;
3003
- } else if (isTouch) {
3004
- element.prop('type', 'text');
3005
- element.attr('readonly', 'true');
3006
- element.on('click', focusElement);
3103
+ function bindKeyboardEvents() {
3104
+ if (options.trigger !== 'focus') {
3105
+ tipElement.on('keyup', $tooltip.$onKeyUp);
3106
+ } else {
3107
+ element.on('keyup', $tooltip.$onFocusKeyUp);
3007
3108
  }
3008
- _init();
3009
- };
3010
- var _destroy = $timepicker.destroy;
3011
- $timepicker.destroy = function() {
3012
- if (isNative && options.useNative) {
3013
- element.off('click', focusElement);
3109
+ }
3110
+ function unbindKeyboardEvents() {
3111
+ if (options.trigger !== 'focus') {
3112
+ tipElement.off('keyup', $tooltip.$onKeyUp);
3113
+ } else {
3114
+ element.off('keyup', $tooltip.$onFocusKeyUp);
3014
3115
  }
3015
- _destroy();
3016
- };
3017
- var _show = $timepicker.show;
3018
- $timepicker.show = function() {
3019
- _show();
3116
+ }
3117
+ var _autoCloseEventsBinded = false;
3118
+ function bindAutoCloseEvents() {
3020
3119
  $timeout(function() {
3021
- $timepicker.$element && $timepicker.$element.on(isTouch ? 'touchstart' : 'mousedown', $timepicker.$onMouseDown);
3022
- if (options.keyboard) {
3023
- element && element.on('keydown', $timepicker.$onKeyDown);
3024
- }
3120
+ tipElement.on('click', stopEventPropagation);
3121
+ $body.on('click', $tooltip.hide);
3122
+ _autoCloseEventsBinded = true;
3025
3123
  }, 0, false);
3026
- };
3027
- var _hide = $timepicker.hide;
3028
- $timepicker.hide = function(blur) {
3029
- if (!$timepicker.$isShown) return;
3030
- $timepicker.$element && $timepicker.$element.off(isTouch ? 'touchstart' : 'mousedown', $timepicker.$onMouseDown);
3031
- if (options.keyboard) {
3032
- element && element.off('keydown', $timepicker.$onKeyDown);
3124
+ }
3125
+ function unbindAutoCloseEvents() {
3126
+ if (_autoCloseEventsBinded) {
3127
+ tipElement.off('click', stopEventPropagation);
3128
+ $body.off('click', $tooltip.hide);
3129
+ _autoCloseEventsBinded = false;
3033
3130
  }
3034
- _hide(blur);
3035
- };
3036
- return $timepicker;
3037
- }
3038
- timepickerFactory.defaults = defaults;
3039
- return timepickerFactory;
3131
+ }
3132
+ function stopEventPropagation(event) {
3133
+ event.stopPropagation();
3134
+ }
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);
3159
+ }
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;
3215
+ }
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;
3242
+ }
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);
3247
+ }
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;
3256
+ }
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
+ }
3272
+ }
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
+ }
3285
+ if (options.keyboard) {
3286
+ unbindKeyboardEvents();
3287
+ }
3288
+ }
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;
3299
+ }
3300
+ function safeDigest(scope) {
3301
+ scope.$$phase || scope.$root && scope.$root.$$phase || scope.$digest();
3302
+ }
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;
3313
+ });
3314
+ }
3315
+ return TooltipFactory;
3040
3316
  } ];
3041
- }).directive('bsTimepicker', [ '$window', '$parse', '$q', '$dateFormatter', '$dateParser', '$timepicker', function($window, $parse, $q, $dateFormatter, $dateParser, $timepicker) {
3042
- var defaults = $timepicker.defaults;
3043
- var isNative = /(ip(a|o)d|iphone|android)/gi.test($window.navigator.userAgent);
3044
- var requestAnimationFrame = $window.requestAnimationFrame || $window.setTimeout;
3317
+ }).directive('bsTooltip', [ '$window', '$location', '$sce', '$tooltip', '$$rAF', function($window, $location, $sce, $tooltip, $$rAF) {
3045
3318
  return {
3046
3319
  restrict: 'EAC',
3047
- require: 'ngModel',
3048
- link: function postLink(scope, element, attr, controller) {
3320
+ scope: true,
3321
+ link: function postLink(scope, element, attr, transclusion) {
3049
3322
  var options = {
3050
- scope: scope,
3051
- controller: controller
3323
+ scope: scope
3052
3324
  };
3053
- angular.forEach([ 'placement', 'container', 'delay', 'trigger', 'keyboard', 'html', 'animation', 'template', 'autoclose', 'timeType', 'timeFormat', 'timezone', 'modelTimeFormat', 'useNative', 'hourStep', 'minuteStep', 'secondStep', 'length', 'arrowBehavior', 'iconUp', 'iconDown', 'roundDisplay', 'id', 'prefixClass', 'prefixEvent' ], function(key) {
3325
+ angular.forEach([ 'template', 'templateUrl', 'controller', 'controllerAs', 'contentTemplate', 'placement', 'container', 'delay', 'trigger', 'html', 'animation', 'backdropAnimation', 'type', 'customClass', 'id' ], function(key) {
3054
3326
  if (angular.isDefined(attr[key])) options[key] = attr[key];
3055
3327
  });
3056
3328
  var falseValueRegExp = /^(false|0|)$/i;
3057
- angular.forEach([ 'html', 'container', 'autoclose', 'useNative', 'roundDisplay' ], function(key) {
3329
+ angular.forEach([ 'html', 'container' ], function(key) {
3058
3330
  if (angular.isDefined(attr[key]) && falseValueRegExp.test(attr[key])) options[key] = false;
3059
3331
  });
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();
3345
+ });
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
+ });
3357
+ }, true);
3060
3358
  attr.bsShow && scope.$watch(attr.bsShow, function(newValue, oldValue) {
3061
- if (!timepicker || !angular.isDefined(newValue)) return;
3062
- if (angular.isString(newValue)) newValue = !!newValue.match(/true|,?(timepicker),?/i);
3063
- newValue === true ? timepicker.show() : timepicker.hide();
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();
3064
3362
  });
3065
- if (isNative && (options.useNative || defaults.useNative)) options.timeFormat = 'HH:mm';
3066
- var timepicker = $timepicker(element, controller, options);
3067
- options = timepicker.$options;
3068
- var lang = options.lang;
3069
- var formatDate = function(date, format, timezone) {
3070
- return $dateFormatter.formatDate(date, format, lang, timezone);
3071
- };
3072
- var dateParser = $dateParser({
3073
- format: options.timeFormat,
3074
- lang: lang
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);
3075
3367
  });
3076
- angular.forEach([ 'minTime', 'maxTime' ], function(key) {
3077
- angular.isDefined(attr[key]) && attr.$observe(key, function(newValue) {
3078
- timepicker.$options[key] = dateParser.getTimeForAttribute(key, newValue);
3079
- !isNaN(timepicker.$options[key]) && timepicker.$build();
3080
- validateAgainstMinMaxTime(controller.$dateValue);
3081
- });
3368
+ attr.viewport && scope.$watch(attr.viewport, function(newValue) {
3369
+ if (!tooltip || !angular.isDefined(newValue)) return;
3370
+ tooltip.setViewport(newValue);
3082
3371
  });
3083
- scope.$watch(attr.ngModel, function(newValue, oldValue) {
3084
- timepicker.update(controller.$dateValue);
3085
- }, true);
3086
- function validateAgainstMinMaxTime(parsedTime) {
3087
- if (!angular.isDate(parsedTime)) return;
3088
- var isMinValid = isNaN(options.minTime) || new Date(parsedTime.getTime()).setFullYear(1970, 0, 1) >= options.minTime;
3089
- var isMaxValid = isNaN(options.maxTime) || new Date(parsedTime.getTime()).setFullYear(1970, 0, 1) <= options.maxTime;
3090
- var isValid = isMinValid && isMaxValid;
3091
- controller.$setValidity('date', isValid);
3092
- controller.$setValidity('min', isMinValid);
3093
- controller.$setValidity('max', isMaxValid);
3094
- if (!isValid) {
3095
- return;
3096
- }
3097
- controller.$dateValue = parsedTime;
3098
- }
3099
- controller.$parsers.unshift(function(viewValue) {
3100
- var date;
3101
- if (!viewValue) {
3102
- controller.$setValidity('date', true);
3103
- return null;
3104
- }
3105
- var parsedTime = angular.isDate(viewValue) ? viewValue : dateParser.parse(viewValue, controller.$dateValue);
3106
- if (!parsedTime || isNaN(parsedTime.getTime())) {
3107
- controller.$setValidity('date', false);
3108
- return;
3109
- } else {
3110
- validateAgainstMinMaxTime(parsedTime);
3111
- }
3112
- if (options.timeType === 'string') {
3113
- date = dateParser.timezoneOffsetAdjust(parsedTime, options.timezone, true);
3114
- return formatDate(date, options.modelTimeFormat || options.timeFormat);
3115
- }
3116
- date = dateParser.timezoneOffsetAdjust(controller.$dateValue, options.timezone, true);
3117
- if (options.timeType === 'number') {
3118
- return date.getTime();
3119
- } else if (options.timeType === 'unix') {
3120
- return date.getTime() / 1e3;
3121
- } else if (options.timeType === 'iso') {
3122
- return date.toISOString();
3123
- } else {
3124
- return new Date(date);
3125
- }
3126
- });
3127
- controller.$formatters.push(function(modelValue) {
3128
- var date;
3129
- if (angular.isUndefined(modelValue) || modelValue === null) {
3130
- date = NaN;
3131
- } else if (angular.isDate(modelValue)) {
3132
- date = modelValue;
3133
- } else if (options.timeType === 'string') {
3134
- date = dateParser.parse(modelValue, null, options.modelTimeFormat);
3135
- } else if (options.timeType === 'unix') {
3136
- date = new Date(modelValue * 1e3);
3137
- } else {
3138
- date = new Date(modelValue);
3139
- }
3140
- controller.$dateValue = dateParser.timezoneOffsetAdjust(date, options.timezone);
3141
- return getTimeFormattedString();
3142
- });
3143
- controller.$render = function() {
3144
- element.val(getTimeFormattedString());
3145
- };
3146
- function getTimeFormattedString() {
3147
- return !controller.$dateValue || isNaN(controller.$dateValue.getTime()) ? '' : formatDate(controller.$dateValue, options.timeFormat);
3148
- }
3372
+ var tooltip = $tooltip(element, options);
3149
3373
  scope.$on('$destroy', function() {
3150
- if (timepicker) timepicker.destroy();
3374
+ if (tooltip) tooltip.destroy();
3151
3375
  options = null;
3152
- timepicker = null;
3376
+ tooltip = null;
3153
3377
  });
3154
3378
  }
3155
3379
  };
3156
3380
  } ]);
3157
- angular.module('mgcrea.ngStrap.tooltip', [ 'mgcrea.ngStrap.helpers.dimensions' ]).provider('$tooltip', function() {
3381
+ angular.module('mgcrea.ngStrap.timepicker', [ 'mgcrea.ngStrap.helpers.dateParser', 'mgcrea.ngStrap.helpers.dateFormatter', 'mgcrea.ngStrap.tooltip' ]).provider('$timepicker', function() {
3158
3382
  var defaults = this.defaults = {
3159
3383
  animation: 'am-fade',
3160
- customClass: '',
3161
- prefixClass: 'tooltip',
3162
- prefixEvent: 'tooltip',
3384
+ prefixClass: 'timepicker',
3385
+ placement: 'bottom-left',
3386
+ templateUrl: 'timepicker/timepicker.tpl.html',
3387
+ trigger: 'focus',
3163
3388
  container: false,
3164
- target: false,
3165
- placement: 'top',
3166
- template: 'tooltip/tooltip.tpl.html',
3167
- contentTemplate: false,
3168
- trigger: 'hover focus',
3169
- keyboard: false,
3389
+ keyboard: true,
3170
3390
  html: false,
3171
- show: false,
3172
- title: '',
3173
- type: '',
3174
3391
  delay: 0,
3175
- autoClose: false,
3176
- bsEnabled: true,
3177
- viewport: {
3178
- selector: 'body',
3179
- padding: 0
3180
- }
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'
3181
3408
  };
3182
- this.$get = [ '$window', '$rootScope', '$compile', '$q', '$templateCache', '$http', '$animate', '$sce', 'dimensions', '$$rAF', '$timeout', function($window, $rootScope, $compile, $q, $templateCache, $http, $animate, $sce, dimensions, $$rAF, $timeout) {
3183
- var trim = String.prototype.trim;
3184
- var isTouch = 'createTouch' in $window.document;
3185
- var htmlReplaceRegExp = /ng-bind="/gi;
3186
- var $body = angular.element($window.document);
3187
- function TooltipFactory(element, config) {
3188
- var $tooltip = {};
3189
- var nodeName = element[0].nodeName.toLowerCase();
3190
- var options = $tooltip.$options = angular.extend({}, defaults, config);
3191
- $tooltip.$promise = fetchTemplate(options.template);
3192
- var scope = $tooltip.$scope = options.scope && options.scope.$new() || $rootScope.$new();
3193
- if (options.delay && angular.isString(options.delay)) {
3194
- var split = options.delay.split(',').map(parseFloat);
3195
- options.delay = split.length > 1 ? {
3196
- show: split[0],
3197
- hide: split[1]
3198
- } : split[0];
3199
- }
3200
- $tooltip.$id = options.id || element.attr('id') || '';
3201
- if (options.title) {
3202
- scope.title = $sce.trustAsHtml(options.title);
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);
3203
3426
  }
3204
- scope.$setEnabled = function(isEnabled) {
3205
- scope.$$postDigest(function() {
3206
- $tooltip.setEnabled(isEnabled);
3207
- });
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()
3208
3436
  };
3209
- scope.$hide = function() {
3210
- scope.$$postDigest(function() {
3211
- $tooltip.hide();
3212
- });
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);
3213
3443
  };
3214
- scope.$show = function() {
3215
- scope.$$postDigest(function() {
3216
- $tooltip.show();
3217
- });
3444
+ scope.$moveIndex = function(value, index) {
3445
+ $timepicker.$moveIndex(value, index);
3218
3446
  };
3219
- scope.$toggle = function() {
3220
- scope.$$postDigest(function() {
3221
- $tooltip.toggle();
3222
- });
3447
+ scope.$switchMeridian = function(date) {
3448
+ $timepicker.switchMeridian(date);
3223
3449
  };
3224
- $tooltip.$isShown = scope.$isShown = false;
3225
- var timeout, hoverState;
3226
- if (options.contentTemplate) {
3227
- $tooltip.$promise = $tooltip.$promise.then(function(template) {
3228
- var templateEl = angular.element(template);
3229
- return fetchTemplate(options.contentTemplate).then(function(contentTemplate) {
3230
- var contentEl = findElement('[ng-bind="content"]', templateEl[0]);
3231
- if (!contentEl.length) contentEl = findElement('[ng-bind="title"]', templateEl[0]);
3232
- contentEl.removeAttr('ng-bind').html(contentTemplate);
3233
- return templateEl[0].outerHTML;
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()
3234
3458
  });
3235
- });
3236
- }
3237
- var tipLinker, tipElement, tipTemplate, tipContainer, tipScope;
3238
- $tooltip.$promise.then(function(template) {
3239
- if (angular.isObject(template)) template = template.data;
3240
- if (options.html) template = template.replace(htmlReplaceRegExp, 'ng-bind-html="');
3241
- template = trim.apply(template);
3242
- tipTemplate = template;
3243
- tipLinker = $compile(template);
3244
- $tooltip.init();
3245
- });
3246
- $tooltip.init = function() {
3247
- if (options.delay && angular.isNumber(options.delay)) {
3248
- options.delay = {
3249
- show: options.delay,
3250
- hide: options.delay
3251
- };
3252
- }
3253
- if (options.container === 'self') {
3254
- tipContainer = element;
3255
- } else if (angular.isElement(options.container)) {
3256
- tipContainer = options.container;
3257
- } else if (options.container) {
3258
- tipContainer = findElement(options.container);
3259
- }
3260
- bindTriggerEvents();
3261
- if (options.target) {
3262
- options.target = angular.isElement(options.target) ? options.target : findElement(options.target);
3459
+ $timepicker.$build();
3460
+ } else if (!$timepicker.$isBuilt) {
3461
+ $timepicker.$build();
3263
3462
  }
3264
- if (options.show) {
3265
- scope.$$postDigest(function() {
3266
- options.trigger === 'focus' ? element[0].focus() : $tooltip.show();
3463
+ };
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);
3267
3473
  });
3268
3474
  }
3269
3475
  };
3270
- $tooltip.destroy = function() {
3271
- unbindTriggerEvents();
3272
- destroyTipElement();
3273
- scope.$destroy();
3274
- };
3275
- $tooltip.enter = function() {
3276
- clearTimeout(timeout);
3277
- hoverState = 'in';
3278
- if (!options.delay || !options.delay.show) {
3279
- return $tooltip.show();
3476
+ $timepicker.switchMeridian = function(date) {
3477
+ if (!controller.$dateValue || isNaN(controller.$dateValue.getTime())) {
3478
+ return;
3280
3479
  }
3281
- timeout = setTimeout(function() {
3282
- if (hoverState === 'in') $tooltip.show();
3283
- }, options.delay.show);
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();
3284
3484
  };
3285
- $tooltip.show = function() {
3286
- if (!options.bsEnabled || $tooltip.$isShown) return;
3287
- scope.$emit(options.prefixEvent + '.show.before', $tooltip);
3288
- var parent, after;
3289
- if (options.container) {
3290
- parent = tipContainer;
3291
- if (tipContainer[0].lastChild) {
3292
- after = angular.element(tipContainer[0].lastChild);
3293
- } else {
3294
- after = null;
3295
- }
3296
- } else {
3297
- parent = null;
3298
- after = element;
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
+ });
3299
3496
  }
3300
- if (tipElement) destroyTipElement();
3301
- tipScope = $tooltip.$scope.$new();
3302
- tipElement = $tooltip.$element = tipLinker(tipScope, function(clonedElement, scope) {});
3303
- tipElement.css({
3304
- top: '-9999px',
3305
- left: '-9999px',
3306
- right: 'auto',
3307
- display: 'block',
3308
- visibility: 'hidden'
3309
- });
3310
- if (options.animation) tipElement.addClass(options.animation);
3311
- if (options.type) tipElement.addClass(options.prefixClass + '-' + options.type);
3312
- if (options.customClass) tipElement.addClass(options.customClass);
3313
- after ? after.after(tipElement) : parent.prepend(tipElement);
3314
- $tooltip.$isShown = scope.$isShown = true;
3315
- safeDigest(scope);
3316
- $tooltip.$applyPlacement();
3317
- if (angular.version.minor <= 2) {
3318
- $animate.enter(tipElement, parent, after, enterAnimateCallback);
3319
- } else {
3320
- $animate.enter(tipElement, parent, after).then(enterAnimateCallback);
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)
3505
+ });
3321
3506
  }
3322
- safeDigest(scope);
3323
- $$rAF(function() {
3324
- if (tipElement) tipElement.css({
3325
- visibility: 'visible'
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)
3326
3515
  });
3327
- });
3328
- if (options.keyboard) {
3329
- if (options.trigger !== 'focus') {
3330
- $tooltip.focus();
3331
- }
3332
- bindKeyboardEvents();
3333
3516
  }
3334
- if (options.autoClose) {
3335
- bindAutoCloseEvents();
3517
+ var rows = [];
3518
+ for (i = 0; i < options.length; i++) {
3519
+ if (showSeconds) {
3520
+ rows.push([ hours[i], minutes[i], seconds[i] ]);
3521
+ } else {
3522
+ rows.push([ hours[i], minutes[i] ]);
3523
+ }
3336
3524
  }
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;
3337
3531
  };
3338
- function enterAnimateCallback() {
3339
- scope.$emit(options.prefixEvent + '.show', $tooltip);
3340
- }
3341
- $tooltip.leave = function() {
3342
- clearTimeout(timeout);
3343
- hoverState = 'out';
3344
- if (!options.delay || !options.delay.hide) {
3345
- return $tooltip.hide();
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();
3346
3539
  }
3347
- timeout = setTimeout(function() {
3348
- if (hoverState === 'out') {
3349
- $tooltip.hide();
3350
- }
3351
- }, options.delay.hide);
3352
3540
  };
3353
- var _blur;
3354
- var _tipToHide;
3355
- $tooltip.hide = function(blur) {
3356
- if (!$tooltip.$isShown) return;
3357
- scope.$emit(options.prefixEvent + '.hide.before', $tooltip);
3358
- _blur = blur;
3359
- _tipToHide = tipElement;
3360
- if (angular.version.minor <= 2) {
3361
- $animate.leave(tipElement, leaveAnimateCallback);
3362
- } else {
3363
- $animate.leave(tipElement).then(leaveAnimateCallback);
3364
- }
3365
- $tooltip.$isShown = scope.$isShown = false;
3366
- safeDigest(scope);
3367
- if (options.keyboard && tipElement !== null) {
3368
- unbindKeyboardEvents();
3369
- }
3370
- if (options.autoClose && tipElement !== null) {
3371
- unbindAutoCloseEvents();
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;
3372
3549
  }
3550
+ return selectedTime < options.minTime * 1 || selectedTime > options.maxTime * 1;
3373
3551
  };
3374
- function leaveAnimateCallback() {
3375
- scope.$emit(options.prefixEvent + '.hide', $tooltip);
3376
- if (tipElement === _tipToHide) {
3377
- if (_blur && options.trigger === 'focus') {
3378
- return element[0].blur();
3379
- }
3380
- destroyTipElement();
3552
+ scope.$arrowAction = function(value, index) {
3553
+ if (options.arrowBehavior === 'picker') {
3554
+ $timepicker.$setTimeByStep(value, index);
3555
+ } else {
3556
+ $timepicker.$moveIndex(value, index);
3381
3557
  }
3382
- }
3383
- $tooltip.toggle = function() {
3384
- $tooltip.$isShown ? $tooltip.leave() : $tooltip.enter();
3385
- };
3386
- $tooltip.focus = function() {
3387
- tipElement[0].focus();
3388
- };
3389
- $tooltip.setEnabled = function(isEnabled) {
3390
- options.bsEnabled = isEnabled;
3391
- };
3392
- $tooltip.setViewport = function(viewport) {
3393
- options.viewport = viewport;
3394
3558
  };
3395
- $tooltip.$applyPlacement = function() {
3396
- if (!tipElement) return;
3397
- var placement = options.placement, autoToken = /\s?auto?\s?/i, autoPlace = autoToken.test(placement);
3398
- if (autoPlace) {
3399
- placement = placement.replace(autoToken, '') || defaults.placement;
3400
- }
3401
- tipElement.addClass(options.placement);
3402
- var elementPosition = getPosition(), tipWidth = tipElement.prop('offsetWidth'), tipHeight = tipElement.prop('offsetHeight');
3403
- if (autoPlace) {
3404
- var originalPlacement = placement;
3405
- var container = options.container ? findElement(options.container) : element.parent();
3406
- var containerPosition = getPosition(container);
3407
- if (originalPlacement.indexOf('bottom') >= 0 && elementPosition.bottom + tipHeight > containerPosition.bottom) {
3408
- placement = originalPlacement.replace('bottom', 'top');
3409
- } else if (originalPlacement.indexOf('top') >= 0 && elementPosition.top - tipHeight < containerPosition.top) {
3410
- placement = originalPlacement.replace('top', 'bottom');
3411
- }
3412
- if ((originalPlacement === 'right' || originalPlacement === 'bottom-left' || originalPlacement === 'top-left') && elementPosition.right + tipWidth > containerPosition.width) {
3413
- placement = originalPlacement === 'right' ? 'left' : placement.replace('left', 'right');
3414
- } else if ((originalPlacement === 'left' || originalPlacement === 'bottom-right' || originalPlacement === 'top-right') && elementPosition.left - tipWidth < containerPosition.left) {
3415
- placement = originalPlacement === 'left' ? 'right' : placement.replace('right', 'left');
3416
- }
3417
- tipElement.removeClass(originalPlacement).addClass(placement);
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);
3418
3570
  }
3419
- var tipPosition = getCalculatedOffset(placement, elementPosition, tipWidth, tipHeight);
3420
- applyPlacement(tipPosition, placement);
3571
+ $timepicker.select(newDate, index, true);
3421
3572
  };
3422
- $tooltip.$onKeyUp = function(evt) {
3423
- if (evt.which === 27 && $tooltip.$isShown) {
3424
- $tooltip.hide();
3425
- evt.stopPropagation();
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
+ });
3426
3590
  }
3591
+ $timepicker.$build();
3427
3592
  };
3428
- $tooltip.$onFocusKeyUp = function(evt) {
3429
- if (evt.which === 27) {
3430
- element[0].blur();
3431
- evt.stopPropagation();
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();
3600
+ }
3601
+ targetEl.triggerHandler('click');
3432
3602
  }
3433
3603
  };
3434
- $tooltip.$onFocusElementMouseDown = function(evt) {
3604
+ $timepicker.$onKeyDown = function(evt) {
3605
+ if (!/(38|37|39|40|13)/.test(evt.keyCode) || evt.shiftKey || evt.altKey) return;
3435
3606
  evt.preventDefault();
3436
3607
  evt.stopPropagation();
3437
- $tooltip.$isShown ? element[0].blur() : element[0].focus();
3438
- };
3439
- function bindTriggerEvents() {
3440
- var triggers = options.trigger.split(' ');
3441
- angular.forEach(triggers, function(trigger) {
3442
- if (trigger === 'click') {
3443
- element.on('click', $tooltip.toggle);
3444
- } else if (trigger !== 'manual') {
3445
- element.on(trigger === 'hover' ? 'mouseenter' : 'focus', $tooltip.enter);
3446
- element.on(trigger === 'hover' ? 'mouseleave' : 'blur', $tooltip.leave);
3447
- nodeName === 'button' && trigger !== 'hover' && element.on(isTouch ? 'touchstart' : 'mousedown', $tooltip.$onFocusElementMouseDown);
3448
- }
3449
- });
3450
- }
3451
- function unbindTriggerEvents() {
3452
- var triggers = options.trigger.split(' ');
3453
- for (var i = triggers.length; i--; ) {
3454
- var trigger = triggers[i];
3455
- if (trigger === 'click') {
3456
- element.off('click', $tooltip.toggle);
3457
- } else if (trigger !== 'manual') {
3458
- element.off(trigger === 'hover' ? 'mouseenter' : 'focus', $tooltip.enter);
3459
- element.off(trigger === 'hover' ? 'mouseleave' : 'blur', $tooltip.leave);
3460
- nodeName === 'button' && trigger !== 'hover' && element.off(isTouch ? 'touchstart' : 'mousedown', $tooltip.$onFocusElementMouseDown);
3461
- }
3462
- }
3463
- }
3464
- function bindKeyboardEvents() {
3465
- if (options.trigger !== 'focus') {
3466
- tipElement.on('keyup', $tooltip.$onKeyUp);
3467
- } else {
3468
- element.on('keyup', $tooltip.$onFocusKeyUp);
3469
- }
3470
- }
3471
- function unbindKeyboardEvents() {
3472
- if (options.trigger !== 'focus') {
3473
- tipElement.off('keyup', $tooltip.$onKeyUp);
3474
- } else {
3475
- element.off('keyup', $tooltip.$onFocusKeyUp);
3476
- }
3477
- }
3478
- var _autoCloseEventsBinded = false;
3479
- function bindAutoCloseEvents() {
3480
- $timeout(function() {
3481
- tipElement.on('click', stopEventPropagation);
3482
- $body.on('click', $tooltip.hide);
3483
- _autoCloseEventsBinded = true;
3484
- }, 0, false);
3485
- }
3486
- function unbindAutoCloseEvents() {
3487
- if (_autoCloseEventsBinded) {
3488
- tipElement.off('click', stopEventPropagation);
3489
- $body.off('click', $tooltip.hide);
3490
- _autoCloseEventsBinded = false;
3491
- }
3492
- }
3493
- function stopEventPropagation(event) {
3494
- event.stopPropagation();
3495
- }
3496
- function getPosition($element) {
3497
- $element = $element || (options.target || element);
3498
- var el = $element[0], isBody = el.tagName === 'BODY';
3499
- var elRect = el.getBoundingClientRect();
3500
- var rect = {};
3501
- for (var p in elRect) {
3502
- rect[p] = elRect[p];
3503
- }
3504
- if (rect.width === null) {
3505
- rect = angular.extend({}, rect, {
3506
- width: elRect.right - elRect.left,
3507
- height: elRect.bottom - elRect.top
3508
- });
3509
- }
3510
- var elOffset = isBody ? {
3511
- top: 0,
3512
- left: 0
3513
- } : dimensions.offset(el), scroll = {
3514
- scroll: isBody ? document.documentElement.scrollTop || document.body.scrollTop : $element.prop('scrollTop') || 0
3515
- }, outerDims = isBody ? {
3516
- width: document.documentElement.clientWidth,
3517
- height: $window.innerHeight
3518
- } : null;
3519
- return angular.extend({}, rect, scroll, outerDims, elOffset);
3520
- }
3521
- function getCalculatedOffset(placement, position, actualWidth, actualHeight) {
3522
- var offset;
3523
- var split = placement.split('-');
3524
- switch (split[0]) {
3525
- case 'right':
3526
- offset = {
3527
- top: position.top + position.height / 2 - actualHeight / 2,
3528
- left: position.left + position.width
3529
- };
3530
- break;
3531
-
3532
- case 'bottom':
3533
- offset = {
3534
- top: position.top + position.height,
3535
- left: position.left + position.width / 2 - actualWidth / 2
3536
- };
3537
- break;
3538
-
3539
- case 'left':
3540
- offset = {
3541
- top: position.top + position.height / 2 - actualHeight / 2,
3542
- left: position.left - actualWidth
3543
- };
3544
- break;
3545
-
3546
- default:
3547
- offset = {
3548
- top: position.top - actualHeight,
3549
- left: position.left + position.width / 2 - actualWidth / 2
3550
- };
3551
- break;
3552
- }
3553
- if (!split[1]) {
3554
- return offset;
3555
- }
3556
- if (split[0] === 'top' || split[0] === 'bottom') {
3557
- switch (split[1]) {
3558
- case 'left':
3559
- offset.left = position.left;
3560
- break;
3561
-
3562
- case 'right':
3563
- offset.left = position.left + position.width - actualWidth;
3564
- }
3565
- } else if (split[0] === 'left' || split[0] === 'right') {
3566
- switch (split[1]) {
3567
- case 'top':
3568
- offset.top = position.top - actualHeight;
3569
- break;
3570
-
3571
- case 'bottom':
3572
- offset.top = position.top + position.height;
3573
- }
3574
- }
3575
- return offset;
3576
- }
3577
- function applyPlacement(offset, placement) {
3578
- var tip = tipElement[0], width = tip.offsetWidth, height = tip.offsetHeight;
3579
- var marginTop = parseInt(dimensions.css(tip, 'margin-top'), 10), marginLeft = parseInt(dimensions.css(tip, 'margin-left'), 10);
3580
- if (isNaN(marginTop)) marginTop = 0;
3581
- if (isNaN(marginLeft)) marginLeft = 0;
3582
- offset.top = offset.top + marginTop;
3583
- offset.left = offset.left + marginLeft;
3584
- dimensions.setOffset(tip, angular.extend({
3585
- using: function(props) {
3586
- tipElement.css({
3587
- top: Math.round(props.top) + 'px',
3588
- left: Math.round(props.left) + 'px',
3589
- right: ''
3590
- });
3591
- }
3592
- }, offset), 0);
3593
- var actualWidth = tip.offsetWidth, actualHeight = tip.offsetHeight;
3594
- if (placement === 'top' && actualHeight !== height) {
3595
- offset.top = offset.top + height - actualHeight;
3596
- }
3597
- if (/top-left|top-right|bottom-left|bottom-right/.test(placement)) return;
3598
- var delta = getViewportAdjustedDelta(placement, offset, actualWidth, actualHeight);
3599
- if (delta.left) {
3600
- offset.left += delta.left;
3601
- } else {
3602
- offset.top += delta.top;
3603
- }
3604
- dimensions.setOffset(tip, offset);
3605
- if (/top|right|bottom|left/.test(placement)) {
3606
- var isVertical = /top|bottom/.test(placement), arrowDelta = isVertical ? delta.left * 2 - width + actualWidth : delta.top * 2 - height + actualHeight, arrowOffsetPosition = isVertical ? 'offsetWidth' : 'offsetHeight';
3607
- replaceArrow(arrowDelta, tip[arrowOffsetPosition], isVertical);
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;
3608
3618
  }
3609
- }
3610
- function getViewportAdjustedDelta(placement, position, actualWidth, actualHeight) {
3611
- var delta = {
3612
- top: 0,
3613
- left: 0
3614
- }, $viewport = options.viewport && findElement(options.viewport.selector || options.viewport);
3615
- if (!$viewport) {
3616
- return delta;
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 ];
3617
3640
  }
3618
- var viewportPadding = options.viewport && options.viewport.padding || 0, viewportDimensions = getPosition($viewport);
3619
- if (/right|left/.test(placement)) {
3620
- var topEdgeOffset = position.top - viewportPadding - viewportDimensions.scroll, bottomEdgeOffset = position.top + viewportPadding - viewportDimensions.scroll + actualHeight;
3621
- if (topEdgeOffset < viewportDimensions.top) {
3622
- delta.top = viewportDimensions.top - topEdgeOffset;
3623
- } else if (bottomEdgeOffset > viewportDimensions.top + viewportDimensions.height) {
3624
- delta.top = viewportDimensions.top + viewportDimensions.height - bottomEdgeOffset;
3625
- }
3626
- } else {
3627
- var leftEdgeOffset = position.left - viewportPadding, rightEdgeOffset = position.left + viewportPadding + actualWidth;
3628
- if (leftEdgeOffset < viewportDimensions.left) {
3629
- delta.left = viewportDimensions.left - leftEdgeOffset;
3630
- } else if (rightEdgeOffset > viewportDimensions.width) {
3631
- delta.left = viewportDimensions.left + viewportDimensions.width - rightEdgeOffset;
3632
- }
3641
+ $timepicker.select(newDate, selectedIndex, true);
3642
+ createSelection(selectRange[0], selectRange[1]);
3643
+ parentScope.$digest();
3644
+ };
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;
3633
3658
  }
3634
- return delta;
3635
3659
  }
3636
- function replaceArrow(delta, dimension, isHorizontal) {
3637
- var $arrow = findElement('.tooltip-arrow, .arrow', tipElement[0]);
3638
- $arrow.css(isHorizontal ? 'left' : 'top', 50 * (1 - delta / dimension) + '%').css(isHorizontal ? 'top' : 'left', '');
3660
+ function focusElement() {
3661
+ element[0].focus();
3639
3662
  }
3640
- function destroyTipElement() {
3641
- clearTimeout(timeout);
3642
- if ($tooltip.$isShown && tipElement !== null) {
3643
- if (options.autoClose) {
3644
- unbindAutoCloseEvents();
3645
- }
3646
- if (options.keyboard) {
3647
- unbindKeyboardEvents();
3648
- }
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);
3649
3673
  }
3650
- if (tipScope) {
3651
- tipScope.$destroy();
3652
- tipScope = null;
3674
+ _init();
3675
+ };
3676
+ var _destroy = $timepicker.destroy;
3677
+ $timepicker.destroy = function() {
3678
+ if (isNative && options.useNative) {
3679
+ element.off('click', focusElement);
3653
3680
  }
3654
- if (tipElement) {
3655
- tipElement.remove();
3656
- tipElement = $tooltip.$element = null;
3681
+ _destroy();
3682
+ };
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);
3690
+ }
3691
+ }, 0, false);
3692
+ };
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);
3657
3699
  }
3658
- }
3659
- return $tooltip;
3660
- }
3661
- function safeDigest(scope) {
3662
- scope.$$phase || scope.$root && scope.$root.$$phase || scope.$digest();
3663
- }
3664
- function findElement(query, element) {
3665
- return angular.element((element || document).querySelectorAll(query));
3666
- }
3667
- var fetchPromises = {};
3668
- function fetchTemplate(template) {
3669
- if (fetchPromises[template]) return fetchPromises[template];
3670
- return fetchPromises[template] = $http.get(template, {
3671
- cache: $templateCache
3672
- }).then(function(res) {
3673
- return res.data;
3674
- });
3700
+ _hide(blur);
3701
+ };
3702
+ return $timepicker;
3675
3703
  }
3676
- return TooltipFactory;
3704
+ timepickerFactory.defaults = defaults;
3705
+ return timepickerFactory;
3677
3706
  } ];
3678
- }).directive('bsTooltip', [ '$window', '$location', '$sce', '$tooltip', '$$rAF', function($window, $location, $sce, $tooltip, $$rAF) {
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;
3679
3711
  return {
3680
3712
  restrict: 'EAC',
3681
- scope: true,
3682
- link: function postLink(scope, element, attr, transclusion) {
3713
+ require: 'ngModel',
3714
+ link: function postLink(scope, element, attr, controller) {
3683
3715
  var options = {
3684
3716
  scope: scope
3685
3717
  };
3686
- angular.forEach([ 'template', 'contentTemplate', 'placement', 'container', 'delay', 'trigger', 'html', 'animation', 'backdropAnimation', 'type', 'customClass', 'id' ], function(key) {
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) {
3687
3719
  if (angular.isDefined(attr[key])) options[key] = attr[key];
3688
3720
  });
3689
3721
  var falseValueRegExp = /^(false|0|)$/i;
3690
- angular.forEach([ 'html', 'container' ], function(key) {
3722
+ angular.forEach([ 'html', 'container', 'autoclose', 'useNative', 'roundDisplay' ], function(key) {
3691
3723
  if (angular.isDefined(attr[key]) && falseValueRegExp.test(attr[key])) options[key] = false;
3692
3724
  });
3693
- var dataTarget = element.attr('data-target');
3694
- if (angular.isDefined(dataTarget)) {
3695
- if (falseValueRegExp.test(dataTarget)) options.target = false; else options.target = dataTarget;
3696
- }
3697
- if (!scope.hasOwnProperty('title')) {
3698
- scope.title = '';
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);
3736
+ };
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);
3746
+ });
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;
3761
+ }
3762
+ controller.$dateValue = parsedTime;
3699
3763
  }
3700
- attr.$observe('title', function(newValue) {
3701
- if (angular.isDefined(newValue) || !scope.hasOwnProperty('title')) {
3702
- var oldValue = scope.title;
3703
- scope.title = $sce.trustAsHtml(newValue);
3704
- angular.isDefined(oldValue) && $$rAF(function() {
3705
- tooltip && tooltip.$applyPlacement();
3706
- });
3764
+ controller.$parsers.unshift(function(viewValue) {
3765
+ var date;
3766
+ if (!viewValue) {
3767
+ controller.$setValidity('date', true);
3768
+ return null;
3707
3769
  }
3708
- });
3709
- attr.bsTooltip && scope.$watch(attr.bsTooltip, function(newValue, oldValue) {
3710
- if (angular.isObject(newValue)) {
3711
- angular.extend(scope, newValue);
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;
3712
3774
  } else {
3713
- scope.title = newValue;
3775
+ validateAgainstMinMaxTime(parsedTime);
3776
+ }
3777
+ if (options.timeType === 'string') {
3778
+ date = dateParser.timezoneOffsetAdjust(parsedTime, options.timezone, true);
3779
+ return formatDate(date, options.modelTimeFormat || options.timeFormat);
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);
3714
3790
  }
3715
- angular.isDefined(oldValue) && $$rAF(function() {
3716
- tooltip && tooltip.$applyPlacement();
3717
- });
3718
- }, true);
3719
- attr.bsShow && scope.$watch(attr.bsShow, function(newValue, oldValue) {
3720
- if (!tooltip || !angular.isDefined(newValue)) return;
3721
- if (angular.isString(newValue)) newValue = !!newValue.match(/true|,?(tooltip),?/i);
3722
- newValue === true ? tooltip.show() : tooltip.hide();
3723
- });
3724
- attr.bsEnabled && scope.$watch(attr.bsEnabled, function(newValue, oldValue) {
3725
- if (!tooltip || !angular.isDefined(newValue)) return;
3726
- if (angular.isString(newValue)) newValue = !!newValue.match(/true|1|,?(tooltip),?/i);
3727
- newValue === false ? tooltip.setEnabled(false) : tooltip.setEnabled(true);
3728
3791
  });
3729
- attr.viewport && scope.$watch(attr.viewport, function(newValue) {
3730
- if (!tooltip || !angular.isDefined(newValue)) return;
3731
- tooltip.setViewport(newValue);
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);
3804
+ }
3805
+ controller.$dateValue = dateParser.timezoneOffsetAdjust(date, options.timezone);
3806
+ return getTimeFormattedString();
3732
3807
  });
3733
- var tooltip = $tooltip(element, options);
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);
3813
+ }
3734
3814
  scope.$on('$destroy', function() {
3735
- if (tooltip) tooltip.destroy();
3815
+ if (timepicker) timepicker.destroy();
3736
3816
  options = null;
3737
- tooltip = null;
3817
+ timepicker = null;
3738
3818
  });
3739
3819
  }
3740
3820
  };
@@ -3745,7 +3825,7 @@
3745
3825
  prefixClass: 'typeahead',
3746
3826
  prefixEvent: '$typeahead',
3747
3827
  placement: 'bottom-left',
3748
- template: 'typeahead/typeahead.tpl.html',
3828
+ templateUrl: 'typeahead/typeahead.tpl.html',
3749
3829
  trigger: 'focus',
3750
3830
  container: false,
3751
3831
  keyboard: true,
@@ -3758,7 +3838,7 @@
3758
3838
  comparator: '',
3759
3839
  trimValue: true
3760
3840
  };
3761
- this.$get = [ '$window', '$rootScope', '$tooltip', '$timeout', function($window, $rootScope, $tooltip, $timeout) {
3841
+ this.$get = [ '$window', '$rootScope', '$tooltip', '$$rAF', '$timeout', function($window, $rootScope, $tooltip, $$rAF, $timeout) {
3762
3842
  var bodyEl = angular.element($window.document.body);
3763
3843
  function TypeaheadFactory(element, controller, config) {
3764
3844
  var $typeahead = {};
@@ -3789,8 +3869,8 @@
3789
3869
  if (scope.$activeIndex >= matches.length) {
3790
3870
  scope.$activeIndex = options.autoSelect ? 0 : -1;
3791
3871
  }
3792
- if (/^(bottom|bottom-left|bottom-right)$/.test(options.placement)) return;
3793
- $timeout($typeahead.$applyPlacement);
3872
+ safeDigest(scope);
3873
+ $$rAF($typeahead.$applyPlacement);
3794
3874
  };
3795
3875
  $typeahead.activate = function(index) {
3796
3876
  scope.$activeIndex = index;
@@ -3855,6 +3935,9 @@
3855
3935
  };
3856
3936
  return $typeahead;
3857
3937
  }
3938
+ function safeDigest(scope) {
3939
+ scope.$$phase || scope.$root && scope.$root.$$phase || scope.$digest();
3940
+ }
3858
3941
  TypeaheadFactory.defaults = defaults;
3859
3942
  return TypeaheadFactory;
3860
3943
  } ];
@@ -3867,14 +3950,14 @@
3867
3950
  var options = {
3868
3951
  scope: scope
3869
3952
  };
3870
- angular.forEach([ 'placement', 'container', 'delay', 'trigger', 'keyboard', 'html', 'animation', 'template', 'filter', 'limit', 'minLength', 'watchOptions', 'selectMode', 'autoSelect', 'comparator', 'id', 'prefixEvent', 'prefixClass' ], function(key) {
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) {
3871
3954
  if (angular.isDefined(attr[key])) options[key] = attr[key];
3872
3955
  });
3873
3956
  var falseValueRegExp = /^(false|0|)$/i;
3874
3957
  angular.forEach([ 'html', 'container', 'trimValue' ], function(key) {
3875
3958
  if (angular.isDefined(attr[key]) && falseValueRegExp.test(attr[key])) options[key] = false;
3876
3959
  });
3877
- element.attr('autocomplete', 'off');
3960
+ element.attr('autocomplete', 'false');
3878
3961
  var filter = options.filter || defaults.filter;
3879
3962
  var limit = options.limit || defaults.limit;
3880
3963
  var comparator = options.comparator || defaults.comparator;