rails-angular-strap 2.2.4 → 2.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 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;