angularjs-rails 1.4.8 → 1.5.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: 2207f74add954921e782d488f371f0585dcf3444
4
- data.tar.gz: 19350206dd0f9aaedf3c29ea4efa5e0702f6b764
3
+ metadata.gz: 0b483abd42e39cf8cece41a4395e00cbbc1f47fa
4
+ data.tar.gz: 6550da43653042c3a20cd931363ea937af6c2cc0
5
5
  SHA512:
6
- metadata.gz: 9cf08093f16ac21d0b9edc221f3544fac6ee747e178fb0f3c3edc7a0cef9a6201f1897a0fbd22db5a9aa678365a100532e5c6c0edcb12c33ec40b6cd21c9a8e2
7
- data.tar.gz: fad7acb23401023066766f4f0a9b97e7cb80399dceb61f9242d2b58818e395fd8ebfcd9ab343b4f7b14302227b6f1313890952bc06c9004e06e79a2187b1f114
6
+ metadata.gz: fb396bd22d16510898e0bcd3055f0b9064f786be225b9bd49b9634df67f6865d0cb39c35ff394ac02ea73f1151adf9fc02bd871679d6ffacec1a50500a86df27
7
+ data.tar.gz: 715cadcda48fb98ef800458f91c3c8b13e01015ddbb39b36526e97de5c33b84dd7d47c09f285c84726debdfe3df642b9ed44338ab09c9d6c7c11e6c019762782
@@ -1,6 +1,6 @@
1
1
  module AngularJS
2
2
  module Rails
3
- VERSION = "1.4.8"
4
- UNSTABLE_VERSION = "2.0.0-alpha.48"
3
+ VERSION = "1.5.0"
4
+ UNSTABLE_VERSION = "2.0.0-beta.6"
5
5
  end
6
6
  end
@@ -1,12 +1,13 @@
1
1
  /**
2
- * @license AngularJS v1.4.8
3
- * (c) 2010-2015 Google, Inc. http://angularjs.org
2
+ * @license AngularJS v1.5.0
3
+ * (c) 2010-2016 Google, Inc. http://angularjs.org
4
4
  * License: MIT
5
5
  */
6
6
  (function(window, angular, undefined) {'use strict';
7
7
 
8
8
  /* jshint ignore:start */
9
9
  var noop = angular.noop;
10
+ var copy = angular.copy;
10
11
  var extend = angular.extend;
11
12
  var jqLite = angular.element;
12
13
  var forEach = angular.forEach;
@@ -25,6 +26,7 @@ var ADD_CLASS_SUFFIX = '-add';
25
26
  var REMOVE_CLASS_SUFFIX = '-remove';
26
27
  var EVENT_CLASS_PREFIX = 'ng-';
27
28
  var ACTIVE_CLASS_SUFFIX = '-active';
29
+ var PREPARE_CLASS_SUFFIX = '-prepare';
28
30
 
29
31
  var NG_ANIMATE_CLASSNAME = 'ng-animate';
30
32
  var NG_ANIMATE_CHILDREN_DATA = '$$ngAnimateChildren';
@@ -76,6 +78,7 @@ var isPromiseLike = function(p) {
76
78
  return p && p.then ? true : false;
77
79
  };
78
80
 
81
+ var ngMinErr = angular.$$minErr('ng');
79
82
  function assertArg(arg, name, reason) {
80
83
  if (!arg) {
81
84
  throw ngMinErr('areq', "Argument '{0}' is {1}", (name || '?'), (reason || "required"));
@@ -220,7 +223,10 @@ function applyAnimationToStyles(element, options) {
220
223
  }
221
224
  }
222
225
 
223
- function mergeAnimationOptions(element, target, newOptions) {
226
+ function mergeAnimationDetails(element, oldAnimation, newAnimation) {
227
+ var target = oldAnimation.options || {};
228
+ var newOptions = newAnimation.options || {};
229
+
224
230
  var toAdd = (target.addClass || '') + ' ' + (newOptions.addClass || '');
225
231
  var toRemove = (target.removeClass || '') + ' ' + (newOptions.removeClass || '');
226
232
  var classes = resolveElementClasses(element.attr('class'), toAdd, toRemove);
@@ -252,6 +258,9 @@ function mergeAnimationOptions(element, target, newOptions) {
252
258
  target.removeClass = null;
253
259
  }
254
260
 
261
+ oldAnimation.addClass = target.addClass;
262
+ oldAnimation.removeClass = target.removeClass;
263
+
255
264
  return target;
256
265
  }
257
266
 
@@ -387,7 +396,7 @@ var $$rAFSchedulerFactory = ['$$rAF', function($$rAF) {
387
396
  queue = scheduler.queue = [];
388
397
 
389
398
  /* waitUntilQuiet does two things:
390
- * 1. It will run the FINAL `fn` value only when an uncancelled RAF has passed through
399
+ * 1. It will run the FINAL `fn` value only when an uncanceled RAF has passed through
391
400
  * 2. It will delay the next wave of tasks from running until the quiet `fn` has run.
392
401
  *
393
402
  * The motivation here is that animation code can request more time from the scheduler
@@ -422,16 +431,101 @@ var $$rAFSchedulerFactory = ['$$rAF', function($$rAF) {
422
431
  }
423
432
  }];
424
433
 
425
- var $$AnimateChildrenDirective = [function() {
426
- return function(scope, element, attrs) {
427
- var val = attrs.ngAnimateChildren;
428
- if (angular.isString(val) && val.length === 0) { //empty attribute
429
- element.data(NG_ANIMATE_CHILDREN_DATA, true);
430
- } else {
431
- attrs.$observe('ngAnimateChildren', function(value) {
434
+ /**
435
+ * @ngdoc directive
436
+ * @name ngAnimateChildren
437
+ * @restrict AE
438
+ * @element ANY
439
+ *
440
+ * @description
441
+ *
442
+ * ngAnimateChildren allows you to specify that children of this element should animate even if any
443
+ * of the children's parents are currently animating. By default, when an element has an active `enter`, `leave`, or `move`
444
+ * (structural) animation, child elements that also have an active structural animation are not animated.
445
+ *
446
+ * Note that even if `ngAnimteChildren` is set, no child animations will run when the parent element is removed from the DOM (`leave` animation).
447
+ *
448
+ *
449
+ * @param {string} ngAnimateChildren If the value is empty, `true` or `on`,
450
+ * then child animations are allowed. If the value is `false`, child animations are not allowed.
451
+ *
452
+ * @example
453
+ * <example module="ngAnimateChildren" name="ngAnimateChildren" deps="angular-animate.js" animations="true">
454
+ <file name="index.html">
455
+ <div ng-controller="mainController as main">
456
+ <label>Show container? <input type="checkbox" ng-model="main.enterElement" /></label>
457
+ <label>Animate children? <input type="checkbox" ng-model="main.animateChildren" /></label>
458
+ <hr>
459
+ <div ng-animate-children="{{main.animateChildren}}">
460
+ <div ng-if="main.enterElement" class="container">
461
+ List of items:
462
+ <div ng-repeat="item in [0, 1, 2, 3]" class="item">Item {{item}}</div>
463
+ </div>
464
+ </div>
465
+ </div>
466
+ </file>
467
+ <file name="animations.css">
468
+
469
+ .container.ng-enter,
470
+ .container.ng-leave {
471
+ transition: all ease 1.5s;
472
+ }
473
+
474
+ .container.ng-enter,
475
+ .container.ng-leave-active {
476
+ opacity: 0;
477
+ }
478
+
479
+ .container.ng-leave,
480
+ .container.ng-enter-active {
481
+ opacity: 1;
482
+ }
483
+
484
+ .item {
485
+ background: firebrick;
486
+ color: #FFF;
487
+ margin-bottom: 10px;
488
+ }
489
+
490
+ .item.ng-enter,
491
+ .item.ng-leave {
492
+ transition: transform 1.5s ease;
493
+ }
494
+
495
+ .item.ng-enter {
496
+ transform: translateX(50px);
497
+ }
498
+
499
+ .item.ng-enter-active {
500
+ transform: translateX(0);
501
+ }
502
+ </file>
503
+ <file name="script.js">
504
+ angular.module('ngAnimateChildren', ['ngAnimate'])
505
+ .controller('mainController', function() {
506
+ this.animateChildren = false;
507
+ this.enterElement = false;
508
+ });
509
+ </file>
510
+ </example>
511
+ */
512
+ var $$AnimateChildrenDirective = ['$interpolate', function($interpolate) {
513
+ return {
514
+ link: function(scope, element, attrs) {
515
+ var val = attrs.ngAnimateChildren;
516
+ if (angular.isString(val) && val.length === 0) { //empty attribute
517
+ element.data(NG_ANIMATE_CHILDREN_DATA, true);
518
+ } else {
519
+ // Interpolate and set the value, so that it is available to
520
+ // animations that run right after compilation
521
+ setData($interpolate(val)(scope));
522
+ attrs.$observe('ngAnimateChildren', setData);
523
+ }
524
+
525
+ function setData(value) {
432
526
  value = value === 'on' || value === 'true';
433
527
  element.data(NG_ANIMATE_CHILDREN_DATA, value);
434
- });
528
+ }
435
529
  }
436
530
  };
437
531
  }];
@@ -603,7 +697,7 @@ var ANIMATE_TIMER_KEY = '$$animateCss';
603
697
  * ```
604
698
  *
605
699
  * To actually start the animation we need to run `animation.start()` which will then return a promise that we can hook into to detect when the animation ends.
606
- * If we choose not to run the animation then we MUST run `animation.end()` to perform a cleanup on the element (since some CSS classes and stlyes may have been
700
+ * If we choose not to run the animation then we MUST run `animation.end()` to perform a cleanup on the element (since some CSS classes and styles may have been
607
701
  * applied to the element during the preparation phase). Note that all other properties such as duration, delay, transitions and keyframes are just properties
608
702
  * and that changing them will not reconfigure the parameters of the animation.
609
703
  *
@@ -640,11 +734,11 @@ var ANIMATE_TIMER_KEY = '$$animateCss';
640
734
  * * `stagger` - A numeric time value representing the delay between successively animated elements
641
735
  * ({@link ngAnimate#css-staggering-animations Click here to learn how CSS-based staggering works in ngAnimate.})
642
736
  * * `staggerIndex` - The numeric index representing the stagger item (e.g. a value of 5 is equal to the sixth item in the stagger; therefore when a
643
- * * `stagger` option value of `0.1` is used then there will be a stagger delay of `600ms`)
644
- * * `applyClassesEarly` - Whether or not the classes being added or removed will be used when detecting the animation. This is set by `$animate` when enter/leave/move animations are fired to ensure that the CSS classes are resolved in time. (Note that this will prevent any transitions from occuring on the classes being added and removed.)
737
+ * `stagger` option value of `0.1` is used then there will be a stagger delay of `600ms`)
738
+ * * `applyClassesEarly` - Whether or not the classes being added or removed will be used when detecting the animation. This is set by `$animate` when enter/leave/move animations are fired to ensure that the CSS classes are resolved in time. (Note that this will prevent any transitions from occurring on the classes being added and removed.)
645
739
  * * `cleanupStyles` - Whether or not the provided `from` and `to` styles will be removed once
646
740
  * the animation is closed. This is useful for when the styles are used purely for the sake of
647
- * the animation and do not have a lasting visual effect on the element (e.g. a colapse and open animation).
741
+ * the animation and do not have a lasting visual effect on the element (e.g. a collapse and open animation).
648
742
  * By default this value is set to `false`.
649
743
  *
650
744
  * @return {object} an object with start and end methods and details about the animation.
@@ -697,7 +791,7 @@ function computeCssStyles($window, element, properties) {
697
791
  }
698
792
 
699
793
  // by setting this to null in the event that the delay is not set or is set directly as 0
700
- // then we can still allow for zegative values to be used later on and not mistake this
794
+ // then we can still allow for negative values to be used later on and not mistake this
701
795
  // value for being greater than any other negative value.
702
796
  if (val === 0) {
703
797
  val = null;
@@ -788,9 +882,9 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
788
882
  var gcsStaggerLookup = createLocalCacheLookup();
789
883
 
790
884
  this.$get = ['$window', '$$jqLite', '$$AnimateRunner', '$timeout',
791
- '$$forceReflow', '$sniffer', '$$rAFScheduler', '$animate',
885
+ '$$forceReflow', '$sniffer', '$$rAFScheduler', '$$animateQueue',
792
886
  function($window, $$jqLite, $$AnimateRunner, $timeout,
793
- $$forceReflow, $sniffer, $$rAFScheduler, $animate) {
887
+ $$forceReflow, $sniffer, $$rAFScheduler, $$animateQueue) {
794
888
 
795
889
  var applyAnimationClasses = applyAnimationClassesFactory($$jqLite);
796
890
 
@@ -813,7 +907,7 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
813
907
  }
814
908
 
815
909
  // we keep putting this in multiple times even though the value and the cacheKey are the same
816
- // because we're keeping an interal tally of how many duplicate animations are detected.
910
+ // because we're keeping an internal tally of how many duplicate animations are detected.
817
911
  gcsLookup.put(cacheKey, timings);
818
912
  return timings;
819
913
  }
@@ -882,17 +976,24 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
882
976
  return timings;
883
977
  }
884
978
 
885
- return function init(element, options) {
979
+ return function init(element, initialOptions) {
980
+ // all of the animation functions should create
981
+ // a copy of the options data, however, if a
982
+ // parent service has already created a copy then
983
+ // we should stick to using that
984
+ var options = initialOptions || {};
985
+ if (!options.$$prepared) {
986
+ options = prepareAnimationOptions(copy(options));
987
+ }
988
+
886
989
  var restoreStyles = {};
887
990
  var node = getDomNode(element);
888
991
  if (!node
889
992
  || !node.parentNode
890
- || !$animate.enabled()) {
993
+ || !$$animateQueue.enabled()) {
891
994
  return closeAndReturnNoopAnimator();
892
995
  }
893
996
 
894
- options = prepareAnimationOptions(options);
895
-
896
997
  var temporaryStyles = [];
897
998
  var classes = element.attr('class');
898
999
  var styles = packageStyles(options);
@@ -905,6 +1006,8 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
905
1006
  var maxDelayTime;
906
1007
  var maxDuration;
907
1008
  var maxDurationTime;
1009
+ var startTime;
1010
+ var events = [];
908
1011
 
909
1012
  if (options.duration === 0 || (!$sniffer.animations && !$sniffer.transitions)) {
910
1013
  return closeAndReturnNoopAnimator();
@@ -1058,7 +1161,12 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
1058
1161
  }
1059
1162
 
1060
1163
  if (options.delay != null) {
1061
- var delayStyle = parseFloat(options.delay);
1164
+ var delayStyle;
1165
+ if (typeof options.delay !== "boolean") {
1166
+ delayStyle = parseFloat(options.delay);
1167
+ // number in options.delay means we have to recalculate the delay for the closing timeout
1168
+ maxDelay = Math.max(delayStyle, 0);
1169
+ }
1062
1170
 
1063
1171
  if (flags.applyTransitionDelay) {
1064
1172
  temporaryStyles.push(getCssDelayStyle(delayStyle));
@@ -1173,6 +1281,18 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
1173
1281
  options.onDone();
1174
1282
  }
1175
1283
 
1284
+ if (events && events.length) {
1285
+ // Remove the transitionend / animationend listener(s)
1286
+ element.off(events.join(' '), onAnimationProgress);
1287
+ }
1288
+
1289
+ //Cancel the fallback closing timeout and remove the timer data
1290
+ var animationTimerData = element.data(ANIMATE_TIMER_KEY);
1291
+ if (animationTimerData) {
1292
+ $timeout.cancel(animationTimerData[0].timer);
1293
+ element.removeData(ANIMATE_TIMER_KEY);
1294
+ }
1295
+
1176
1296
  // if the preparation function fails then the promise is not setup
1177
1297
  if (runner) {
1178
1298
  runner.complete(!rejected);
@@ -1208,6 +1328,33 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
1208
1328
  };
1209
1329
  }
1210
1330
 
1331
+ function onAnimationProgress(event) {
1332
+ event.stopPropagation();
1333
+ var ev = event.originalEvent || event;
1334
+
1335
+ // we now always use `Date.now()` due to the recent changes with
1336
+ // event.timeStamp in Firefox, Webkit and Chrome (see #13494 for more info)
1337
+ var timeStamp = ev.$manualTimeStamp || Date.now();
1338
+
1339
+ /* Firefox (or possibly just Gecko) likes to not round values up
1340
+ * when a ms measurement is used for the animation */
1341
+ var elapsedTime = parseFloat(ev.elapsedTime.toFixed(ELAPSED_TIME_MAX_DECIMAL_PLACES));
1342
+
1343
+ /* $manualTimeStamp is a mocked timeStamp value which is set
1344
+ * within browserTrigger(). This is only here so that tests can
1345
+ * mock animations properly. Real events fallback to event.timeStamp,
1346
+ * or, if they don't, then a timeStamp is automatically created for them.
1347
+ * We're checking to see if the timeStamp surpasses the expected delay,
1348
+ * but we're using elapsedTime instead of the timeStamp on the 2nd
1349
+ * pre-condition since animationPauseds sometimes close off early */
1350
+ if (Math.max(timeStamp - startTime, 0) >= maxDelayTime && elapsedTime >= maxDuration) {
1351
+ // we set this flag to ensure that if the transition is paused then, when resumed,
1352
+ // the animation will automatically close itself since transitions cannot be paused.
1353
+ animationCompleted = true;
1354
+ close();
1355
+ }
1356
+ }
1357
+
1211
1358
  function start() {
1212
1359
  if (animationClosed) return;
1213
1360
  if (!node.parentNode) {
@@ -1215,8 +1362,6 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
1215
1362
  return;
1216
1363
  }
1217
1364
 
1218
- var startTime, events = [];
1219
-
1220
1365
  // even though we only pause keyframe animations here the pause flag
1221
1366
  // will still happen when transitions are used. Only the transition will
1222
1367
  // not be paused since that is not possible. If the animation ends when
@@ -1236,9 +1381,9 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
1236
1381
  }
1237
1382
  };
1238
1383
 
1239
- // checking the stagger duration prevents an accidently cascade of the CSS delay style
1384
+ // checking the stagger duration prevents an accidentally cascade of the CSS delay style
1240
1385
  // being inherited from the parent. If the transition duration is zero then we can safely
1241
- // rely that the delay value is an intential stagger delay style.
1386
+ // rely that the delay value is an intentional stagger delay style.
1242
1387
  var maxStagger = itemIndex > 0
1243
1388
  && ((timings.transitionDuration && stagger.transitionDuration === 0) ||
1244
1389
  (timings.animationDuration && stagger.animationDuration === 0))
@@ -1357,7 +1502,10 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
1357
1502
  element.data(ANIMATE_TIMER_KEY, animationsData);
1358
1503
  }
1359
1504
 
1360
- element.on(events.join(' '), onAnimationProgress);
1505
+ if (events.length) {
1506
+ element.on(events.join(' '), onAnimationProgress);
1507
+ }
1508
+
1361
1509
  if (options.to) {
1362
1510
  if (options.cleanupStyles) {
1363
1511
  registerRestorableStyles(restoreStyles, node, Object.keys(options.to));
@@ -1379,30 +1527,6 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
1379
1527
  element.removeData(ANIMATE_TIMER_KEY);
1380
1528
  }
1381
1529
  }
1382
-
1383
- function onAnimationProgress(event) {
1384
- event.stopPropagation();
1385
- var ev = event.originalEvent || event;
1386
- var timeStamp = ev.$manualTimeStamp || ev.timeStamp || Date.now();
1387
-
1388
- /* Firefox (or possibly just Gecko) likes to not round values up
1389
- * when a ms measurement is used for the animation */
1390
- var elapsedTime = parseFloat(ev.elapsedTime.toFixed(ELAPSED_TIME_MAX_DECIMAL_PLACES));
1391
-
1392
- /* $manualTimeStamp is a mocked timeStamp value which is set
1393
- * within browserTrigger(). This is only here so that tests can
1394
- * mock animations properly. Real events fallback to event.timeStamp,
1395
- * or, if they don't, then a timeStamp is automatically created for them.
1396
- * We're checking to see if the timeStamp surpasses the expected delay,
1397
- * but we're using elapsedTime instead of the timeStamp on the 2nd
1398
- * pre-condition since animations sometimes close off early */
1399
- if (Math.max(timeStamp - startTime, 0) >= maxDelayTime && elapsedTime >= maxDuration) {
1400
- // we set this flag to ensure that if the transition is paused then, when resumed,
1401
- // the animation will automatically close itself since transitions cannot be paused.
1402
- animationCompleted = true;
1403
- close();
1404
- }
1405
- }
1406
1530
  }
1407
1531
  };
1408
1532
  }];
@@ -1432,7 +1556,7 @@ var $$AnimateCssDriverProvider = ['$$animationProvider', function($$animationPro
1432
1556
 
1433
1557
  var rootBodyElement = jqLite(
1434
1558
  // this is to avoid using something that exists outside of the body
1435
- // we also special case the doc fragement case because our unit test code
1559
+ // we also special case the doc fragment case because our unit test code
1436
1560
  // appends the $rootElement to the body after the app has been bootstrapped
1437
1561
  isDocumentFragment(rootNode) || bodyNode.contains(rootNode) ? rootNode : bodyNode
1438
1562
  );
@@ -1532,7 +1656,7 @@ var $$AnimateCssDriverProvider = ['$$animationProvider', function($$animationPro
1532
1656
  var coords = getDomNode(anchor).getBoundingClientRect();
1533
1657
 
1534
1658
  // we iterate directly since safari messes up and doesn't return
1535
- // all the keys for the coods object when iterated
1659
+ // all the keys for the coords object when iterated
1536
1660
  forEach(['width','height','top','left'], function(key) {
1537
1661
  var value = coords[key];
1538
1662
  switch (key) {
@@ -1687,6 +1811,8 @@ var $$AnimateJsProvider = ['$animateProvider', function($animateProvider) {
1687
1811
  var applyAnimationClasses = applyAnimationClassesFactory($$jqLite);
1688
1812
  // $animateJs(element, 'enter');
1689
1813
  return function(element, event, classes, options) {
1814
+ var animationClosed = false;
1815
+
1690
1816
  // the `classes` argument is optional and if it is not used
1691
1817
  // then the classes will be resolved from the element's className
1692
1818
  // property as well as options.addClass/options.removeClass.
@@ -1739,8 +1865,32 @@ var $$AnimateJsProvider = ['$animateProvider', function($animateProvider) {
1739
1865
  applyAnimationClasses(element, options);
1740
1866
  }
1741
1867
 
1868
+ function close() {
1869
+ animationClosed = true;
1870
+ applyOptions();
1871
+ applyAnimationStyles(element, options);
1872
+ }
1873
+
1874
+ var runner;
1875
+
1742
1876
  return {
1877
+ $$willAnimate: true,
1878
+ end: function() {
1879
+ if (runner) {
1880
+ runner.end();
1881
+ } else {
1882
+ close();
1883
+ runner = new $$AnimateRunner();
1884
+ runner.complete(true);
1885
+ }
1886
+ return runner;
1887
+ },
1743
1888
  start: function() {
1889
+ if (runner) {
1890
+ return runner;
1891
+ }
1892
+
1893
+ runner = new $$AnimateRunner();
1744
1894
  var closeActiveAnimations;
1745
1895
  var chain = [];
1746
1896
 
@@ -1765,8 +1915,7 @@ var $$AnimateJsProvider = ['$animateProvider', function($animateProvider) {
1765
1915
  });
1766
1916
  }
1767
1917
 
1768
- var animationClosed = false;
1769
- var runner = new $$AnimateRunner({
1918
+ runner.setHost({
1770
1919
  end: function() {
1771
1920
  endAnimations();
1772
1921
  },
@@ -1779,9 +1928,7 @@ var $$AnimateJsProvider = ['$animateProvider', function($animateProvider) {
1779
1928
  return runner;
1780
1929
 
1781
1930
  function onComplete(success) {
1782
- animationClosed = true;
1783
- applyOptions();
1784
- applyAnimationStyles(element, options);
1931
+ close(success);
1785
1932
  runner.complete(success);
1786
1933
  }
1787
1934
 
@@ -2001,6 +2148,7 @@ var NG_ANIMATE_PIN_DATA = '$ngAnimatePin';
2001
2148
  var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
2002
2149
  var PRE_DIGEST_STATE = 1;
2003
2150
  var RUNNING_STATE = 2;
2151
+ var ONE_SPACE = ' ';
2004
2152
 
2005
2153
  var rules = this.rules = {
2006
2154
  skip: [],
@@ -2008,28 +2156,50 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
2008
2156
  join: []
2009
2157
  };
2010
2158
 
2159
+ function makeTruthyCssClassMap(classString) {
2160
+ if (!classString) {
2161
+ return null;
2162
+ }
2163
+
2164
+ var keys = classString.split(ONE_SPACE);
2165
+ var map = Object.create(null);
2166
+
2167
+ forEach(keys, function(key) {
2168
+ map[key] = true;
2169
+ });
2170
+ return map;
2171
+ }
2172
+
2173
+ function hasMatchingClasses(newClassString, currentClassString) {
2174
+ if (newClassString && currentClassString) {
2175
+ var currentClassMap = makeTruthyCssClassMap(currentClassString);
2176
+ return newClassString.split(ONE_SPACE).some(function(className) {
2177
+ return currentClassMap[className];
2178
+ });
2179
+ }
2180
+ }
2181
+
2011
2182
  function isAllowed(ruleType, element, currentAnimation, previousAnimation) {
2012
2183
  return rules[ruleType].some(function(fn) {
2013
2184
  return fn(element, currentAnimation, previousAnimation);
2014
2185
  });
2015
2186
  }
2016
2187
 
2017
- function hasAnimationClasses(options, and) {
2018
- options = options || {};
2019
- var a = (options.addClass || '').length > 0;
2020
- var b = (options.removeClass || '').length > 0;
2188
+ function hasAnimationClasses(animation, and) {
2189
+ var a = (animation.addClass || '').length > 0;
2190
+ var b = (animation.removeClass || '').length > 0;
2021
2191
  return and ? a && b : a || b;
2022
2192
  }
2023
2193
 
2024
2194
  rules.join.push(function(element, newAnimation, currentAnimation) {
2025
2195
  // if the new animation is class-based then we can just tack that on
2026
- return !newAnimation.structural && hasAnimationClasses(newAnimation.options);
2196
+ return !newAnimation.structural && hasAnimationClasses(newAnimation);
2027
2197
  });
2028
2198
 
2029
2199
  rules.skip.push(function(element, newAnimation, currentAnimation) {
2030
2200
  // there is no need to animate anything if no classes are being added and
2031
2201
  // there is no structural animation that will be triggered
2032
- return !newAnimation.structural && !hasAnimationClasses(newAnimation.options);
2202
+ return !newAnimation.structural && !hasAnimationClasses(newAnimation);
2033
2203
  });
2034
2204
 
2035
2205
  rules.skip.push(function(element, newAnimation, currentAnimation) {
@@ -2055,11 +2225,17 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
2055
2225
  });
2056
2226
 
2057
2227
  rules.cancel.push(function(element, newAnimation, currentAnimation) {
2058
- var nO = newAnimation.options;
2059
- var cO = currentAnimation.options;
2228
+ var nA = newAnimation.addClass;
2229
+ var nR = newAnimation.removeClass;
2230
+ var cA = currentAnimation.addClass;
2231
+ var cR = currentAnimation.removeClass;
2232
+
2233
+ // early detection to save the global CPU shortage :)
2234
+ if ((isUndefined(nA) && isUndefined(nR)) || (isUndefined(cA) && isUndefined(cR))) {
2235
+ return false;
2236
+ }
2060
2237
 
2061
- // if the exact same CSS class is added/removed then it's safe to cancel it
2062
- return (nO.addClass && nO.addClass === cO.removeClass) || (nO.removeClass && nO.removeClass === cO.addClass);
2238
+ return hasMatchingClasses(nA, cR) || hasMatchingClasses(nR, cA);
2063
2239
  });
2064
2240
 
2065
2241
  this.$get = ['$$rAF', '$rootScope', '$rootElement', '$document', '$$HashMap',
@@ -2131,10 +2307,17 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
2131
2307
 
2132
2308
  var applyAnimationClasses = applyAnimationClassesFactory($$jqLite);
2133
2309
 
2134
- function normalizeAnimationOptions(element, options) {
2135
- return mergeAnimationOptions(element, options, {});
2310
+ function normalizeAnimationDetails(element, animation) {
2311
+ return mergeAnimationDetails(element, animation, {});
2136
2312
  }
2137
2313
 
2314
+ // IE9-11 has no method "contains" in SVG element and in Node.prototype. Bug #10259.
2315
+ var contains = Node.prototype.contains || function(arg) {
2316
+ // jshint bitwise: false
2317
+ return this === arg || !!(this.compareDocumentPosition(arg) & 16);
2318
+ // jshint bitwise: true
2319
+ };
2320
+
2138
2321
  function findCallbacks(parent, element, event) {
2139
2322
  var targetNode = getDomNode(element);
2140
2323
  var targetParentNode = getDomNode(parent);
@@ -2143,9 +2326,9 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
2143
2326
  var entries = callbackRegistry[event];
2144
2327
  if (entries) {
2145
2328
  forEach(entries, function(entry) {
2146
- if (entry.node.contains(targetNode)) {
2329
+ if (contains.call(entry.node, targetNode)) {
2147
2330
  matches.push(entry.callback);
2148
- } else if (event === 'leave' && entry.node.contains(targetParentNode)) {
2331
+ } else if (event === 'leave' && contains.call(entry.node, targetParentNode)) {
2149
2332
  matches.push(entry.callback);
2150
2333
  }
2151
2334
  });
@@ -2220,12 +2403,7 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
2220
2403
  bool = !recordExists;
2221
2404
  } else {
2222
2405
  // (element, bool) - Element setter
2223
- bool = !!bool;
2224
- if (!bool) {
2225
- disabledElementsLookup.put(node, true);
2226
- } else if (recordExists) {
2227
- disabledElementsLookup.remove(node);
2228
- }
2406
+ disabledElementsLookup.put(node, !bool);
2229
2407
  }
2230
2408
  }
2231
2409
  }
@@ -2234,7 +2412,12 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
2234
2412
  }
2235
2413
  };
2236
2414
 
2237
- function queueAnimation(element, event, options) {
2415
+ function queueAnimation(element, event, initialOptions) {
2416
+ // we always make a copy of the options since
2417
+ // there should never be any side effects on
2418
+ // the input data when running `$animateCss`.
2419
+ var options = copy(initialOptions);
2420
+
2238
2421
  var node, parent;
2239
2422
  element = stripCommentsFromElement(element);
2240
2423
  if (element) {
@@ -2294,7 +2477,9 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
2294
2477
  // this is a hard disable of all animations for the application or on
2295
2478
  // the element itself, therefore there is no need to continue further
2296
2479
  // past this point if not enabled
2297
- var skipAnimations = !animationsEnabled || disabledElementsLookup.get(node);
2480
+ // Animations are also disabled if the document is currently hidden (page is not visible
2481
+ // to the user), because browsers slow down or do not flush calls to requestAnimationFrame
2482
+ var skipAnimations = !animationsEnabled || $document[0].hidden || disabledElementsLookup.get(node);
2298
2483
  var existingAnimation = (!skipAnimations && activeAnimationsLookup.get(node)) || {};
2299
2484
  var hasExistingAnimation = !!existingAnimation.state;
2300
2485
 
@@ -2317,6 +2502,8 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
2317
2502
  structural: isStructural,
2318
2503
  element: element,
2319
2504
  event: event,
2505
+ addClass: options.addClass,
2506
+ removeClass: options.removeClass,
2320
2507
  close: close,
2321
2508
  options: options,
2322
2509
  runner: runner
@@ -2329,11 +2516,10 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
2329
2516
  close();
2330
2517
  return runner;
2331
2518
  } else {
2332
- mergeAnimationOptions(element, existingAnimation.options, options);
2519
+ mergeAnimationDetails(element, existingAnimation, newAnimation);
2333
2520
  return existingAnimation.runner;
2334
2521
  }
2335
2522
  }
2336
-
2337
2523
  var cancelAnimationFlag = isAllowed('cancel', element, newAnimation, existingAnimation);
2338
2524
  if (cancelAnimationFlag) {
2339
2525
  if (existingAnimation.state === RUNNING_STATE) {
@@ -2348,7 +2534,8 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
2348
2534
  existingAnimation.close();
2349
2535
  } else {
2350
2536
  // this will merge the new animation options into existing animation options
2351
- mergeAnimationOptions(element, existingAnimation.options, newAnimation.options);
2537
+ mergeAnimationDetails(element, existingAnimation, newAnimation);
2538
+
2352
2539
  return existingAnimation.runner;
2353
2540
  }
2354
2541
  } else {
@@ -2358,12 +2545,12 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
2358
2545
  var joinAnimationFlag = isAllowed('join', element, newAnimation, existingAnimation);
2359
2546
  if (joinAnimationFlag) {
2360
2547
  if (existingAnimation.state === RUNNING_STATE) {
2361
- normalizeAnimationOptions(element, options);
2548
+ normalizeAnimationDetails(element, newAnimation);
2362
2549
  } else {
2363
2550
  applyGeneratedPreparationClasses(element, isStructural ? event : null, options);
2364
2551
 
2365
2552
  event = newAnimation.event = existingAnimation.event;
2366
- options = mergeAnimationOptions(element, existingAnimation.options, newAnimation.options);
2553
+ options = mergeAnimationDetails(element, existingAnimation, newAnimation);
2367
2554
 
2368
2555
  //we return the same runner since only the option values of this animation will
2369
2556
  //be fed into the `existingAnimation`.
@@ -2374,7 +2561,7 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
2374
2561
  } else {
2375
2562
  // normalization in this case means that it removes redundant CSS classes that
2376
2563
  // already exist (addClass) or do not exist (removeClass) on the element
2377
- normalizeAnimationOptions(element, options);
2564
+ normalizeAnimationDetails(element, newAnimation);
2378
2565
  }
2379
2566
 
2380
2567
  // when the options are merged and cleaned up we may end up not having to do
@@ -2384,7 +2571,7 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
2384
2571
  if (!isValidAnimation) {
2385
2572
  // animate (from/to) can be quickly checked first, otherwise we check if any classes are present
2386
2573
  isValidAnimation = (newAnimation.event === 'animate' && Object.keys(newAnimation.options.to || {}).length > 0)
2387
- || hasAnimationClasses(newAnimation.options);
2574
+ || hasAnimationClasses(newAnimation);
2388
2575
  }
2389
2576
 
2390
2577
  if (!isValidAnimation) {
@@ -2414,7 +2601,7 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
2414
2601
  var isValidAnimation = parentElement.length > 0
2415
2602
  && (animationDetails.event === 'animate'
2416
2603
  || animationDetails.structural
2417
- || hasAnimationClasses(animationDetails.options));
2604
+ || hasAnimationClasses(animationDetails));
2418
2605
 
2419
2606
  // this means that the previous animation was cancelled
2420
2607
  // even if the follow-up animation is the same event
@@ -2446,7 +2633,7 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
2446
2633
 
2447
2634
  // this combined multiple class to addClass / removeClass into a setClass event
2448
2635
  // so long as a structural event did not take over the animation
2449
- event = !animationDetails.structural && hasAnimationClasses(animationDetails.options, true)
2636
+ event = !animationDetails.structural && hasAnimationClasses(animationDetails, true)
2450
2637
  ? 'setClass'
2451
2638
  : animationDetails.event;
2452
2639
 
@@ -2503,15 +2690,15 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
2503
2690
  forEach(children, function(child) {
2504
2691
  var state = parseInt(child.getAttribute(NG_ANIMATE_ATTR_NAME));
2505
2692
  var animationDetails = activeAnimationsLookup.get(child);
2506
- switch (state) {
2507
- case RUNNING_STATE:
2508
- animationDetails.runner.end();
2509
- /* falls through */
2510
- case PRE_DIGEST_STATE:
2511
- if (animationDetails) {
2693
+ if (animationDetails) {
2694
+ switch (state) {
2695
+ case RUNNING_STATE:
2696
+ animationDetails.runner.end();
2697
+ /* falls through */
2698
+ case PRE_DIGEST_STATE:
2512
2699
  activeAnimationsLookup.remove(child);
2513
- }
2514
- break;
2700
+ break;
2701
+ }
2515
2702
  }
2516
2703
  });
2517
2704
  }
@@ -2526,12 +2713,20 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
2526
2713
  return getDomNode(nodeOrElmA) === getDomNode(nodeOrElmB);
2527
2714
  }
2528
2715
 
2716
+ /**
2717
+ * This fn returns false if any of the following is true:
2718
+ * a) animations on any parent element are disabled, and animations on the element aren't explicitly allowed
2719
+ * b) a parent element has an ongoing structural animation, and animateChildren is false
2720
+ * c) the element is not a child of the body
2721
+ * d) the element is not a child of the $rootElement
2722
+ */
2529
2723
  function areAnimationsAllowed(element, parentElement, event) {
2530
2724
  var bodyElement = jqLite($document[0].body);
2531
2725
  var bodyElementDetected = isMatchingElement(element, bodyElement) || element[0].nodeName === 'HTML';
2532
2726
  var rootElementDetected = isMatchingElement(element, $rootElement);
2533
2727
  var parentAnimationDetected = false;
2534
2728
  var animateChildren;
2729
+ var elementDisabled = disabledElementsLookup.get(getDomNode(element));
2535
2730
 
2536
2731
  var parentHost = element.data(NG_ANIMATE_PIN_DATA);
2537
2732
  if (parentHost) {
@@ -2556,7 +2751,18 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
2556
2751
  // therefore we can't allow any animations to take place
2557
2752
  // but if a parent animation is class-based then that's ok
2558
2753
  if (!parentAnimationDetected) {
2559
- parentAnimationDetected = details.structural || disabledElementsLookup.get(parentNode);
2754
+ var parentElementDisabled = disabledElementsLookup.get(parentNode);
2755
+
2756
+ if (parentElementDisabled === true && elementDisabled !== false) {
2757
+ // disable animations if the user hasn't explicitly enabled animations on the
2758
+ // current element
2759
+ elementDisabled = true;
2760
+ // element is disabled via parent element, no need to check anything else
2761
+ break;
2762
+ } else if (parentElementDisabled === false) {
2763
+ elementDisabled = false;
2764
+ }
2765
+ parentAnimationDetected = details.structural;
2560
2766
  }
2561
2767
 
2562
2768
  if (isUndefined(animateChildren) || animateChildren === true) {
@@ -2569,28 +2775,32 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
2569
2775
  // there is no need to continue traversing at this point
2570
2776
  if (parentAnimationDetected && animateChildren === false) break;
2571
2777
 
2572
- if (!rootElementDetected) {
2573
- // angular doesn't want to attempt to animate elements outside of the application
2574
- // therefore we need to ensure that the rootElement is an ancestor of the current element
2575
- rootElementDetected = isMatchingElement(parentElement, $rootElement);
2576
- if (!rootElementDetected) {
2577
- parentHost = parentElement.data(NG_ANIMATE_PIN_DATA);
2578
- if (parentHost) {
2579
- parentElement = parentHost;
2580
- }
2581
- }
2582
- }
2583
-
2584
2778
  if (!bodyElementDetected) {
2585
- // we also need to ensure that the element is or will be apart of the body element
2779
+ // we also need to ensure that the element is or will be a part of the body element
2586
2780
  // otherwise it is pointless to even issue an animation to be rendered
2587
2781
  bodyElementDetected = isMatchingElement(parentElement, bodyElement);
2588
2782
  }
2589
2783
 
2784
+ if (bodyElementDetected && rootElementDetected) {
2785
+ // If both body and root have been found, any other checks are pointless,
2786
+ // as no animation data should live outside the application
2787
+ break;
2788
+ }
2789
+
2790
+ if (!rootElementDetected) {
2791
+ // If no rootElement is detected, check if the parentElement is pinned to another element
2792
+ parentHost = parentElement.data(NG_ANIMATE_PIN_DATA);
2793
+ if (parentHost) {
2794
+ // The pin target element becomes the next parent element
2795
+ parentElement = parentHost;
2796
+ continue;
2797
+ }
2798
+ }
2799
+
2590
2800
  parentElement = parentElement.parent();
2591
2801
  }
2592
2802
 
2593
- var allowAnimation = !parentAnimationDetected || animateChildren;
2803
+ var allowAnimation = (!parentAnimationDetected || animateChildren) && elementDisabled !== true;
2594
2804
  return allowAnimation && rootElementDetected && bodyElementDetected;
2595
2805
  }
2596
2806
 
@@ -2610,171 +2820,6 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
2610
2820
  }];
2611
2821
  }];
2612
2822
 
2613
- var $$AnimateAsyncRunFactory = ['$$rAF', function($$rAF) {
2614
- var waitQueue = [];
2615
-
2616
- function waitForTick(fn) {
2617
- waitQueue.push(fn);
2618
- if (waitQueue.length > 1) return;
2619
- $$rAF(function() {
2620
- for (var i = 0; i < waitQueue.length; i++) {
2621
- waitQueue[i]();
2622
- }
2623
- waitQueue = [];
2624
- });
2625
- }
2626
-
2627
- return function() {
2628
- var passed = false;
2629
- waitForTick(function() {
2630
- passed = true;
2631
- });
2632
- return function(callback) {
2633
- passed ? callback() : waitForTick(callback);
2634
- };
2635
- };
2636
- }];
2637
-
2638
- var $$AnimateRunnerFactory = ['$q', '$sniffer', '$$animateAsyncRun',
2639
- function($q, $sniffer, $$animateAsyncRun) {
2640
-
2641
- var INITIAL_STATE = 0;
2642
- var DONE_PENDING_STATE = 1;
2643
- var DONE_COMPLETE_STATE = 2;
2644
-
2645
- AnimateRunner.chain = function(chain, callback) {
2646
- var index = 0;
2647
-
2648
- next();
2649
- function next() {
2650
- if (index === chain.length) {
2651
- callback(true);
2652
- return;
2653
- }
2654
-
2655
- chain[index](function(response) {
2656
- if (response === false) {
2657
- callback(false);
2658
- return;
2659
- }
2660
- index++;
2661
- next();
2662
- });
2663
- }
2664
- };
2665
-
2666
- AnimateRunner.all = function(runners, callback) {
2667
- var count = 0;
2668
- var status = true;
2669
- forEach(runners, function(runner) {
2670
- runner.done(onProgress);
2671
- });
2672
-
2673
- function onProgress(response) {
2674
- status = status && response;
2675
- if (++count === runners.length) {
2676
- callback(status);
2677
- }
2678
- }
2679
- };
2680
-
2681
- function AnimateRunner(host) {
2682
- this.setHost(host);
2683
-
2684
- this._doneCallbacks = [];
2685
- this._runInAnimationFrame = $$animateAsyncRun();
2686
- this._state = 0;
2687
- }
2688
-
2689
- AnimateRunner.prototype = {
2690
- setHost: function(host) {
2691
- this.host = host || {};
2692
- },
2693
-
2694
- done: function(fn) {
2695
- if (this._state === DONE_COMPLETE_STATE) {
2696
- fn();
2697
- } else {
2698
- this._doneCallbacks.push(fn);
2699
- }
2700
- },
2701
-
2702
- progress: noop,
2703
-
2704
- getPromise: function() {
2705
- if (!this.promise) {
2706
- var self = this;
2707
- this.promise = $q(function(resolve, reject) {
2708
- self.done(function(status) {
2709
- status === false ? reject() : resolve();
2710
- });
2711
- });
2712
- }
2713
- return this.promise;
2714
- },
2715
-
2716
- then: function(resolveHandler, rejectHandler) {
2717
- return this.getPromise().then(resolveHandler, rejectHandler);
2718
- },
2719
-
2720
- 'catch': function(handler) {
2721
- return this.getPromise()['catch'](handler);
2722
- },
2723
-
2724
- 'finally': function(handler) {
2725
- return this.getPromise()['finally'](handler);
2726
- },
2727
-
2728
- pause: function() {
2729
- if (this.host.pause) {
2730
- this.host.pause();
2731
- }
2732
- },
2733
-
2734
- resume: function() {
2735
- if (this.host.resume) {
2736
- this.host.resume();
2737
- }
2738
- },
2739
-
2740
- end: function() {
2741
- if (this.host.end) {
2742
- this.host.end();
2743
- }
2744
- this._resolve(true);
2745
- },
2746
-
2747
- cancel: function() {
2748
- if (this.host.cancel) {
2749
- this.host.cancel();
2750
- }
2751
- this._resolve(false);
2752
- },
2753
-
2754
- complete: function(response) {
2755
- var self = this;
2756
- if (self._state === INITIAL_STATE) {
2757
- self._state = DONE_PENDING_STATE;
2758
- self._runInAnimationFrame(function() {
2759
- self._resolve(response);
2760
- });
2761
- }
2762
- },
2763
-
2764
- _resolve: function(response) {
2765
- if (this._state !== DONE_COMPLETE_STATE) {
2766
- forEach(this._doneCallbacks, function(fn) {
2767
- fn(response);
2768
- });
2769
- this._doneCallbacks.length = 0;
2770
- this._state = DONE_COMPLETE_STATE;
2771
- }
2772
- }
2773
- };
2774
-
2775
- return AnimateRunner;
2776
- }];
2777
-
2778
2823
  var $$AnimationProvider = ['$animateProvider', function($animateProvider) {
2779
2824
  var NG_ANIMATE_REF_ATTR = 'ng-animate-ref';
2780
2825
 
@@ -2910,6 +2955,12 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) {
2910
2955
  options.tempClasses = null;
2911
2956
  }
2912
2957
 
2958
+ var prepareClassName;
2959
+ if (isStructural) {
2960
+ prepareClassName = 'ng-' + event + PREPARE_CLASS_SUFFIX;
2961
+ $$jqLite.addClass(element, prepareClassName);
2962
+ }
2963
+
2913
2964
  animationQueue.push({
2914
2965
  // this data is used by the postDigest code and passed into
2915
2966
  // the driver step function
@@ -3074,7 +3125,7 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) {
3074
3125
  };
3075
3126
 
3076
3127
  // the anchor animations require that the from and to elements both have at least
3077
- // one shared CSS class which effictively marries the two elements together to use
3128
+ // one shared CSS class which effectively marries the two elements together to use
3078
3129
  // the same animation driver and to properly sequence the anchor animation.
3079
3130
  if (group.classes.length) {
3080
3131
  preparedAnimations.push(group);
@@ -3132,6 +3183,10 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) {
3132
3183
  if (tempClasses) {
3133
3184
  $$jqLite.addClass(element, tempClasses);
3134
3185
  }
3186
+ if (prepareClassName) {
3187
+ $$jqLite.removeClass(element, prepareClassName);
3188
+ prepareClassName = null;
3189
+ }
3135
3190
  }
3136
3191
 
3137
3192
  function updateAnimationRunners(animation, newRunner) {
@@ -3173,12 +3228,121 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) {
3173
3228
  }];
3174
3229
  }];
3175
3230
 
3231
+ /**
3232
+ * @ngdoc directive
3233
+ * @name ngAnimateSwap
3234
+ * @restrict A
3235
+ * @scope
3236
+ *
3237
+ * @description
3238
+ *
3239
+ * ngAnimateSwap is a animation-oriented directive that allows for the container to
3240
+ * be removed and entered in whenever the associated expression changes. A
3241
+ * common usecase for this directive is a rotating banner component which
3242
+ * contains one image being present at a time. When the active image changes
3243
+ * then the old image will perform a `leave` animation and the new element
3244
+ * will be inserted via an `enter` animation.
3245
+ *
3246
+ * @example
3247
+ * <example name="ngAnimateSwap-directive" module="ngAnimateSwapExample"
3248
+ * deps="angular-animate.js"
3249
+ * animations="true" fixBase="true">
3250
+ * <file name="index.html">
3251
+ * <div class="container" ng-controller="AppCtrl">
3252
+ * <div ng-animate-swap="number" class="cell swap-animation" ng-class="colorClass(number)">
3253
+ * {{ number }}
3254
+ * </div>
3255
+ * </div>
3256
+ * </file>
3257
+ * <file name="script.js">
3258
+ * angular.module('ngAnimateSwapExample', ['ngAnimate'])
3259
+ * .controller('AppCtrl', ['$scope', '$interval', function($scope, $interval) {
3260
+ * $scope.number = 0;
3261
+ * $interval(function() {
3262
+ * $scope.number++;
3263
+ * }, 1000);
3264
+ *
3265
+ * var colors = ['red','blue','green','yellow','orange'];
3266
+ * $scope.colorClass = function(number) {
3267
+ * return colors[number % colors.length];
3268
+ * };
3269
+ * }]);
3270
+ * </file>
3271
+ * <file name="animations.css">
3272
+ * .container {
3273
+ * height:250px;
3274
+ * width:250px;
3275
+ * position:relative;
3276
+ * overflow:hidden;
3277
+ * border:2px solid black;
3278
+ * }
3279
+ * .container .cell {
3280
+ * font-size:150px;
3281
+ * text-align:center;
3282
+ * line-height:250px;
3283
+ * position:absolute;
3284
+ * top:0;
3285
+ * left:0;
3286
+ * right:0;
3287
+ * border-bottom:2px solid black;
3288
+ * }
3289
+ * .swap-animation.ng-enter, .swap-animation.ng-leave {
3290
+ * transition:0.5s linear all;
3291
+ * }
3292
+ * .swap-animation.ng-enter {
3293
+ * top:-250px;
3294
+ * }
3295
+ * .swap-animation.ng-enter-active {
3296
+ * top:0px;
3297
+ * }
3298
+ * .swap-animation.ng-leave {
3299
+ * top:0px;
3300
+ * }
3301
+ * .swap-animation.ng-leave-active {
3302
+ * top:250px;
3303
+ * }
3304
+ * .red { background:red; }
3305
+ * .green { background:green; }
3306
+ * .blue { background:blue; }
3307
+ * .yellow { background:yellow; }
3308
+ * .orange { background:orange; }
3309
+ * </file>
3310
+ * </example>
3311
+ */
3312
+ var ngAnimateSwapDirective = ['$animate', '$rootScope', function($animate, $rootScope) {
3313
+ return {
3314
+ restrict: 'A',
3315
+ transclude: 'element',
3316
+ terminal: true,
3317
+ priority: 600, // we use 600 here to ensure that the directive is caught before others
3318
+ link: function(scope, $element, attrs, ctrl, $transclude) {
3319
+ var previousElement, previousScope;
3320
+ scope.$watchCollection(attrs.ngAnimateSwap || attrs['for'], function(value) {
3321
+ if (previousElement) {
3322
+ $animate.leave(previousElement);
3323
+ }
3324
+ if (previousScope) {
3325
+ previousScope.$destroy();
3326
+ previousScope = null;
3327
+ }
3328
+ if (value || value === 0) {
3329
+ previousScope = scope.$new();
3330
+ $transclude(previousScope, function(element) {
3331
+ previousElement = element;
3332
+ $animate.enter(element, null, $element);
3333
+ });
3334
+ }
3335
+ });
3336
+ }
3337
+ };
3338
+ }];
3339
+
3176
3340
  /* global angularAnimateModule: true,
3177
3341
 
3342
+ ngAnimateSwapDirective,
3178
3343
  $$AnimateAsyncRunFactory,
3179
3344
  $$rAFSchedulerFactory,
3180
3345
  $$AnimateChildrenDirective,
3181
- $$AnimateRunnerFactory,
3182
3346
  $$AnimateQueueProvider,
3183
3347
  $$AnimationProvider,
3184
3348
  $AnimateCssProvider,
@@ -3427,11 +3591,39 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) {
3427
3591
  * the CSS class once an animation has completed.)
3428
3592
  *
3429
3593
  *
3594
+ * ### The `ng-[event]-prepare` class
3595
+ *
3596
+ * This is a special class that can be used to prevent unwanted flickering / flash of content before
3597
+ * the actual animation starts. The class is added as soon as an animation is initialized, but removed
3598
+ * before the actual animation starts (after waiting for a $digest).
3599
+ * It is also only added for *structural* animations (`enter`, `move`, and `leave`).
3600
+ *
3601
+ * In practice, flickering can appear when nesting elements with structural animations such as `ngIf`
3602
+ * into elements that have class-based animations such as `ngClass`.
3603
+ *
3604
+ * ```html
3605
+ * <div ng-class="{red: myProp}">
3606
+ * <div ng-class="{blue: myProp}">
3607
+ * <div class="message" ng-if="myProp"></div>
3608
+ * </div>
3609
+ * </div>
3610
+ * ```
3611
+ *
3612
+ * It is possible that during the `enter` animation, the `.message` div will be briefly visible before it starts animating.
3613
+ * In that case, you can add styles to the CSS that make sure the element stays hidden before the animation starts:
3614
+ *
3615
+ * ```css
3616
+ * .message.ng-enter-prepare {
3617
+ * opacity: 0;
3618
+ * }
3619
+ *
3620
+ * ```
3621
+ *
3430
3622
  * ## JavaScript-based Animations
3431
3623
  *
3432
3624
  * ngAnimate also allows for animations to be consumed by JavaScript code. The approach is similar to CSS-based animations (where there is a shared
3433
3625
  * CSS class that is referenced in our HTML code) but in addition we need to register the JavaScript animation on the module. By making use of the
3434
- * `module.animation()` module function we can register the ainmation.
3626
+ * `module.animation()` module function we can register the animation.
3435
3627
  *
3436
3628
  * Let's see an example of a enter/leave animation using `ngRepeat`:
3437
3629
  *
@@ -3911,12 +4103,11 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) {
3911
4103
  * Click here {@link ng.$animate to learn more about animations with `$animate`}.
3912
4104
  */
3913
4105
  angular.module('ngAnimate', [])
4106
+ .directive('ngAnimateSwap', ngAnimateSwapDirective)
4107
+
3914
4108
  .directive('ngAnimateChildren', $$AnimateChildrenDirective)
3915
4109
  .factory('$$rAFScheduler', $$rAFSchedulerFactory)
3916
4110
 
3917
- .factory('$$AnimateRunner', $$AnimateRunnerFactory)
3918
- .factory('$$animateAsyncRun', $$AnimateAsyncRunFactory)
3919
-
3920
4111
  .provider('$$animateQueue', $$AnimateQueueProvider)
3921
4112
  .provider('$$animation', $$AnimationProvider)
3922
4113