angularjs-rails 1.2.26 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (27) hide show
  1. checksums.yaml +4 -4
  2. data/lib/angularjs-rails/version.rb +2 -2
  3. data/vendor/assets/javascripts/angular-animate.js +879 -456
  4. data/vendor/assets/javascripts/angular-aria.js +250 -0
  5. data/vendor/assets/javascripts/angular-cookies.js +1 -1
  6. data/vendor/assets/javascripts/angular-loader.js +17 -10
  7. data/vendor/assets/javascripts/angular-messages.js +400 -0
  8. data/vendor/assets/javascripts/angular-mocks.js +220 -110
  9. data/vendor/assets/javascripts/angular-resource.js +287 -247
  10. data/vendor/assets/javascripts/angular-route.js +111 -54
  11. data/vendor/assets/javascripts/angular-sanitize.js +1 -1
  12. data/vendor/assets/javascripts/angular-scenario.js +11579 -8665
  13. data/vendor/assets/javascripts/angular-touch.js +49 -11
  14. data/vendor/assets/javascripts/angular.js +6660 -3106
  15. data/vendor/assets/javascripts/unstable/angular-animate.js +240 -71
  16. data/vendor/assets/javascripts/unstable/angular-aria.js +12 -12
  17. data/vendor/assets/javascripts/unstable/angular-cookies.js +1 -1
  18. data/vendor/assets/javascripts/unstable/angular-loader.js +4 -4
  19. data/vendor/assets/javascripts/unstable/angular-messages.js +1 -1
  20. data/vendor/assets/javascripts/unstable/angular-mocks.js +21 -14
  21. data/vendor/assets/javascripts/unstable/angular-resource.js +2 -2
  22. data/vendor/assets/javascripts/unstable/angular-route.js +1 -1
  23. data/vendor/assets/javascripts/unstable/angular-sanitize.js +1 -1
  24. data/vendor/assets/javascripts/unstable/angular-scenario.js +562 -262
  25. data/vendor/assets/javascripts/unstable/angular-touch.js +1 -1
  26. data/vendor/assets/javascripts/unstable/angular.js +562 -262
  27. metadata +16 -14
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: edc80934126c7bb8e08926262373f206bddf9ad0
4
- data.tar.gz: 22c50ed2a0f707116afb01e01b7e0290565bebf8
3
+ metadata.gz: a3cd96826a3169a674e33daacf9dee50de68f15f
4
+ data.tar.gz: a8787ed1e78aefc2a82645783b20ae2ab04abe81
5
5
  SHA512:
6
- metadata.gz: 10b94b7f399eef907f3cbbef83bc0ffe1f961334b23b9d498f03f9a213d91577b47cc92f883efe2da1ab762728dbb478ac09845d477ad850f8e1ccf46c04f236
7
- data.tar.gz: 7c4af8a50c83d720781394c49862756896832d3c5dcd66b0eced5934ae95f3ffce1b17cdbb437bc636da2a590c9dccbcb8ce20e78fae3a4c89047b65d390fac4
6
+ metadata.gz: 3b0ea7cfc34fab4d6bb0f4f2cc2cba4ef4bd7d95c12812d6020696f099f92351f0e3ca6a62cded02aa4113c8f0cd52b135cb8f8ee3e9d0114e86c0db44ca819c
7
+ data.tar.gz: fa8f0730137282d6c580e7934e2f8d4bf272bcd70e8d3883194ca5989476865ce9bb2f5043b13d8f30ca2fe8c67936a2612f29e6d6d9ae935a68fa9f2bf1fb55
@@ -1,6 +1,6 @@
1
1
  module AngularJS
2
2
  module Rails
3
- VERSION = "1.2.26"
4
- UNSTABLE_VERSION = "1.3.0-rc.5"
3
+ VERSION = "1.3.0"
4
+ UNSTABLE_VERSION = "1.3.0"
5
5
  end
6
6
  end
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @license AngularJS v1.2.26
2
+ * @license AngularJS v1.3.0
3
3
  * (c) 2010-2014 Google, Inc. http://angularjs.org
4
4
  * License: MIT
5
5
  */
@@ -12,11 +12,8 @@
12
12
  * @name ngAnimate
13
13
  * @description
14
14
  *
15
- * # ngAnimate
16
- *
17
15
  * The `ngAnimate` module provides support for JavaScript, CSS3 transition and CSS3 keyframe animation hooks within existing core and custom directives.
18
16
  *
19
- *
20
17
  * <div doc-module-components="ngAnimate"></div>
21
18
  *
22
19
  * # Usage
@@ -28,17 +25,18 @@
28
25
  *
29
26
  * Below is a more detailed breakdown of the supported animation events provided by pre-existing ng directives:
30
27
  *
31
- * | Directive | Supported Animations |
32
- * |---------------------------------------------------------- |----------------------------------------------------|
33
- * | {@link ng.directive:ngRepeat#usage_animations ngRepeat} | enter, leave and move |
34
- * | {@link ngRoute.directive:ngView#usage_animations ngView} | enter and leave |
35
- * | {@link ng.directive:ngInclude#usage_animations ngInclude} | enter and leave |
36
- * | {@link ng.directive:ngSwitch#usage_animations ngSwitch} | enter and leave |
37
- * | {@link ng.directive:ngIf#usage_animations ngIf} | enter and leave |
38
- * | {@link ng.directive:ngClass#usage_animations ngClass} | add and remove |
39
- * | {@link ng.directive:ngShow#usage_animations ngShow & ngHide} | add and remove (the ng-hide class value) |
40
- * | {@link ng.directive:form#usage_animations form} | add and remove (dirty, pristine, valid, invalid & all other validations) |
41
- * | {@link ng.directive:ngModel#usage_animations ngModel} | add and remove (dirty, pristine, valid, invalid & all other validations) |
28
+ * | Directive | Supported Animations |
29
+ * |----------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------|
30
+ * | {@link ng.directive:ngRepeat#animations ngRepeat} | enter, leave and move |
31
+ * | {@link ngRoute.directive:ngView#animations ngView} | enter and leave |
32
+ * | {@link ng.directive:ngInclude#animations ngInclude} | enter and leave |
33
+ * | {@link ng.directive:ngSwitch#animations ngSwitch} | enter and leave |
34
+ * | {@link ng.directive:ngIf#animations ngIf} | enter and leave |
35
+ * | {@link ng.directive:ngClass#animations ngClass} | add and remove (the CSS class(es) present) |
36
+ * | {@link ng.directive:ngShow#animations ngShow} & {@link ng.directive:ngHide#animations ngHide} | add and remove (the ng-hide class value) |
37
+ * | {@link ng.directive:form#animation-hooks form} & {@link ng.directive:ngModel#animation-hooks ngModel} | add and remove (dirty, pristine, valid, invalid & all other validations) |
38
+ * | {@link module:ngMessages#animations ngMessages} | add and remove (ng-active & ng-inactive) |
39
+ * | {@link module:ngMessages#animations ngMessage} | enter and leave |
42
40
  *
43
41
  * You can find out more information about animations upon visiting each directive page.
44
42
  *
@@ -81,7 +79,17 @@
81
79
  * When the `on` expression value changes and an animation is triggered then each of the elements within
82
80
  * will all animate without the block being applied to child elements.
83
81
  *
84
- * <h2>CSS-defined Animations</h2>
82
+ * ## Are animations run when the application starts?
83
+ * No they are not. When an application is bootstrapped Angular will disable animations from running to avoid
84
+ * a frenzy of animations from being triggered as soon as the browser has rendered the screen. For this to work,
85
+ * Angular will wait for two digest cycles until enabling animations. From there on, any animation-triggering
86
+ * layout changes in the application will trigger animations as normal.
87
+ *
88
+ * In addition, upon bootstrap, if the routing system or any directives or load remote data (via $http) then Angular
89
+ * will automatically extend the wait time to enable animations once **all** of the outbound HTTP requests
90
+ * are complete.
91
+ *
92
+ * ## CSS-defined Animations
85
93
  * The animate service will automatically apply two CSS classes to the animated element and these two CSS classes
86
94
  * are designed to contain the start and end CSS styling. Both CSS transitions and keyframe animations are supported
87
95
  * and can be used to play along with this naming structure.
@@ -150,9 +158,76 @@
150
158
  * immediately resulting in a DOM element that is at its final state. This final state is when the DOM element
151
159
  * has no CSS transition/animation classes applied to it.
152
160
  *
153
- * <h3>CSS Staggering Animations</h3>
161
+ * ### Structural transition animations
162
+ *
163
+ * Structural transitions (such as enter, leave and move) will always apply a `0s none` transition
164
+ * value to force the browser into rendering the styles defined in the setup (.ng-enter, .ng-leave
165
+ * or .ng-move) class. This means that any active transition animations operating on the element
166
+ * will be cut off to make way for the enter, leave or move animation.
167
+ *
168
+ * ### Class-based transition animations
169
+ *
170
+ * Class-based transitions refer to transition animations that are triggered when a CSS class is
171
+ * added to or removed from the element (via `$animate.addClass`, `$animate.removeClass`,
172
+ * `$animate.setClass`, or by directives such as `ngClass`, `ngModel` and `form`).
173
+ * They are different when compared to structural animations since they **do not cancel existing
174
+ * animations** nor do they **block successive transitions** from rendering on the same element.
175
+ * This distinction allows for **multiple class-based transitions** to be performed on the same element.
176
+ *
177
+ * In addition to ngAnimate supporting the default (natural) functionality of class-based transition
178
+ * animations, ngAnimate also decorates the element with starting and ending CSS classes to aid the
179
+ * developer in further styling the element throughout the transition animation. Earlier versions
180
+ * of ngAnimate may have caused natural CSS transitions to break and not render properly due to
181
+ * $animate temporarily blocking transitions using `0s none` in order to allow the setup CSS class
182
+ * (the `-add` or `-remove` class) to be applied without triggering an animation. However, as of
183
+ * **version 1.3**, this workaround has been removed with ngAnimate and all non-ngAnimate CSS
184
+ * class transitions are compatible with ngAnimate.
185
+ *
186
+ * There is, however, one special case when dealing with class-based transitions in ngAnimate.
187
+ * When rendering class-based transitions that make use of the setup and active CSS classes
188
+ * (e.g. `.fade-add` and `.fade-add-active` for when `.fade` is added) be sure to define
189
+ * the transition value **on the active CSS class** and not the setup class.
190
+ *
191
+ * ```css
192
+ * .fade-add {
193
+ * /&#42; remember to place a 0s transition here
194
+ * to ensure that the styles are applied instantly
195
+ * even if the element already has a transition style &#42;/
196
+ * transition:0s linear all;
197
+ *
198
+ * /&#42; starting CSS styles &#42;/
199
+ * opacity:1;
200
+ * }
201
+ * .fade-add.fade-add-active {
202
+ * /&#42; this will be the length of the animation &#42;/
203
+ * transition:1s linear all;
204
+ * opacity:0;
205
+ * }
206
+ * ```
207
+ *
208
+ * The setup CSS class (in this case `.fade-add`) also has a transition style property, however, it
209
+ * has a duration of zero. This may not be required, however, incase the browser is unable to render
210
+ * the styling present in this CSS class instantly then it could be that the browser is attempting
211
+ * to perform an unnecessary transition.
212
+ *
213
+ * This workaround, however, does not apply to standard class-based transitions that are rendered
214
+ * when a CSS class containing a transition is applied to an element:
215
+ *
216
+ * ```css
217
+ * .fade {
218
+ * /&#42; this works as expected &#42;/
219
+ * transition:1s linear all;
220
+ * opacity:0;
221
+ * }
222
+ * ```
223
+ *
224
+ * Please keep this in mind when coding the CSS markup that will be used within class-based transitions.
225
+ * Also, try not to mix the two class-based animation flavors together since the CSS code may become
226
+ * overly complex.
227
+ *
228
+ * ### CSS Staggering Animations
154
229
  * A Staggering animation is a collection of animations that are issued with a slight delay in between each successive operation resulting in a
155
- * curtain-like effect. The ngAnimate module, as of 1.2.0, supports staggering animations and the stagger effect can be
230
+ * curtain-like effect. The ngAnimate module (versions >=1.2) supports staggering animations and the stagger effect can be
156
231
  * performed by creating a **ng-EVENT-stagger** CSS class and attaching that class to the base CSS class used for
157
232
  * the animation. The style property expected within the stagger class can either be a **transition-delay** or an
158
233
  * **animation-delay** property (or both if your animation contains both transitions and keyframe animations).
@@ -205,7 +280,7 @@
205
280
  *
206
281
  * Stagger animations are currently only supported within CSS-defined animations.
207
282
  *
208
- * <h2>JavaScript-defined Animations</h2>
283
+ * ## JavaScript-defined Animations
209
284
  * In the event that you do not want to use CSS3 transitions or CSS3 animations or if you wish to offer animations on browsers that do not
210
285
  * yet support CSS transitions/animations, then you can make use of JavaScript animations defined inside of your AngularJS module.
211
286
  *
@@ -251,6 +326,49 @@
251
326
  * and the JavaScript animation is found, then the enter callback will handle that animation (in addition to the CSS keyframe animation
252
327
  * or transition code that is defined via a stylesheet).
253
328
  *
329
+ *
330
+ * ### Applying Directive-specific Styles to an Animation
331
+ * In some cases a directive or service may want to provide `$animate` with extra details that the animation will
332
+ * include into its animation. Let's say for example we wanted to render an animation that animates an element
333
+ * towards the mouse coordinates as to where the user clicked last. By collecting the X/Y coordinates of the click
334
+ * (via the event parameter) we can set the `top` and `left` styles into an object and pass that into our function
335
+ * call to `$animate.addClass`.
336
+ *
337
+ * ```js
338
+ * canvas.on('click', function(e) {
339
+ * $animate.addClass(element, 'on', {
340
+ * to: {
341
+ * left : e.client.x + 'px',
342
+ * top : e.client.y + 'px'
343
+ * }
344
+ * }):
345
+ * });
346
+ * ```
347
+ *
348
+ * Now when the animation runs, and a transition or keyframe animation is picked up, then the animation itself will
349
+ * also include and transition the styling of the `left` and `top` properties into its running animation. If we want
350
+ * to provide some starting animation values then we can do so by placing the starting animations styles into an object
351
+ * called `from` in the same object as the `to` animations.
352
+ *
353
+ * ```js
354
+ * canvas.on('click', function(e) {
355
+ * $animate.addClass(element, 'on', {
356
+ * from: {
357
+ * position: 'absolute',
358
+ * left: '0px',
359
+ * top: '0px'
360
+ * },
361
+ * to: {
362
+ * left : e.client.x + 'px',
363
+ * top : e.client.y + 'px'
364
+ * }
365
+ * }):
366
+ * });
367
+ * ```
368
+ *
369
+ * Once the animation is complete or cancelled then the union of both the before and after styles are applied to the
370
+ * element. If `ngAnimate` is not present then the styles will be applied immediately.
371
+ *
254
372
  */
255
373
 
256
374
  angular.module('ngAnimate', ['ng'])
@@ -273,7 +391,7 @@ angular.module('ngAnimate', ['ng'])
273
391
  var NG_ANIMATE_CHILDREN = '$$ngAnimateChildren';
274
392
  return function(scope, element, attrs) {
275
393
  var val = attrs.ngAnimateChildren;
276
- if(angular.isString(val) && val.length === 0) { //empty attribute
394
+ if (angular.isString(val) && val.length === 0) { //empty attribute
277
395
  element.data(NG_ANIMATE_CHILDREN, true);
278
396
  } else {
279
397
  scope.$watch(val, function(value) {
@@ -307,6 +425,9 @@ angular.module('ngAnimate', ['ng'])
307
425
  var noop = angular.noop;
308
426
  var forEach = angular.forEach;
309
427
  var selectors = $animateProvider.$$selectors;
428
+ var isArray = angular.isArray;
429
+ var isString = angular.isString;
430
+ var isObject = angular.isObject;
310
431
 
311
432
  var ELEMENT_NODE = 1;
312
433
  var NG_ANIMATE_STATE = '$$ngAnimateState';
@@ -317,7 +438,7 @@ angular.module('ngAnimate', ['ng'])
317
438
  function extractElementNode(element) {
318
439
  for(var i = 0; i < element.length; i++) {
319
440
  var elm = element[i];
320
- if(elm.nodeType == ELEMENT_NODE) {
441
+ if (elm.nodeType == ELEMENT_NODE) {
321
442
  return elm;
322
443
  }
323
444
  }
@@ -335,24 +456,38 @@ angular.module('ngAnimate', ['ng'])
335
456
  return extractElementNode(elm1) == extractElementNode(elm2);
336
457
  }
337
458
 
338
- $provide.decorator('$animate', ['$delegate', '$injector', '$sniffer', '$rootElement', '$$asyncCallback', '$rootScope', '$document',
339
- function($delegate, $injector, $sniffer, $rootElement, $$asyncCallback, $rootScope, $document) {
459
+ $provide.decorator('$animate',
460
+ ['$delegate', '$$q', '$injector', '$sniffer', '$rootElement', '$$asyncCallback', '$rootScope', '$document', '$templateRequest',
461
+ function($delegate, $$q, $injector, $sniffer, $rootElement, $$asyncCallback, $rootScope, $document, $templateRequest) {
340
462
 
341
- var globalAnimationCounter = 0;
342
463
  $rootElement.data(NG_ANIMATE_STATE, rootAnimateState);
343
464
 
344
- // disable animations during bootstrap, but once we bootstrapped, wait again
345
- // for another digest until enabling animations. The reason why we digest twice
346
- // is because all structural animations (enter, leave and move) all perform a
347
- // post digest operation before animating. If we only wait for a single digest
348
- // to pass then the structural animation would render its animation on page load.
349
- // (which is what we're trying to avoid when the application first boots up.)
350
- $rootScope.$$postDigest(function() {
351
- $rootScope.$$postDigest(function() {
352
- rootAnimateState.running = false;
353
- });
354
- });
465
+ // Wait until all directive and route-related templates are downloaded and
466
+ // compiled. The $templateRequest.totalPendingRequests variable keeps track of
467
+ // all of the remote templates being currently downloaded. If there are no
468
+ // templates currently downloading then the watcher will still fire anyway.
469
+ var deregisterWatch = $rootScope.$watch(
470
+ function() { return $templateRequest.totalPendingRequests; },
471
+ function(val, oldVal) {
472
+ if (val !== 0) return;
473
+ deregisterWatch();
474
+
475
+ // Now that all templates have been downloaded, $animate will wait until
476
+ // the post digest queue is empty before enabling animations. By having two
477
+ // calls to $postDigest calls we can ensure that the flag is enabled at the
478
+ // very end of the post digest queue. Since all of the animations in $animate
479
+ // use $postDigest, it's important that the code below executes at the end.
480
+ // This basically means that the page is fully downloaded and compiled before
481
+ // any animations are triggered.
482
+ $rootScope.$$postDigest(function() {
483
+ $rootScope.$$postDigest(function() {
484
+ rootAnimateState.running = false;
485
+ });
486
+ });
487
+ }
488
+ );
355
489
 
490
+ var globalAnimationCounter = 0;
356
491
  var classNameFilter = $animateProvider.classNameFilter();
357
492
  var isAnimatableClassName = !classNameFilter
358
493
  ? function() { return true; }
@@ -360,10 +495,83 @@ angular.module('ngAnimate', ['ng'])
360
495
  return classNameFilter.test(className);
361
496
  };
362
497
 
363
- function blockElementAnimations(element) {
498
+ function classBasedAnimationsBlocked(element, setter) {
364
499
  var data = element.data(NG_ANIMATE_STATE) || {};
365
- data.running = true;
366
- element.data(NG_ANIMATE_STATE, data);
500
+ if (setter) {
501
+ data.running = true;
502
+ data.structural = true;
503
+ element.data(NG_ANIMATE_STATE, data);
504
+ }
505
+ return data.disabled || (data.running && data.structural);
506
+ }
507
+
508
+ function runAnimationPostDigest(fn) {
509
+ var cancelFn, defer = $$q.defer();
510
+ defer.promise.$$cancelFn = function() {
511
+ cancelFn && cancelFn();
512
+ };
513
+ $rootScope.$$postDigest(function() {
514
+ cancelFn = fn(function() {
515
+ defer.resolve();
516
+ });
517
+ });
518
+ return defer.promise;
519
+ }
520
+
521
+ function parseAnimateOptions(options) {
522
+ // some plugin code may still be passing in the callback
523
+ // function as the last param for the $animate methods so
524
+ // it's best to only allow string or array values for now
525
+ if (isObject(options)) {
526
+ if (options.tempClasses && isString(options.tempClasses)) {
527
+ options.tempClasses = options.tempClasses.split(/\s+/);
528
+ }
529
+ return options;
530
+ }
531
+ }
532
+
533
+ function resolveElementClasses(element, cache, runningAnimations) {
534
+ runningAnimations = runningAnimations || {};
535
+
536
+ var lookup = {};
537
+ forEach(runningAnimations, function(data, selector) {
538
+ forEach(selector.split(' '), function(s) {
539
+ lookup[s]=data;
540
+ });
541
+ });
542
+
543
+ var hasClasses = Object.create(null);
544
+ forEach((element.attr('class') || '').split(/\s+/), function(className) {
545
+ hasClasses[className] = true;
546
+ });
547
+
548
+ var toAdd = [], toRemove = [];
549
+ forEach(cache.classes, function(status, className) {
550
+ var hasClass = hasClasses[className];
551
+ var matchingAnimation = lookup[className] || {};
552
+
553
+ // When addClass and removeClass is called then $animate will check to
554
+ // see if addClass and removeClass cancel each other out. When there are
555
+ // more calls to removeClass than addClass then the count falls below 0
556
+ // and then the removeClass animation will be allowed. Otherwise if the
557
+ // count is above 0 then that means an addClass animation will commence.
558
+ // Once an animation is allowed then the code will also check to see if
559
+ // there exists any on-going animation that is already adding or remvoing
560
+ // the matching CSS class.
561
+ if (status === false) {
562
+ //does it have the class or will it have the class
563
+ if (hasClass || matchingAnimation.event == 'addClass') {
564
+ toRemove.push(className);
565
+ }
566
+ } else if (status === true) {
567
+ //is the class missing or will it be removed?
568
+ if (!hasClass || matchingAnimation.event == 'removeClass') {
569
+ toAdd.push(className);
570
+ }
571
+ }
572
+ });
573
+
574
+ return (toAdd.length + toRemove.length) > 0 && [toAdd.join(' '), toRemove.join(' ')];
367
575
  }
368
576
 
369
577
  function lookup(name) {
@@ -387,7 +595,7 @@ angular.module('ngAnimate', ['ng'])
387
595
  for(var i=0; i < classes.length; i++) {
388
596
  var klass = classes[i],
389
597
  selectorFactoryName = selectors[klass];
390
- if(selectorFactoryName && !flagMap[klass]) {
598
+ if (selectorFactoryName && !flagMap[klass]) {
391
599
  matches.push($injector.get(selectorFactoryName));
392
600
  flagMap[klass] = true;
393
601
  }
@@ -396,29 +604,44 @@ angular.module('ngAnimate', ['ng'])
396
604
  }
397
605
  }
398
606
 
399
- function animationRunner(element, animationEvent, className) {
607
+ function animationRunner(element, animationEvent, className, options) {
400
608
  //transcluded directives may sometimes fire an animation using only comment nodes
401
609
  //best to catch this early on to prevent any animation operations from occurring
402
610
  var node = element[0];
403
- if(!node) {
611
+ if (!node) {
404
612
  return;
405
613
  }
406
614
 
407
- var isSetClassOperation = animationEvent == 'setClass';
408
- var isClassBased = isSetClassOperation ||
409
- animationEvent == 'addClass' ||
410
- animationEvent == 'removeClass';
615
+ if (options) {
616
+ options.to = options.to || {};
617
+ options.from = options.from || {};
618
+ }
411
619
 
412
- var classNameAdd, classNameRemove;
413
- if(angular.isArray(className)) {
620
+ var classNameAdd;
621
+ var classNameRemove;
622
+ if (isArray(className)) {
414
623
  classNameAdd = className[0];
415
624
  classNameRemove = className[1];
416
- className = classNameAdd + ' ' + classNameRemove;
625
+ if (!classNameAdd) {
626
+ className = classNameRemove;
627
+ animationEvent = 'removeClass';
628
+ } else if (!classNameRemove) {
629
+ className = classNameAdd;
630
+ animationEvent = 'addClass';
631
+ } else {
632
+ className = classNameAdd + ' ' + classNameRemove;
633
+ }
417
634
  }
418
635
 
636
+ var isSetClassOperation = animationEvent == 'setClass';
637
+ var isClassBased = isSetClassOperation
638
+ || animationEvent == 'addClass'
639
+ || animationEvent == 'removeClass'
640
+ || animationEvent == 'animate';
641
+
419
642
  var currentClassName = element.attr('class');
420
643
  var classes = currentClassName + ' ' + className;
421
- if(!isAnimatableClassName(classes)) {
644
+ if (!isAnimatableClassName(classes)) {
422
645
  return;
423
646
  }
424
647
 
@@ -432,7 +655,7 @@ angular.module('ngAnimate', ['ng'])
432
655
  var animationLookup = (' ' + classes).replace(/\s+/g,'.');
433
656
  forEach(lookup(animationLookup), function(animationFactory) {
434
657
  var created = registerAnimation(animationFactory, animationEvent);
435
- if(!created && isSetClassOperation) {
658
+ if (!created && isSetClassOperation) {
436
659
  registerAnimation(animationFactory, 'addClass');
437
660
  registerAnimation(animationFactory, 'removeClass');
438
661
  }
@@ -441,8 +664,8 @@ angular.module('ngAnimate', ['ng'])
441
664
  function registerAnimation(animationFactory, event) {
442
665
  var afterFn = animationFactory[event];
443
666
  var beforeFn = animationFactory['before' + event.charAt(0).toUpperCase() + event.substr(1)];
444
- if(afterFn || beforeFn) {
445
- if(event == 'leave') {
667
+ if (afterFn || beforeFn) {
668
+ if (event == 'leave') {
446
669
  beforeFn = afterFn;
447
670
  //when set as null then animation knows to skip this phase
448
671
  afterFn = null;
@@ -465,9 +688,9 @@ angular.module('ngAnimate', ['ng'])
465
688
 
466
689
  var count = 0;
467
690
  function afterAnimationComplete(index) {
468
- if(cancellations) {
691
+ if (cancellations) {
469
692
  (cancellations[index] || noop)();
470
- if(++count < animations.length) return;
693
+ if (++count < animations.length) return;
471
694
  cancellations = null;
472
695
  }
473
696
  allCompleteFn();
@@ -482,21 +705,24 @@ angular.module('ngAnimate', ['ng'])
482
705
  };
483
706
  switch(animation.event) {
484
707
  case 'setClass':
485
- cancellations.push(animation.fn(element, classNameAdd, classNameRemove, progress));
708
+ cancellations.push(animation.fn(element, classNameAdd, classNameRemove, progress, options));
709
+ break;
710
+ case 'animate':
711
+ cancellations.push(animation.fn(element, className, options.from, options.to, progress));
486
712
  break;
487
713
  case 'addClass':
488
- cancellations.push(animation.fn(element, classNameAdd || className, progress));
714
+ cancellations.push(animation.fn(element, classNameAdd || className, progress, options));
489
715
  break;
490
716
  case 'removeClass':
491
- cancellations.push(animation.fn(element, classNameRemove || className, progress));
717
+ cancellations.push(animation.fn(element, classNameRemove || className, progress, options));
492
718
  break;
493
719
  default:
494
- cancellations.push(animation.fn(element, progress));
720
+ cancellations.push(animation.fn(element, progress, options));
495
721
  break;
496
722
  }
497
723
  });
498
724
 
499
- if(cancellations && cancellations.length === 0) {
725
+ if (cancellations && cancellations.length === 0) {
500
726
  allCompleteFn();
501
727
  }
502
728
  }
@@ -507,6 +733,11 @@ angular.module('ngAnimate', ['ng'])
507
733
  className : className,
508
734
  isClassBased : isClassBased,
509
735
  isSetClassOperation : isSetClassOperation,
736
+ applyStyles : function() {
737
+ if (options) {
738
+ element.css(angular.extend(options.from || {}, options.to || {}));
739
+ }
740
+ },
510
741
  before : function(allCompleteFn) {
511
742
  beforeComplete = allCompleteFn;
512
743
  run(before, beforeCancel, function() {
@@ -522,13 +753,13 @@ angular.module('ngAnimate', ['ng'])
522
753
  });
523
754
  },
524
755
  cancel : function() {
525
- if(beforeCancel) {
756
+ if (beforeCancel) {
526
757
  forEach(beforeCancel, function(cancelFn) {
527
758
  (cancelFn || noop)(true);
528
759
  });
529
760
  beforeComplete(true);
530
761
  }
531
- if(afterCancel) {
762
+ if (afterCancel) {
532
763
  forEach(afterCancel, function(cancelFn) {
533
764
  (cancelFn || noop)(true);
534
765
  });
@@ -541,7 +772,7 @@ angular.module('ngAnimate', ['ng'])
541
772
  /**
542
773
  * @ngdoc service
543
774
  * @name $animate
544
- * @kind function
775
+ * @kind object
545
776
  *
546
777
  * @description
547
778
  * The `$animate` service provides animation detection support while performing DOM operations (enter, leave and move) as well as during addClass and removeClass operations.
@@ -555,9 +786,108 @@ angular.module('ngAnimate', ['ng'])
555
786
  * Requires the {@link ngAnimate `ngAnimate`} module to be installed.
556
787
  *
557
788
  * Please visit the {@link ngAnimate `ngAnimate`} module overview page learn more about how to use animations in your application.
789
+ * ## Callback Promises
790
+ * With AngularJS 1.3, each of the animation methods, on the `$animate` service, return a promise when called. The
791
+ * promise itself is then resolved once the animation has completed itself, has been cancelled or has been
792
+ * skipped due to animations being disabled. (Note that even if the animation is cancelled it will still
793
+ * call the resolve function of the animation.)
794
+ *
795
+ * ```js
796
+ * $animate.enter(element, container).then(function() {
797
+ * //...this is called once the animation is complete...
798
+ * });
799
+ * ```
800
+ *
801
+ * Also note that, due to the nature of the callback promise, if any Angular-specific code (like changing the scope,
802
+ * location of the page, etc...) is executed within the callback promise then be sure to wrap the code using
803
+ * `$scope.$apply(...)`;
804
+ *
805
+ * ```js
806
+ * $animate.leave(element).then(function() {
807
+ * $scope.$apply(function() {
808
+ * $location.path('/new-page');
809
+ * });
810
+ * });
811
+ * ```
812
+ *
813
+ * An animation can also be cancelled by calling the `$animate.cancel(promise)` method with the provided
814
+ * promise that was returned when the animation was started.
815
+ *
816
+ * ```js
817
+ * var promise = $animate.addClass(element, 'super-long-animation').then(function() {
818
+ * //this will still be called even if cancelled
819
+ * });
820
+ *
821
+ * element.on('click', function() {
822
+ * //tooo lazy to wait for the animation to end
823
+ * $animate.cancel(promise);
824
+ * });
825
+ * ```
826
+ *
827
+ * (Keep in mind that the promise cancellation is unique to `$animate` since promises in
828
+ * general cannot be cancelled.)
558
829
  *
559
830
  */
560
831
  return {
832
+ /**
833
+ * @ngdoc method
834
+ * @name $animate#animate
835
+ * @kind function
836
+ *
837
+ * @description
838
+ * Performs an inline animation on the element which applies the provided `to` and `from` CSS styles to the element.
839
+ * If any detected CSS transition, keyframe or JavaScript matches the provided `className` value then the animation
840
+ * will take on the provided styles. For example, if a transition animation is set for the given className then the
841
+ * provided `from` and `to` styles will be applied alongside the given transition. If a JavaScript animation is
842
+ * detected then the provided styles will be given in as function paramters.
843
+ *
844
+ * ```js
845
+ * ngModule.animation('.my-inline-animation', function() {
846
+ * return {
847
+ * animate : function(element, className, from, to, done) {
848
+ * //styles
849
+ * }
850
+ * }
851
+ * });
852
+ * ```
853
+ *
854
+ * Below is a breakdown of each step that occurs during the `animate` animation:
855
+ *
856
+ * | Animation Step | What the element class attribute looks like |
857
+ * |-------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------|
858
+ * | 1. $animate.animate(...) is called | class="my-animation" |
859
+ * | 2. $animate waits for the next digest to start the animation | class="my-animation ng-animate" |
860
+ * | 3. $animate runs the JavaScript-defined animations detected on the element | class="my-animation ng-animate" |
861
+ * | 4. the className class value is added to the element | class="my-animation ng-animate className" |
862
+ * | 5. $animate scans the element styles to get the CSS transition/animation duration and delay | class="my-animation ng-animate className" |
863
+ * | 6. $animate blocks all CSS transitions on the element to ensure the .className class styling is applied right away| class="my-animation ng-animate className" |
864
+ * | 7. $animate applies the provided collection of `from` CSS styles to the element | class="my-animation ng-animate className" |
865
+ * | 8. $animate waits for a single animation frame (this performs a reflow) | class="my-animation ng-animate className" |
866
+ * | 9. $animate removes the CSS transition block placed on the element | class="my-animation ng-animate className" |
867
+ * | 10. the className-active class is added (this triggers the CSS transition/animation) | class="my-animation ng-animate className className-active" |
868
+ * | 11. $animate applies the collection of `to` CSS styles to the element which are then handled by the transition | class="my-animation ng-animate className className-active" |
869
+ * | 12. $animate waits for the animation to complete (via events and timeout) | class="my-animation ng-animate className className-active" |
870
+ * | 13. The animation ends and all generated CSS classes are removed from the element | class="my-animation" |
871
+ * | 14. The returned promise is resolved. | class="my-animation" |
872
+ *
873
+ * @param {DOMElement} element the element that will be the focus of the enter animation
874
+ * @param {object} from a collection of CSS styles that will be applied to the element at the start of the animation
875
+ * @param {object} to a collection of CSS styles that the element will animate towards
876
+ * @param {string=} className an optional CSS class that will be added to the element for the duration of the animation (the default class is `ng-inline-animate`)
877
+ * @param {object=} options an optional collection of options that will be picked up by the CSS transition/animation
878
+ * @return {Promise} the animation callback promise
879
+ */
880
+ animate : function(element, from, to, className, options) {
881
+ className = className || 'ng-inline-animate';
882
+ options = parseAnimateOptions(options) || {};
883
+ options.from = to ? from : null;
884
+ options.to = to ? to : from;
885
+
886
+ return runAnimationPostDigest(function(done) {
887
+ return performAnimation('animate', className, stripCommentsFromElement(element), null, null, noop, options, done);
888
+ });
889
+ },
890
+
561
891
  /**
562
892
  * @ngdoc method
563
893
  * @name $animate#enter
@@ -569,34 +899,38 @@ angular.module('ngAnimate', ['ng'])
569
899
  *
570
900
  * Below is a breakdown of each step that occurs during enter animation:
571
901
  *
572
- * | Animation Step | What the element class attribute looks like |
573
- * |----------------------------------------------------------------------------------------------|---------------------------------------------|
574
- * | 1. $animate.enter(...) is called | class="my-animation" |
575
- * | 2. element is inserted into the parentElement element or beside the afterElement element | class="my-animation" |
576
- * | 3. $animate runs any JavaScript-defined animations on the element | class="my-animation ng-animate" |
577
- * | 4. the .ng-enter class is added to the element | class="my-animation ng-animate ng-enter" |
578
- * | 5. $animate scans the element styles to get the CSS transition/animation duration and delay | class="my-animation ng-animate ng-enter" |
579
- * | 6. $animate waits for 10ms (this performs a reflow) | class="my-animation ng-animate ng-enter" |
580
- * | 7. the .ng-enter-active and .ng-animate-active classes are added (this triggers the CSS transition/animation) | class="my-animation ng-animate ng-animate-active ng-enter ng-enter-active" |
581
- * | 8. $animate waits for X milliseconds for the animation to complete | class="my-animation ng-animate ng-animate-active ng-enter ng-enter-active" |
582
- * | 9. The animation ends and all generated CSS classes are removed from the element | class="my-animation" |
583
- * | 10. The doneCallback() callback is fired (if provided) | class="my-animation" |
902
+ * | Animation Step | What the element class attribute looks like |
903
+ * |-------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------|
904
+ * | 1. $animate.enter(...) is called | class="my-animation" |
905
+ * | 2. element is inserted into the parentElement element or beside the afterElement element | class="my-animation" |
906
+ * | 3. $animate waits for the next digest to start the animation | class="my-animation ng-animate" |
907
+ * | 4. $animate runs the JavaScript-defined animations detected on the element | class="my-animation ng-animate" |
908
+ * | 5. the .ng-enter class is added to the element | class="my-animation ng-animate ng-enter" |
909
+ * | 6. $animate scans the element styles to get the CSS transition/animation duration and delay | class="my-animation ng-animate ng-enter" |
910
+ * | 7. $animate blocks all CSS transitions on the element to ensure the .ng-enter class styling is applied right away | class="my-animation ng-animate ng-enter" |
911
+ * | 8. $animate waits for a single animation frame (this performs a reflow) | class="my-animation ng-animate ng-enter" |
912
+ * | 9. $animate removes the CSS transition block placed on the element | class="my-animation ng-animate ng-enter" |
913
+ * | 10. the .ng-enter-active class is added (this triggers the CSS transition/animation) | class="my-animation ng-animate ng-enter ng-enter-active" |
914
+ * | 11. $animate waits for the animation to complete (via events and timeout) | class="my-animation ng-animate ng-enter ng-enter-active" |
915
+ * | 12. The animation ends and all generated CSS classes are removed from the element | class="my-animation" |
916
+ * | 13. The returned promise is resolved. | class="my-animation" |
584
917
  *
585
918
  * @param {DOMElement} element the element that will be the focus of the enter animation
586
919
  * @param {DOMElement} parentElement the parent element of the element that will be the focus of the enter animation
587
920
  * @param {DOMElement} afterElement the sibling element (which is the previous element) of the element that will be the focus of the enter animation
588
- * @param {function()=} doneCallback the callback function that will be called once the animation is complete
921
+ * @param {object=} options an optional collection of options that will be picked up by the CSS transition/animation
922
+ * @return {Promise} the animation callback promise
589
923
  */
590
- enter : function(element, parentElement, afterElement, doneCallback) {
924
+ enter : function(element, parentElement, afterElement, options) {
925
+ options = parseAnimateOptions(options);
591
926
  element = angular.element(element);
592
927
  parentElement = prepareElement(parentElement);
593
928
  afterElement = prepareElement(afterElement);
594
929
 
595
- blockElementAnimations(element);
930
+ classBasedAnimationsBlocked(element, true);
596
931
  $delegate.enter(element, parentElement, afterElement);
597
- $rootScope.$$postDigest(function() {
598
- element = stripCommentsFromElement(element);
599
- performAnimation('enter', 'ng-enter', element, parentElement, afterElement, noop, doneCallback);
932
+ return runAnimationPostDigest(function(done) {
933
+ return performAnimation('enter', 'ng-enter', stripCommentsFromElement(element), parentElement, afterElement, noop, options, done);
600
934
  });
601
935
  },
602
936
 
@@ -611,30 +945,36 @@ angular.module('ngAnimate', ['ng'])
611
945
  *
612
946
  * Below is a breakdown of each step that occurs during leave animation:
613
947
  *
614
- * | Animation Step | What the element class attribute looks like |
615
- * |----------------------------------------------------------------------------------------------|---------------------------------------------|
616
- * | 1. $animate.leave(...) is called | class="my-animation" |
617
- * | 2. $animate runs any JavaScript-defined animations on the element | class="my-animation ng-animate" |
618
- * | 3. the .ng-leave class is added to the element | class="my-animation ng-animate ng-leave" |
619
- * | 4. $animate scans the element styles to get the CSS transition/animation duration and delay | class="my-animation ng-animate ng-leave" |
620
- * | 5. $animate waits for 10ms (this performs a reflow) | class="my-animation ng-animate ng-leave" |
621
- * | 6. the .ng-leave-active and .ng-animate-active classes is added (this triggers the CSS transition/animation) | class="my-animation ng-animate ng-animate-active ng-leave ng-leave-active" |
622
- * | 7. $animate waits for X milliseconds for the animation to complete | class="my-animation ng-animate ng-animate-active ng-leave ng-leave-active" |
623
- * | 8. The animation ends and all generated CSS classes are removed from the element | class="my-animation" |
624
- * | 9. The element is removed from the DOM | ... |
625
- * | 10. The doneCallback() callback is fired (if provided) | ... |
948
+ * | Animation Step | What the element class attribute looks like |
949
+ * |-------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------|
950
+ * | 1. $animate.leave(...) is called | class="my-animation" |
951
+ * | 2. $animate runs the JavaScript-defined animations detected on the element | class="my-animation ng-animate" |
952
+ * | 3. $animate waits for the next digest to start the animation | class="my-animation ng-animate" |
953
+ * | 4. the .ng-leave class is added to the element | class="my-animation ng-animate ng-leave" |
954
+ * | 5. $animate scans the element styles to get the CSS transition/animation duration and delay | class="my-animation ng-animate ng-leave" |
955
+ * | 6. $animate blocks all CSS transitions on the element to ensure the .ng-leave class styling is applied right away | class="my-animation ng-animate ng-leave|
956
+ * | 7. $animate waits for a single animation frame (this performs a reflow) | class="my-animation ng-animate ng-leave" |
957
+ * | 8. $animate removes the CSS transition block placed on the element | class="my-animation ng-animate ng-leave” |
958
+ * | 9. the .ng-leave-active class is added (this triggers the CSS transition/animation) | class="my-animation ng-animate ng-leave ng-leave-active" |
959
+ * | 10. $animate waits for the animation to complete (via events and timeout) | class="my-animation ng-animate ng-leave ng-leave-active" |
960
+ * | 11. The animation ends and all generated CSS classes are removed from the element | class="my-animation" |
961
+ * | 12. The element is removed from the DOM | ... |
962
+ * | 13. The returned promise is resolved. | ... |
626
963
  *
627
964
  * @param {DOMElement} element the element that will be the focus of the leave animation
628
- * @param {function()=} doneCallback the callback function that will be called once the animation is complete
965
+ * @param {object=} options an optional collection of styles that will be picked up by the CSS transition/animation
966
+ * @return {Promise} the animation callback promise
629
967
  */
630
- leave : function(element, doneCallback) {
968
+ leave : function(element, options) {
969
+ options = parseAnimateOptions(options);
631
970
  element = angular.element(element);
971
+
632
972
  cancelChildAnimations(element);
633
- blockElementAnimations(element);
634
- $rootScope.$$postDigest(function() {
635
- performAnimation('leave', 'ng-leave', stripCommentsFromElement(element), null, null, function() {
973
+ classBasedAnimationsBlocked(element, true);
974
+ return runAnimationPostDigest(function(done) {
975
+ return performAnimation('leave', 'ng-leave', stripCommentsFromElement(element), null, null, function() {
636
976
  $delegate.leave(element);
637
- }, doneCallback);
977
+ }, options, done);
638
978
  });
639
979
  },
640
980
 
@@ -650,35 +990,39 @@ angular.module('ngAnimate', ['ng'])
650
990
  *
651
991
  * Below is a breakdown of each step that occurs during move animation:
652
992
  *
653
- * | Animation Step | What the element class attribute looks like |
654
- * |----------------------------------------------------------------------------------------------|---------------------------------------------|
655
- * | 1. $animate.move(...) is called | class="my-animation" |
656
- * | 2. element is moved into the parentElement element or beside the afterElement element | class="my-animation" |
657
- * | 3. $animate runs any JavaScript-defined animations on the element | class="my-animation ng-animate" |
658
- * | 4. the .ng-move class is added to the element | class="my-animation ng-animate ng-move" |
659
- * | 5. $animate scans the element styles to get the CSS transition/animation duration and delay | class="my-animation ng-animate ng-move" |
660
- * | 6. $animate waits for 10ms (this performs a reflow) | class="my-animation ng-animate ng-move" |
661
- * | 7. the .ng-move-active and .ng-animate-active classes is added (this triggers the CSS transition/animation) | class="my-animation ng-animate ng-animate-active ng-move ng-move-active" |
662
- * | 8. $animate waits for X milliseconds for the animation to complete | class="my-animation ng-animate ng-animate-active ng-move ng-move-active" |
663
- * | 9. The animation ends and all generated CSS classes are removed from the element | class="my-animation" |
664
- * | 10. The doneCallback() callback is fired (if provided) | class="my-animation" |
993
+ * | Animation Step | What the element class attribute looks like |
994
+ * |------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------|
995
+ * | 1. $animate.move(...) is called | class="my-animation" |
996
+ * | 2. element is moved into the parentElement element or beside the afterElement element | class="my-animation" |
997
+ * | 3. $animate waits for the next digest to start the animation | class="my-animation ng-animate" |
998
+ * | 4. $animate runs the JavaScript-defined animations detected on the element | class="my-animation ng-animate" |
999
+ * | 5. the .ng-move class is added to the element | class="my-animation ng-animate ng-move" |
1000
+ * | 6. $animate scans the element styles to get the CSS transition/animation duration and delay | class="my-animation ng-animate ng-move" |
1001
+ * | 7. $animate blocks all CSS transitions on the element to ensure the .ng-move class styling is applied right away | class="my-animation ng-animate ng-move|
1002
+ * | 8. $animate waits for a single animation frame (this performs a reflow) | class="my-animation ng-animate ng-move" |
1003
+ * | 9. $animate removes the CSS transition block placed on the element | class="my-animation ng-animate ng-move” |
1004
+ * | 10. the .ng-move-active class is added (this triggers the CSS transition/animation) | class="my-animation ng-animate ng-move ng-move-active" |
1005
+ * | 11. $animate waits for the animation to complete (via events and timeout) | class="my-animation ng-animate ng-move ng-move-active" |
1006
+ * | 12. The animation ends and all generated CSS classes are removed from the element | class="my-animation" |
1007
+ * | 13. The returned promise is resolved. | class="my-animation" |
665
1008
  *
666
1009
  * @param {DOMElement} element the element that will be the focus of the move animation
667
1010
  * @param {DOMElement} parentElement the parentElement element of the element that will be the focus of the move animation
668
1011
  * @param {DOMElement} afterElement the sibling element (which is the previous element) of the element that will be the focus of the move animation
669
- * @param {function()=} doneCallback the callback function that will be called once the animation is complete
1012
+ * @param {object=} options an optional collection of styles that will be picked up by the CSS transition/animation
1013
+ * @return {Promise} the animation callback promise
670
1014
  */
671
- move : function(element, parentElement, afterElement, doneCallback) {
1015
+ move : function(element, parentElement, afterElement, options) {
1016
+ options = parseAnimateOptions(options);
672
1017
  element = angular.element(element);
673
1018
  parentElement = prepareElement(parentElement);
674
1019
  afterElement = prepareElement(afterElement);
675
1020
 
676
1021
  cancelChildAnimations(element);
677
- blockElementAnimations(element);
1022
+ classBasedAnimationsBlocked(element, true);
678
1023
  $delegate.move(element, parentElement, afterElement);
679
- $rootScope.$$postDigest(function() {
680
- element = stripCommentsFromElement(element);
681
- performAnimation('move', 'ng-move', element, parentElement, afterElement, noop, doneCallback);
1024
+ return runAnimationPostDigest(function(done) {
1025
+ return performAnimation('move', 'ng-move', stripCommentsFromElement(element), parentElement, afterElement, noop, options, done);
682
1026
  });
683
1027
  },
684
1028
 
@@ -690,33 +1034,30 @@ angular.module('ngAnimate', ['ng'])
690
1034
  * Triggers a custom animation event based off the className variable and then attaches the className value to the element as a CSS class.
691
1035
  * Unlike the other animation methods, the animate service will suffix the className value with {@type -add} in order to provide
692
1036
  * the animate service the setup and active CSS classes in order to trigger the animation (this will be skipped if no CSS transitions
693
- * or keyframes are defined on the -add or base CSS class).
1037
+ * or keyframes are defined on the -add-active or base CSS class).
694
1038
  *
695
1039
  * Below is a breakdown of each step that occurs during addClass animation:
696
1040
  *
697
- * | Animation Step | What the element class attribute looks like |
698
- * |------------------------------------------------------------------------------------------------|---------------------------------------------|
699
- * | 1. $animate.addClass(element, 'super') is called | class="my-animation" |
700
- * | 2. $animate runs any JavaScript-defined animations on the element | class="my-animation ng-animate" |
701
- * | 3. the .super-add class are added to the element | class="my-animation ng-animate super-add" |
702
- * | 4. $animate scans the element styles to get the CSS transition/animation duration and delay | class="my-animation ng-animate super-add" |
703
- * | 5. $animate waits for 10ms (this performs a reflow) | class="my-animation ng-animate super-add" |
704
- * | 6. the .super, .super-add-active and .ng-animate-active classes are added (this triggers the CSS transition/animation) | class="my-animation ng-animate ng-animate-active super super-add super-add-active" |
705
- * | 7. $animate waits for X milliseconds for the animation to complete | class="my-animation super super-add super-add-active" |
706
- * | 8. The animation ends and all generated CSS classes are removed from the element | class="my-animation super" |
707
- * | 9. The super class is kept on the element | class="my-animation super" |
708
- * | 10. The doneCallback() callback is fired (if provided) | class="my-animation super" |
1041
+ * | Animation Step | What the element class attribute looks like |
1042
+ * |----------------------------------------------------------------------------------------------------|------------------------------------------------------------------|
1043
+ * | 1. $animate.addClass(element, 'super') is called | class="my-animation" |
1044
+ * | 2. $animate runs the JavaScript-defined animations detected on the element | class="my-animation ng-animate" |
1045
+ * | 3. the .super-add class is added to the element | class="my-animation ng-animate super-add" |
1046
+ * | 4. $animate waits for a single animation frame (this performs a reflow) | class="my-animation ng-animate super-add" |
1047
+ * | 5. the .super and .super-add-active classes are added (this triggers the CSS transition/animation) | class="my-animation ng-animate super super-add super-add-active" |
1048
+ * | 6. $animate scans the element styles to get the CSS transition/animation duration and delay | class="my-animation ng-animate super-add" |
1049
+ * | 7. $animate waits for the animation to complete (via events and timeout) | class="my-animation super super-add super-add-active" |
1050
+ * | 8. The animation ends and all generated CSS classes are removed from the element | class="my-animation super" |
1051
+ * | 9. The super class is kept on the element | class="my-animation super" |
1052
+ * | 10. The returned promise is resolved. | class="my-animation super" |
709
1053
  *
710
1054
  * @param {DOMElement} element the element that will be animated
711
1055
  * @param {string} className the CSS class that will be added to the element and then animated
712
- * @param {function()=} doneCallback the callback function that will be called once the animation is complete
1056
+ * @param {object=} options an optional collection of styles that will be picked up by the CSS transition/animation
1057
+ * @return {Promise} the animation callback promise
713
1058
  */
714
- addClass : function(element, className, doneCallback) {
715
- element = angular.element(element);
716
- element = stripCommentsFromElement(element);
717
- performAnimation('addClass', className, element, null, null, function() {
718
- $delegate.addClass(element, className);
719
- }, doneCallback);
1059
+ addClass : function(element, className, options) {
1060
+ return this.setClass(element, className, [], options);
720
1061
  },
721
1062
 
722
1063
  /**
@@ -731,51 +1072,141 @@ angular.module('ngAnimate', ['ng'])
731
1072
  *
732
1073
  * Below is a breakdown of each step that occurs during removeClass animation:
733
1074
  *
734
- * | Animation Step | What the element class attribute looks like |
735
- * |-----------------------------------------------------------------------------------------------|---------------------------------------------|
736
- * | 1. $animate.removeClass(element, 'super') is called | class="my-animation super" |
737
- * | 2. $animate runs any JavaScript-defined animations on the element | class="my-animation super ng-animate" |
738
- * | 3. the .super-remove class are added to the element | class="my-animation super ng-animate super-remove"|
739
- * | 4. $animate scans the element styles to get the CSS transition/animation duration and delay | class="my-animation super ng-animate super-remove" |
740
- * | 5. $animate waits for 10ms (this performs a reflow) | class="my-animation super ng-animate super-remove" |
741
- * | 6. the .super-remove-active and .ng-animate-active classes are added and .super is removed (this triggers the CSS transition/animation) | class="my-animation ng-animate ng-animate-active super-remove super-remove-active" |
742
- * | 7. $animate waits for X milliseconds for the animation to complete | class="my-animation ng-animate ng-animate-active super-remove super-remove-active" |
743
- * | 8. The animation ends and all generated CSS classes are removed from the element | class="my-animation" |
744
- * | 9. The doneCallback() callback is fired (if provided) | class="my-animation" |
1075
+ * | Animation Step | What the element class attribute looks like |
1076
+ * |------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------|
1077
+ * | 1. $animate.removeClass(element, 'super') is called | class="my-animation super" |
1078
+ * | 2. $animate runs the JavaScript-defined animations detected on the element | class="my-animation super ng-animate" |
1079
+ * | 3. the .super-remove class is added to the element | class="my-animation super ng-animate super-remove" |
1080
+ * | 4. $animate waits for a single animation frame (this performs a reflow) | class="my-animation super ng-animate super-remove" |
1081
+ * | 5. the .super-remove-active classes are added and .super is removed (this triggers the CSS transition/animation) | class="my-animation ng-animate super-remove super-remove-active" |
1082
+ * | 6. $animate scans the element styles to get the CSS transition/animation duration and delay | class="my-animation super ng-animate super-remove" |
1083
+ * | 7. $animate waits for the animation to complete (via events and timeout) | class="my-animation ng-animate super-remove super-remove-active" |
1084
+ * | 8. The animation ends and all generated CSS classes are removed from the element | class="my-animation" |
1085
+ * | 9. The returned promise is resolved. | class="my-animation" |
745
1086
  *
746
1087
  *
747
1088
  * @param {DOMElement} element the element that will be animated
748
1089
  * @param {string} className the CSS class that will be animated and then removed from the element
749
- * @param {function()=} doneCallback the callback function that will be called once the animation is complete
1090
+ * @param {object=} options an optional collection of styles that will be picked up by the CSS transition/animation
1091
+ * @return {Promise} the animation callback promise
750
1092
  */
751
- removeClass : function(element, className, doneCallback) {
752
- element = angular.element(element);
753
- element = stripCommentsFromElement(element);
754
- performAnimation('removeClass', className, element, null, null, function() {
755
- $delegate.removeClass(element, className);
756
- }, doneCallback);
1093
+ removeClass : function(element, className, options) {
1094
+ return this.setClass(element, [], className, options);
757
1095
  },
758
1096
 
759
- /**
760
- *
761
- * @ngdoc function
762
- * @name $animate#setClass
763
- * @function
764
- * @description Adds and/or removes the given CSS classes to and from the element.
765
- * Once complete, the done() callback will be fired (if provided).
766
- * @param {DOMElement} element the element which will its CSS classes changed
767
- * removed from it
768
- * @param {string} add the CSS classes which will be added to the element
769
- * @param {string} remove the CSS class which will be removed from the element
770
- * @param {Function=} done the callback function (if provided) that will be fired after the
771
- * CSS classes have been set on the element
772
- */
773
- setClass : function(element, add, remove, doneCallback) {
1097
+ /**
1098
+ *
1099
+ * @ngdoc method
1100
+ * @name $animate#setClass
1101
+ *
1102
+ * @description Adds and/or removes the given CSS classes to and from the element.
1103
+ * Once complete, the done() callback will be fired (if provided).
1104
+ *
1105
+ * | Animation Step | What the element class attribute looks like |
1106
+ * |--------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------|
1107
+ * | 1. $animate.removeClass(element, ‘on’, ‘off’) is called | class="my-animation super off” |
1108
+ * | 2. $animate runs the JavaScript-defined animations detected on the element | class="my-animation super ng-animate off” |
1109
+ * | 3. the .on-add and .off-remove classes are added to the element | class="my-animation ng-animate on-add off-remove off” |
1110
+ * | 4. $animate waits for a single animation frame (this performs a reflow) | class="my-animation ng-animate on-add off-remove off” |
1111
+ * | 5. the .on, .on-add-active and .off-remove-active classes are added and .off is removed (this triggers the CSS transition/animation) | class="my-animation ng-animate on on-add on-add-active off-remove off-remove-active” |
1112
+ * | 6. $animate scans the element styles to get the CSS transition/animation duration and delay | class="my-animation ng-animate on on-add on-add-active off-remove off-remove-active" |
1113
+ * | 7. $animate waits for the animation to complete (via events and timeout) | class="my-animation ng-animate on on-add on-add-active off-remove off-remove-active" |
1114
+ * | 8. The animation ends and all generated CSS classes are removed from the element | class="my-animation on" |
1115
+ * | 9. The returned promise is resolved. | class="my-animation on" |
1116
+ *
1117
+ * @param {DOMElement} element the element which will have its CSS classes changed
1118
+ * removed from it
1119
+ * @param {string} add the CSS classes which will be added to the element
1120
+ * @param {string} remove the CSS class which will be removed from the element
1121
+ * CSS classes have been set on the element
1122
+ * @param {object=} options an optional collection of styles that will be picked up by the CSS transition/animation
1123
+ * @return {Promise} the animation callback promise
1124
+ */
1125
+ setClass : function(element, add, remove, options) {
1126
+ options = parseAnimateOptions(options);
1127
+
1128
+ var STORAGE_KEY = '$$animateClasses';
774
1129
  element = angular.element(element);
775
1130
  element = stripCommentsFromElement(element);
776
- performAnimation('setClass', [add, remove], element, null, null, function() {
777
- $delegate.setClass(element, add, remove);
778
- }, doneCallback);
1131
+
1132
+ if (classBasedAnimationsBlocked(element)) {
1133
+ return $delegate.$$setClassImmediately(element, add, remove, options);
1134
+ }
1135
+
1136
+ // we're using a combined array for both the add and remove
1137
+ // operations since the ORDER OF addClass and removeClass matters
1138
+ var classes, cache = element.data(STORAGE_KEY);
1139
+ var hasCache = !!cache;
1140
+ if (!cache) {
1141
+ cache = {};
1142
+ cache.classes = {};
1143
+ }
1144
+ classes = cache.classes;
1145
+
1146
+ add = isArray(add) ? add : add.split(' ');
1147
+ forEach(add, function(c) {
1148
+ if (c && c.length) {
1149
+ classes[c] = true;
1150
+ }
1151
+ });
1152
+
1153
+ remove = isArray(remove) ? remove : remove.split(' ');
1154
+ forEach(remove, function(c) {
1155
+ if (c && c.length) {
1156
+ classes[c] = false;
1157
+ }
1158
+ });
1159
+
1160
+ if (hasCache) {
1161
+ if (options && cache.options) {
1162
+ cache.options = angular.extend(cache.options || {}, options);
1163
+ }
1164
+
1165
+ //the digest cycle will combine all the animations into one function
1166
+ return cache.promise;
1167
+ } else {
1168
+ element.data(STORAGE_KEY, cache = {
1169
+ classes : classes,
1170
+ options : options
1171
+ });
1172
+ }
1173
+
1174
+ return cache.promise = runAnimationPostDigest(function(done) {
1175
+ var parentElement = element.parent();
1176
+ var elementNode = extractElementNode(element);
1177
+ var parentNode = elementNode.parentNode;
1178
+ // TODO(matsko): move this code into the animationsDisabled() function once #8092 is fixed
1179
+ if (!parentNode || parentNode['$$NG_REMOVED'] || elementNode['$$NG_REMOVED']) {
1180
+ done();
1181
+ return;
1182
+ }
1183
+
1184
+ var cache = element.data(STORAGE_KEY);
1185
+ element.removeData(STORAGE_KEY);
1186
+
1187
+ var state = element.data(NG_ANIMATE_STATE) || {};
1188
+ var classes = resolveElementClasses(element, cache, state.active);
1189
+ return !classes
1190
+ ? done()
1191
+ : performAnimation('setClass', classes, element, parentElement, null, function() {
1192
+ if (classes[0]) $delegate.$$addClassImmediately(element, classes[0]);
1193
+ if (classes[1]) $delegate.$$removeClassImmediately(element, classes[1]);
1194
+ }, cache.options, done);
1195
+ });
1196
+ },
1197
+
1198
+ /**
1199
+ * @ngdoc method
1200
+ * @name $animate#cancel
1201
+ * @kind function
1202
+ *
1203
+ * @param {Promise} animationPromise The animation promise that is returned when an animation is started.
1204
+ *
1205
+ * @description
1206
+ * Cancels the provided animation.
1207
+ */
1208
+ cancel : function(promise) {
1209
+ promise.$$cancelFn();
779
1210
  },
780
1211
 
781
1212
  /**
@@ -794,7 +1225,7 @@ angular.module('ngAnimate', ['ng'])
794
1225
  enabled : function(value, element) {
795
1226
  switch(arguments.length) {
796
1227
  case 2:
797
- if(value) {
1228
+ if (value) {
798
1229
  cleanup(element);
799
1230
  } else {
800
1231
  var data = element.data(NG_ANIMATE_STATE) || {};
@@ -822,17 +1253,18 @@ angular.module('ngAnimate', ['ng'])
822
1253
  CSS code. Element, parentElement and afterElement are provided DOM elements for the animation
823
1254
  and the onComplete callback will be fired once the animation is fully complete.
824
1255
  */
825
- function performAnimation(animationEvent, className, element, parentElement, afterElement, domOperation, doneCallback) {
826
-
827
- var runner = animationRunner(element, animationEvent, className);
828
- if(!runner) {
1256
+ function performAnimation(animationEvent, className, element, parentElement, afterElement, domOperation, options, doneCallback) {
1257
+ var noopCancel = noop;
1258
+ var runner = animationRunner(element, animationEvent, className, options);
1259
+ if (!runner) {
829
1260
  fireDOMOperation();
830
1261
  fireBeforeCallbackAsync();
831
1262
  fireAfterCallbackAsync();
832
1263
  closeAnimation();
833
- return;
1264
+ return noopCancel;
834
1265
  }
835
1266
 
1267
+ animationEvent = runner.event;
836
1268
  className = runner.className;
837
1269
  var elementEvents = angular.element._data(runner.node);
838
1270
  elementEvents = elementEvents && elementEvents.events;
@@ -841,54 +1273,44 @@ angular.module('ngAnimate', ['ng'])
841
1273
  parentElement = afterElement ? afterElement.parent() : element.parent();
842
1274
  }
843
1275
 
844
- var ngAnimateState = element.data(NG_ANIMATE_STATE) || {};
845
- var runningAnimations = ngAnimateState.active || {};
846
- var totalActiveAnimations = ngAnimateState.totalActive || 0;
847
- var lastAnimation = ngAnimateState.last;
848
-
849
- //only allow animations if the currently running animation is not structural
850
- //or if there is no animation running at all
851
- var skipAnimations;
852
- if (runner.isClassBased) {
853
- skipAnimations = ngAnimateState.running ||
854
- ngAnimateState.disabled ||
855
- (lastAnimation && !lastAnimation.isClassBased);
856
- }
857
-
858
1276
  //skip the animation if animations are disabled, a parent is already being animated,
859
1277
  //the element is not currently attached to the document body or then completely close
860
1278
  //the animation if any matching animations are not found at all.
861
1279
  //NOTE: IE8 + IE9 should close properly (run closeAnimation()) in case an animation was found.
862
- if (skipAnimations || animationsDisabled(element, parentElement)) {
1280
+ if (animationsDisabled(element, parentElement)) {
863
1281
  fireDOMOperation();
864
1282
  fireBeforeCallbackAsync();
865
1283
  fireAfterCallbackAsync();
866
1284
  closeAnimation();
867
- return;
1285
+ return noopCancel;
868
1286
  }
869
1287
 
1288
+ var ngAnimateState = element.data(NG_ANIMATE_STATE) || {};
1289
+ var runningAnimations = ngAnimateState.active || {};
1290
+ var totalActiveAnimations = ngAnimateState.totalActive || 0;
1291
+ var lastAnimation = ngAnimateState.last;
870
1292
  var skipAnimation = false;
871
- if(totalActiveAnimations > 0) {
1293
+
1294
+ if (totalActiveAnimations > 0) {
872
1295
  var animationsToCancel = [];
873
- if(!runner.isClassBased) {
874
- if(animationEvent == 'leave' && runningAnimations['ng-leave']) {
1296
+ if (!runner.isClassBased) {
1297
+ if (animationEvent == 'leave' && runningAnimations['ng-leave']) {
875
1298
  skipAnimation = true;
876
1299
  } else {
877
1300
  //cancel all animations when a structural animation takes place
878
1301
  for(var klass in runningAnimations) {
879
1302
  animationsToCancel.push(runningAnimations[klass]);
880
- cleanup(element, klass);
881
1303
  }
882
- runningAnimations = {};
883
- totalActiveAnimations = 0;
1304
+ ngAnimateState = {};
1305
+ cleanup(element, true);
884
1306
  }
885
- } else if(lastAnimation.event == 'setClass') {
1307
+ } else if (lastAnimation.event == 'setClass') {
886
1308
  animationsToCancel.push(lastAnimation);
887
1309
  cleanup(element, className);
888
1310
  }
889
- else if(runningAnimations[className]) {
1311
+ else if (runningAnimations[className]) {
890
1312
  var current = runningAnimations[className];
891
- if(current.event == animationEvent) {
1313
+ if (current.event == animationEvent) {
892
1314
  skipAnimation = true;
893
1315
  } else {
894
1316
  animationsToCancel.push(current);
@@ -896,35 +1318,41 @@ angular.module('ngAnimate', ['ng'])
896
1318
  }
897
1319
  }
898
1320
 
899
- if(animationsToCancel.length > 0) {
1321
+ if (animationsToCancel.length > 0) {
900
1322
  forEach(animationsToCancel, function(operation) {
901
1323
  operation.cancel();
902
1324
  });
903
1325
  }
904
1326
  }
905
1327
 
906
- if(runner.isClassBased && !runner.isSetClassOperation && !skipAnimation) {
1328
+ if (runner.isClassBased
1329
+ && !runner.isSetClassOperation
1330
+ && animationEvent != 'animate'
1331
+ && !skipAnimation) {
907
1332
  skipAnimation = (animationEvent == 'addClass') == element.hasClass(className); //opposite of XOR
908
1333
  }
909
1334
 
910
- if(skipAnimation) {
1335
+ if (skipAnimation) {
911
1336
  fireDOMOperation();
912
1337
  fireBeforeCallbackAsync();
913
1338
  fireAfterCallbackAsync();
914
1339
  fireDoneCallbackAsync();
915
- return;
1340
+ return noopCancel;
916
1341
  }
917
1342
 
918
- if(animationEvent == 'leave') {
1343
+ runningAnimations = ngAnimateState.active || {};
1344
+ totalActiveAnimations = ngAnimateState.totalActive || 0;
1345
+
1346
+ if (animationEvent == 'leave') {
919
1347
  //there's no need to ever remove the listener since the element
920
1348
  //will be removed (destroyed) after the leave animation ends or
921
1349
  //is cancelled midway
922
1350
  element.one('$destroy', function(e) {
923
1351
  var element = angular.element(this);
924
1352
  var state = element.data(NG_ANIMATE_STATE);
925
- if(state) {
1353
+ if (state) {
926
1354
  var activeLeaveAnimation = state.active['ng-leave'];
927
- if(activeLeaveAnimation) {
1355
+ if (activeLeaveAnimation) {
928
1356
  activeLeaveAnimation.cancel();
929
1357
  cleanup(element, 'ng-leave');
930
1358
  }
@@ -935,6 +1363,11 @@ angular.module('ngAnimate', ['ng'])
935
1363
  //the ng-animate class does nothing, but it's here to allow for
936
1364
  //parent animations to find and cancel child animations when needed
937
1365
  element.addClass(NG_ANIMATE_CLASS_NAME);
1366
+ if (options && options.tempClasses) {
1367
+ forEach(options.tempClasses, function(className) {
1368
+ element.addClass(className);
1369
+ });
1370
+ }
938
1371
 
939
1372
  var localAnimationCount = globalAnimationCounter++;
940
1373
  totalActiveAnimations++;
@@ -957,7 +1390,7 @@ angular.module('ngAnimate', ['ng'])
957
1390
  (runner.isClassBased && data.active[className].event != animationEvent);
958
1391
 
959
1392
  fireDOMOperation();
960
- if(cancelled === true) {
1393
+ if (cancelled === true) {
961
1394
  closeAnimation();
962
1395
  } else {
963
1396
  fireAfterCallbackAsync();
@@ -965,9 +1398,11 @@ angular.module('ngAnimate', ['ng'])
965
1398
  }
966
1399
  });
967
1400
 
1401
+ return runner.cancel;
1402
+
968
1403
  function fireDOMCallback(animationPhase) {
969
1404
  var eventName = '$animate:' + animationPhase;
970
- if(elementEvents && elementEvents[eventName] && elementEvents[eventName].length > 0) {
1405
+ if (elementEvents && elementEvents[eventName] && elementEvents[eventName].length > 0) {
971
1406
  $$asyncCallback(function() {
972
1407
  element.triggerHandler(eventName, {
973
1408
  event : animationEvent,
@@ -987,37 +1422,44 @@ angular.module('ngAnimate', ['ng'])
987
1422
 
988
1423
  function fireDoneCallbackAsync() {
989
1424
  fireDOMCallback('close');
990
- if(doneCallback) {
991
- $$asyncCallback(function() {
992
- doneCallback();
993
- });
994
- }
1425
+ doneCallback();
995
1426
  }
996
1427
 
997
1428
  //it is less complicated to use a flag than managing and canceling
998
1429
  //timeouts containing multiple callbacks.
999
1430
  function fireDOMOperation() {
1000
- if(!fireDOMOperation.hasBeenRun) {
1431
+ if (!fireDOMOperation.hasBeenRun) {
1001
1432
  fireDOMOperation.hasBeenRun = true;
1002
1433
  domOperation();
1003
1434
  }
1004
1435
  }
1005
1436
 
1006
1437
  function closeAnimation() {
1007
- if(!closeAnimation.hasBeenRun) {
1438
+ if (!closeAnimation.hasBeenRun) {
1439
+ if (runner) { //the runner doesn't exist if it fails to instantiate
1440
+ runner.applyStyles();
1441
+ }
1442
+
1008
1443
  closeAnimation.hasBeenRun = true;
1444
+ if (options && options.tempClasses) {
1445
+ forEach(options.tempClasses, function(className) {
1446
+ element.removeClass(className);
1447
+ });
1448
+ }
1449
+
1009
1450
  var data = element.data(NG_ANIMATE_STATE);
1010
- if(data) {
1451
+ if (data) {
1452
+
1011
1453
  /* only structural animations wait for reflow before removing an
1012
1454
  animation, but class-based animations don't. An example of this
1013
1455
  failing would be when a parent HTML tag has a ng-class attribute
1014
1456
  causing ALL directives below to skip animations during the digest */
1015
- if(runner && runner.isClassBased) {
1457
+ if (runner && runner.isClassBased) {
1016
1458
  cleanup(element, className);
1017
1459
  } else {
1018
1460
  $$asyncCallback(function() {
1019
1461
  var data = element.data(NG_ANIMATE_STATE) || {};
1020
- if(localAnimationCount == data.index) {
1462
+ if (localAnimationCount == data.index) {
1021
1463
  cleanup(element, className, animationEvent);
1022
1464
  }
1023
1465
  });
@@ -1038,7 +1480,7 @@ angular.module('ngAnimate', ['ng'])
1038
1480
  forEach(nodes, function(element) {
1039
1481
  element = angular.element(element);
1040
1482
  var data = element.data(NG_ANIMATE_STATE);
1041
- if(data && data.active) {
1483
+ if (data && data.active) {
1042
1484
  forEach(data.active, function(runner) {
1043
1485
  runner.cancel();
1044
1486
  });
@@ -1048,21 +1490,21 @@ angular.module('ngAnimate', ['ng'])
1048
1490
  }
1049
1491
 
1050
1492
  function cleanup(element, className) {
1051
- if(isMatchingElement(element, $rootElement)) {
1052
- if(!rootAnimateState.disabled) {
1493
+ if (isMatchingElement(element, $rootElement)) {
1494
+ if (!rootAnimateState.disabled) {
1053
1495
  rootAnimateState.running = false;
1054
1496
  rootAnimateState.structural = false;
1055
1497
  }
1056
- } else if(className) {
1498
+ } else if (className) {
1057
1499
  var data = element.data(NG_ANIMATE_STATE) || {};
1058
1500
 
1059
1501
  var removeAnimations = className === true;
1060
- if(!removeAnimations && data.active && data.active[className]) {
1502
+ if (!removeAnimations && data.active && data.active[className]) {
1061
1503
  data.totalActive--;
1062
1504
  delete data.active[className];
1063
1505
  }
1064
1506
 
1065
- if(removeAnimations || !data.totalActive) {
1507
+ if (removeAnimations || !data.totalActive) {
1066
1508
  element.removeClass(NG_ANIMATE_CLASS_NAME);
1067
1509
  element.removeData(NG_ANIMATE_STATE);
1068
1510
  }
@@ -1101,7 +1543,7 @@ angular.module('ngAnimate', ['ng'])
1101
1543
  //it will be discarded and all child animations will be restricted
1102
1544
  if (allowChildAnimations !== false) {
1103
1545
  var animateChildrenFlag = parentElement.data(NG_ANIMATE_CHILDREN);
1104
- if(angular.isDefined(animateChildrenFlag)) {
1546
+ if (angular.isDefined(animateChildrenFlag)) {
1105
1547
  allowChildAnimations = animateChildrenFlag;
1106
1548
  }
1107
1549
  }
@@ -1151,9 +1593,9 @@ angular.module('ngAnimate', ['ng'])
1151
1593
  var PROPERTY_KEY = 'Property';
1152
1594
  var DELAY_KEY = 'Delay';
1153
1595
  var ANIMATION_ITERATION_COUNT_KEY = 'IterationCount';
1596
+ var ANIMATION_PLAYSTATE_KEY = 'PlayState';
1154
1597
  var NG_ANIMATE_PARENT_KEY = '$$ngAnimateKey';
1155
1598
  var NG_ANIMATE_CSS_DATA_KEY = '$$ngAnimateCSS3Data';
1156
- var NG_ANIMATE_BLOCK_CLASS_NAME = 'ng-animate-block-transitions';
1157
1599
  var ELAPSED_TIME_MAX_DECIMAL_PLACES = 3;
1158
1600
  var CLOSING_TIME_BUFFER = 1.5;
1159
1601
  var ONE_SECOND = 1000;
@@ -1162,8 +1604,18 @@ angular.module('ngAnimate', ['ng'])
1162
1604
  var parentCounter = 0;
1163
1605
  var animationReflowQueue = [];
1164
1606
  var cancelAnimationReflow;
1607
+ function clearCacheAfterReflow() {
1608
+ if (!cancelAnimationReflow) {
1609
+ cancelAnimationReflow = $$animateReflow(function() {
1610
+ animationReflowQueue = [];
1611
+ cancelAnimationReflow = null;
1612
+ lookupCache = {};
1613
+ });
1614
+ }
1615
+ }
1616
+
1165
1617
  function afterReflow(element, callback) {
1166
- if(cancelAnimationReflow) {
1618
+ if (cancelAnimationReflow) {
1167
1619
  cancelAnimationReflow();
1168
1620
  }
1169
1621
  animationReflowQueue.push(callback);
@@ -1192,7 +1644,7 @@ angular.module('ngAnimate', ['ng'])
1192
1644
  //but it may not need to cancel out the existing timeout
1193
1645
  //if the timestamp is less than the previous one
1194
1646
  var futureTimestamp = Date.now() + totalTime;
1195
- if(futureTimestamp <= closingTimestamp) {
1647
+ if (futureTimestamp <= closingTimestamp) {
1196
1648
  return;
1197
1649
  }
1198
1650
 
@@ -1208,64 +1660,52 @@ angular.module('ngAnimate', ['ng'])
1208
1660
  function closeAllAnimations(elements) {
1209
1661
  forEach(elements, function(element) {
1210
1662
  var elementData = element.data(NG_ANIMATE_CSS_DATA_KEY);
1211
- if(elementData) {
1212
- (elementData.closeAnimationFn || noop)();
1663
+ if (elementData) {
1664
+ forEach(elementData.closeAnimationFns, function(fn) {
1665
+ fn();
1666
+ });
1213
1667
  }
1214
1668
  });
1215
1669
  }
1216
1670
 
1217
1671
  function getElementAnimationDetails(element, cacheKey) {
1218
1672
  var data = cacheKey ? lookupCache[cacheKey] : null;
1219
- if(!data) {
1673
+ if (!data) {
1220
1674
  var transitionDuration = 0;
1221
1675
  var transitionDelay = 0;
1222
1676
  var animationDuration = 0;
1223
1677
  var animationDelay = 0;
1224
- var transitionDelayStyle;
1225
- var animationDelayStyle;
1226
- var transitionDurationStyle;
1227
- var transitionPropertyStyle;
1228
1678
 
1229
1679
  //we want all the styles defined before and after
1230
1680
  forEach(element, function(element) {
1231
1681
  if (element.nodeType == ELEMENT_NODE) {
1232
1682
  var elementStyles = $window.getComputedStyle(element) || {};
1233
1683
 
1234
- transitionDurationStyle = elementStyles[TRANSITION_PROP + DURATION_KEY];
1235
-
1684
+ var transitionDurationStyle = elementStyles[TRANSITION_PROP + DURATION_KEY];
1236
1685
  transitionDuration = Math.max(parseMaxTime(transitionDurationStyle), transitionDuration);
1237
1686
 
1238
- transitionPropertyStyle = elementStyles[TRANSITION_PROP + PROPERTY_KEY];
1239
-
1240
- transitionDelayStyle = elementStyles[TRANSITION_PROP + DELAY_KEY];
1241
-
1687
+ var transitionDelayStyle = elementStyles[TRANSITION_PROP + DELAY_KEY];
1242
1688
  transitionDelay = Math.max(parseMaxTime(transitionDelayStyle), transitionDelay);
1243
1689
 
1244
- animationDelayStyle = elementStyles[ANIMATION_PROP + DELAY_KEY];
1245
-
1246
- animationDelay = Math.max(parseMaxTime(animationDelayStyle), animationDelay);
1690
+ var animationDelayStyle = elementStyles[ANIMATION_PROP + DELAY_KEY];
1691
+ animationDelay = Math.max(parseMaxTime(elementStyles[ANIMATION_PROP + DELAY_KEY]), animationDelay);
1247
1692
 
1248
1693
  var aDuration = parseMaxTime(elementStyles[ANIMATION_PROP + DURATION_KEY]);
1249
1694
 
1250
- if(aDuration > 0) {
1695
+ if (aDuration > 0) {
1251
1696
  aDuration *= parseInt(elementStyles[ANIMATION_PROP + ANIMATION_ITERATION_COUNT_KEY], 10) || 1;
1252
1697
  }
1253
-
1254
1698
  animationDuration = Math.max(aDuration, animationDuration);
1255
1699
  }
1256
1700
  });
1257
1701
  data = {
1258
1702
  total : 0,
1259
- transitionPropertyStyle: transitionPropertyStyle,
1260
- transitionDurationStyle: transitionDurationStyle,
1261
- transitionDelayStyle: transitionDelayStyle,
1262
1703
  transitionDelay: transitionDelay,
1263
1704
  transitionDuration: transitionDuration,
1264
- animationDelayStyle: animationDelayStyle,
1265
1705
  animationDelay: animationDelay,
1266
1706
  animationDuration: animationDuration
1267
1707
  };
1268
- if(cacheKey) {
1708
+ if (cacheKey) {
1269
1709
  lookupCache[cacheKey] = data;
1270
1710
  }
1271
1711
  }
@@ -1274,7 +1714,7 @@ angular.module('ngAnimate', ['ng'])
1274
1714
 
1275
1715
  function parseMaxTime(str) {
1276
1716
  var maxValue = 0;
1277
- var values = angular.isString(str) ?
1717
+ var values = isString(str) ?
1278
1718
  str.split(/\s*,\s*/) :
1279
1719
  [];
1280
1720
  forEach(values, function(value) {
@@ -1286,20 +1726,22 @@ angular.module('ngAnimate', ['ng'])
1286
1726
  function getCacheKey(element) {
1287
1727
  var parentElement = element.parent();
1288
1728
  var parentID = parentElement.data(NG_ANIMATE_PARENT_KEY);
1289
- if(!parentID) {
1729
+ if (!parentID) {
1290
1730
  parentElement.data(NG_ANIMATE_PARENT_KEY, ++parentCounter);
1291
1731
  parentID = parentCounter;
1292
1732
  }
1293
1733
  return parentID + '-' + extractElementNode(element).getAttribute('class');
1294
1734
  }
1295
1735
 
1296
- function animateSetup(animationEvent, element, className, calculationDecorator) {
1736
+ function animateSetup(animationEvent, element, className, styles) {
1737
+ var structural = ['ng-enter','ng-leave','ng-move'].indexOf(className) >= 0;
1738
+
1297
1739
  var cacheKey = getCacheKey(element);
1298
1740
  var eventCacheKey = cacheKey + ' ' + className;
1299
1741
  var itemIndex = lookupCache[eventCacheKey] ? ++lookupCache[eventCacheKey].total : 0;
1300
1742
 
1301
1743
  var stagger = {};
1302
- if(itemIndex > 0) {
1744
+ if (itemIndex > 0) {
1303
1745
  var staggerClassName = className + '-stagger';
1304
1746
  var staggerCacheKey = cacheKey + ' ' + staggerClassName;
1305
1747
  var applyClasses = !lookupCache[staggerCacheKey];
@@ -1311,154 +1753,160 @@ angular.module('ngAnimate', ['ng'])
1311
1753
  applyClasses && element.removeClass(staggerClassName);
1312
1754
  }
1313
1755
 
1314
- /* the animation itself may need to add/remove special CSS classes
1315
- * before calculating the anmation styles */
1316
- calculationDecorator = calculationDecorator ||
1317
- function(fn) { return fn(); };
1318
-
1319
1756
  element.addClass(className);
1320
1757
 
1321
1758
  var formerData = element.data(NG_ANIMATE_CSS_DATA_KEY) || {};
1322
-
1323
- var timings = calculationDecorator(function() {
1324
- return getElementAnimationDetails(element, eventCacheKey);
1325
- });
1326
-
1759
+ var timings = getElementAnimationDetails(element, eventCacheKey);
1327
1760
  var transitionDuration = timings.transitionDuration;
1328
1761
  var animationDuration = timings.animationDuration;
1329
- if(transitionDuration === 0 && animationDuration === 0) {
1762
+
1763
+ if (structural && transitionDuration === 0 && animationDuration === 0) {
1330
1764
  element.removeClass(className);
1331
1765
  return false;
1332
1766
  }
1333
1767
 
1768
+ var blockTransition = styles || (structural && transitionDuration > 0);
1769
+ var blockAnimation = animationDuration > 0 &&
1770
+ stagger.animationDelay > 0 &&
1771
+ stagger.animationDuration === 0;
1772
+
1773
+ var closeAnimationFns = formerData.closeAnimationFns || [];
1334
1774
  element.data(NG_ANIMATE_CSS_DATA_KEY, {
1775
+ stagger : stagger,
1776
+ cacheKey : eventCacheKey,
1335
1777
  running : formerData.running || 0,
1336
1778
  itemIndex : itemIndex,
1337
- stagger : stagger,
1338
- timings : timings,
1339
- closeAnimationFn : noop
1779
+ blockTransition : blockTransition,
1780
+ closeAnimationFns : closeAnimationFns
1340
1781
  });
1341
1782
 
1342
- //temporarily disable the transition so that the enter styles
1343
- //don't animate twice (this is here to avoid a bug in Chrome/FF).
1344
- var isCurrentlyAnimating = formerData.running > 0 || animationEvent == 'setClass';
1345
- if(transitionDuration > 0) {
1346
- blockTransitions(element, className, isCurrentlyAnimating);
1347
- }
1348
-
1349
- //staggering keyframe animations work by adjusting the `animation-delay` CSS property
1350
- //on the given element, however, the delay value can only calculated after the reflow
1351
- //since by that time $animate knows how many elements are being animated. Therefore,
1352
- //until the reflow occurs the element needs to be blocked (where the keyframe animation
1353
- //is set to `none 0s`). This blocking mechanism should only be set for when a stagger
1354
- //animation is detected and when the element item index is greater than 0.
1355
- if(animationDuration > 0 && stagger.animationDelay > 0 && stagger.animationDuration === 0) {
1356
- blockKeyframeAnimations(element);
1357
- }
1358
-
1359
- return true;
1360
- }
1361
-
1362
- function isStructuralAnimation(className) {
1363
- return className == 'ng-enter' || className == 'ng-move' || className == 'ng-leave';
1364
- }
1783
+ var node = extractElementNode(element);
1365
1784
 
1366
- function blockTransitions(element, className, isAnimating) {
1367
- if(isStructuralAnimation(className) || !isAnimating) {
1368
- extractElementNode(element).style[TRANSITION_PROP + PROPERTY_KEY] = 'none';
1369
- } else {
1370
- element.addClass(NG_ANIMATE_BLOCK_CLASS_NAME);
1785
+ if (blockTransition) {
1786
+ blockTransitions(node, true);
1787
+ if (styles) {
1788
+ element.css(styles);
1789
+ }
1371
1790
  }
1372
- }
1373
-
1374
- function blockKeyframeAnimations(element) {
1375
- extractElementNode(element).style[ANIMATION_PROP] = 'none 0s';
1376
- }
1377
1791
 
1378
- function unblockTransitions(element, className) {
1379
- var prop = TRANSITION_PROP + PROPERTY_KEY;
1380
- var node = extractElementNode(element);
1381
- if(node.style[prop] && node.style[prop].length > 0) {
1382
- node.style[prop] = '';
1792
+ if (blockAnimation) {
1793
+ blockAnimations(node, true);
1383
1794
  }
1384
- element.removeClass(NG_ANIMATE_BLOCK_CLASS_NAME);
1385
- }
1386
1795
 
1387
- function unblockKeyframeAnimations(element) {
1388
- var prop = ANIMATION_PROP;
1389
- var node = extractElementNode(element);
1390
- if(node.style[prop] && node.style[prop].length > 0) {
1391
- node.style[prop] = '';
1392
- }
1796
+ return true;
1393
1797
  }
1394
1798
 
1395
- function animateRun(animationEvent, element, className, activeAnimationComplete) {
1799
+ function animateRun(animationEvent, element, className, activeAnimationComplete, styles) {
1396
1800
  var node = extractElementNode(element);
1397
1801
  var elementData = element.data(NG_ANIMATE_CSS_DATA_KEY);
1398
- if(node.getAttribute('class').indexOf(className) == -1 || !elementData) {
1802
+ if (node.getAttribute('class').indexOf(className) == -1 || !elementData) {
1399
1803
  activeAnimationComplete();
1400
1804
  return;
1401
1805
  }
1402
1806
 
1403
1807
  var activeClassName = '';
1808
+ var pendingClassName = '';
1404
1809
  forEach(className.split(' '), function(klass, i) {
1405
- activeClassName += (i > 0 ? ' ' : '') + klass + '-active';
1810
+ var prefix = (i > 0 ? ' ' : '') + klass;
1811
+ activeClassName += prefix + '-active';
1812
+ pendingClassName += prefix + '-pending';
1406
1813
  });
1407
1814
 
1408
- var stagger = elementData.stagger;
1409
- var timings = elementData.timings;
1815
+ var style = '';
1816
+ var appliedStyles = [];
1410
1817
  var itemIndex = elementData.itemIndex;
1411
- var maxDuration = Math.max(timings.transitionDuration, timings.animationDuration);
1412
- var maxDelay = Math.max(timings.transitionDelay, timings.animationDelay);
1413
- var maxDelayTime = maxDelay * ONE_SECOND;
1414
-
1415
- var startTime = Date.now();
1416
- var css3AnimationEvents = ANIMATIONEND_EVENT + ' ' + TRANSITIONEND_EVENT;
1818
+ var stagger = elementData.stagger;
1819
+ var staggerTime = 0;
1820
+ if (itemIndex > 0) {
1821
+ var transitionStaggerDelay = 0;
1822
+ if (stagger.transitionDelay > 0 && stagger.transitionDuration === 0) {
1823
+ transitionStaggerDelay = stagger.transitionDelay * itemIndex;
1824
+ }
1417
1825
 
1418
- var style = '', appliedStyles = [];
1419
- if(timings.transitionDuration > 0) {
1420
- var propertyStyle = timings.transitionPropertyStyle;
1421
- if(propertyStyle.indexOf('all') == -1) {
1422
- style += CSS_PREFIX + 'transition-property: ' + propertyStyle + ';';
1423
- style += CSS_PREFIX + 'transition-duration: ' + timings.transitionDurationStyle + ';';
1424
- appliedStyles.push(CSS_PREFIX + 'transition-property');
1425
- appliedStyles.push(CSS_PREFIX + 'transition-duration');
1826
+ var animationStaggerDelay = 0;
1827
+ if (stagger.animationDelay > 0 && stagger.animationDuration === 0) {
1828
+ animationStaggerDelay = stagger.animationDelay * itemIndex;
1829
+ appliedStyles.push(CSS_PREFIX + 'animation-play-state');
1426
1830
  }
1831
+
1832
+ staggerTime = Math.round(Math.max(transitionStaggerDelay, animationStaggerDelay) * 100) / 100;
1427
1833
  }
1428
1834
 
1429
- if(itemIndex > 0) {
1430
- if(stagger.transitionDelay > 0 && stagger.transitionDuration === 0) {
1431
- var delayStyle = timings.transitionDelayStyle;
1432
- style += CSS_PREFIX + 'transition-delay: ' +
1433
- prepareStaggerDelay(delayStyle, stagger.transitionDelay, itemIndex) + '; ';
1434
- appliedStyles.push(CSS_PREFIX + 'transition-delay');
1835
+ if (!staggerTime) {
1836
+ element.addClass(activeClassName);
1837
+ if (elementData.blockTransition) {
1838
+ blockTransitions(node, false);
1435
1839
  }
1840
+ }
1436
1841
 
1437
- if(stagger.animationDelay > 0 && stagger.animationDuration === 0) {
1438
- style += CSS_PREFIX + 'animation-delay: ' +
1439
- prepareStaggerDelay(timings.animationDelayStyle, stagger.animationDelay, itemIndex) + '; ';
1440
- appliedStyles.push(CSS_PREFIX + 'animation-delay');
1842
+ var eventCacheKey = elementData.cacheKey + ' ' + activeClassName;
1843
+ var timings = getElementAnimationDetails(element, eventCacheKey);
1844
+ var maxDuration = Math.max(timings.transitionDuration, timings.animationDuration);
1845
+ if (maxDuration === 0) {
1846
+ element.removeClass(activeClassName);
1847
+ animateClose(element, className);
1848
+ activeAnimationComplete();
1849
+ return;
1850
+ }
1851
+
1852
+ if (!staggerTime && styles) {
1853
+ if (!timings.transitionDuration) {
1854
+ element.css('transition', timings.animationDuration + 's linear all');
1855
+ appliedStyles.push('transition');
1441
1856
  }
1857
+ element.css(styles);
1442
1858
  }
1443
1859
 
1444
- if(appliedStyles.length > 0) {
1860
+ var maxDelay = Math.max(timings.transitionDelay, timings.animationDelay);
1861
+ var maxDelayTime = maxDelay * ONE_SECOND;
1862
+
1863
+ if (appliedStyles.length > 0) {
1445
1864
  //the element being animated may sometimes contain comment nodes in
1446
1865
  //the jqLite object, so we're safe to use a single variable to house
1447
1866
  //the styles since there is always only one element being animated
1448
1867
  var oldStyle = node.getAttribute('style') || '';
1449
- node.setAttribute('style', oldStyle + '; ' + style);
1868
+ if (oldStyle.charAt(oldStyle.length-1) !== ';') {
1869
+ oldStyle += ';';
1870
+ }
1871
+ node.setAttribute('style', oldStyle + ' ' + style);
1872
+ }
1873
+
1874
+ var startTime = Date.now();
1875
+ var css3AnimationEvents = ANIMATIONEND_EVENT + ' ' + TRANSITIONEND_EVENT;
1876
+ var animationTime = (maxDelay + maxDuration) * CLOSING_TIME_BUFFER;
1877
+ var totalTime = (staggerTime + animationTime) * ONE_SECOND;
1878
+
1879
+ var staggerTimeout;
1880
+ if (staggerTime > 0) {
1881
+ element.addClass(pendingClassName);
1882
+ staggerTimeout = $timeout(function() {
1883
+ staggerTimeout = null;
1884
+
1885
+ if (timings.transitionDuration > 0) {
1886
+ blockTransitions(node, false);
1887
+ }
1888
+ if (timings.animationDuration > 0) {
1889
+ blockAnimations(node, false);
1890
+ }
1891
+
1892
+ element.addClass(activeClassName);
1893
+ element.removeClass(pendingClassName);
1894
+
1895
+ if (styles) {
1896
+ if (timings.transitionDuration === 0) {
1897
+ element.css('transition', timings.animationDuration + 's linear all');
1898
+ }
1899
+ element.css(styles);
1900
+ appliedStyles.push('transition');
1901
+ }
1902
+ }, staggerTime * ONE_SECOND, false);
1450
1903
  }
1451
1904
 
1452
1905
  element.on(css3AnimationEvents, onAnimationProgress);
1453
- element.addClass(activeClassName);
1454
- elementData.closeAnimationFn = function() {
1906
+ elementData.closeAnimationFns.push(function() {
1455
1907
  onEnd();
1456
1908
  activeAnimationComplete();
1457
- };
1458
-
1459
- var staggerTime = itemIndex * (Math.max(stagger.animationDelay, stagger.transitionDelay) || 0);
1460
- var animationTime = (maxDelay + maxDuration) * CLOSING_TIME_BUFFER;
1461
- var totalTime = (staggerTime + animationTime) * ONE_SECOND;
1909
+ });
1462
1910
 
1463
1911
  elementData.running++;
1464
1912
  animationCloseHandler(element, totalTime);
@@ -1467,9 +1915,13 @@ angular.module('ngAnimate', ['ng'])
1467
1915
  // This will automatically be called by $animate so
1468
1916
  // there is no need to attach this internally to the
1469
1917
  // timeout done method.
1470
- function onEnd(cancelled) {
1918
+ function onEnd() {
1471
1919
  element.off(css3AnimationEvents, onAnimationProgress);
1472
1920
  element.removeClass(activeClassName);
1921
+ element.removeClass(pendingClassName);
1922
+ if (staggerTimeout) {
1923
+ $timeout.cancel(staggerTimeout);
1924
+ }
1473
1925
  animateClose(element, className);
1474
1926
  var node = extractElementNode(element);
1475
1927
  for (var i in appliedStyles) {
@@ -1493,44 +1945,44 @@ angular.module('ngAnimate', ['ng'])
1493
1945
  * We're checking to see if the timeStamp surpasses the expected delay,
1494
1946
  * but we're using elapsedTime instead of the timeStamp on the 2nd
1495
1947
  * pre-condition since animations sometimes close off early */
1496
- if(Math.max(timeStamp - startTime, 0) >= maxDelayTime && elapsedTime >= maxDuration) {
1948
+ if (Math.max(timeStamp - startTime, 0) >= maxDelayTime && elapsedTime >= maxDuration) {
1497
1949
  activeAnimationComplete();
1498
1950
  }
1499
1951
  }
1500
1952
  }
1501
1953
 
1502
- function prepareStaggerDelay(delayStyle, staggerDelay, index) {
1503
- var style = '';
1504
- forEach(delayStyle.split(','), function(val, i) {
1505
- style += (i > 0 ? ',' : '') +
1506
- (index * staggerDelay + parseInt(val, 10)) + 's';
1507
- });
1508
- return style;
1954
+ function blockTransitions(node, bool) {
1955
+ node.style[TRANSITION_PROP + PROPERTY_KEY] = bool ? 'none' : '';
1956
+ }
1957
+
1958
+ function blockAnimations(node, bool) {
1959
+ node.style[ANIMATION_PROP + ANIMATION_PLAYSTATE_KEY] = bool ? 'paused' : '';
1509
1960
  }
1510
1961
 
1511
- function animateBefore(animationEvent, element, className, calculationDecorator) {
1512
- if(animateSetup(animationEvent, element, className, calculationDecorator)) {
1962
+ function animateBefore(animationEvent, element, className, styles) {
1963
+ if (animateSetup(animationEvent, element, className, styles)) {
1513
1964
  return function(cancelled) {
1514
1965
  cancelled && animateClose(element, className);
1515
1966
  };
1516
1967
  }
1517
1968
  }
1518
1969
 
1519
- function animateAfter(animationEvent, element, className, afterAnimationComplete) {
1520
- if(element.data(NG_ANIMATE_CSS_DATA_KEY)) {
1521
- return animateRun(animationEvent, element, className, afterAnimationComplete);
1970
+ function animateAfter(animationEvent, element, className, afterAnimationComplete, styles) {
1971
+ if (element.data(NG_ANIMATE_CSS_DATA_KEY)) {
1972
+ return animateRun(animationEvent, element, className, afterAnimationComplete, styles);
1522
1973
  } else {
1523
1974
  animateClose(element, className);
1524
1975
  afterAnimationComplete();
1525
1976
  }
1526
1977
  }
1527
1978
 
1528
- function animate(animationEvent, element, className, animationComplete) {
1979
+ function animate(animationEvent, element, className, animationComplete, options) {
1529
1980
  //If the animateSetup function doesn't bother returning a
1530
1981
  //cancellation function then it means that there is no animation
1531
1982
  //to perform at all
1532
- var preReflowCancellation = animateBefore(animationEvent, element, className);
1533
- if(!preReflowCancellation) {
1983
+ var preReflowCancellation = animateBefore(animationEvent, element, className, options.from);
1984
+ if (!preReflowCancellation) {
1985
+ clearCacheAfterReflow();
1534
1986
  animationComplete();
1535
1987
  return;
1536
1988
  }
@@ -1542,12 +1994,10 @@ angular.module('ngAnimate', ['ng'])
1542
1994
  //happen in the first place
1543
1995
  var cancel = preReflowCancellation;
1544
1996
  afterReflow(element, function() {
1545
- unblockTransitions(element, className);
1546
- unblockKeyframeAnimations(element);
1547
1997
  //once the reflow is complete then we point cancel to
1548
1998
  //the new cancellation function which will remove all of the
1549
1999
  //animation properties from the active animation
1550
- cancel = animateAfter(animationEvent, element, className, animationComplete);
2000
+ cancel = animateAfter(animationEvent, element, className, animationComplete, options.to);
1551
2001
  });
1552
2002
 
1553
2003
  return function(cancelled) {
@@ -1558,125 +2008,98 @@ angular.module('ngAnimate', ['ng'])
1558
2008
  function animateClose(element, className) {
1559
2009
  element.removeClass(className);
1560
2010
  var data = element.data(NG_ANIMATE_CSS_DATA_KEY);
1561
- if(data) {
1562
- if(data.running) {
2011
+ if (data) {
2012
+ if (data.running) {
1563
2013
  data.running--;
1564
2014
  }
1565
- if(!data.running || data.running === 0) {
2015
+ if (!data.running || data.running === 0) {
1566
2016
  element.removeData(NG_ANIMATE_CSS_DATA_KEY);
1567
2017
  }
1568
2018
  }
1569
2019
  }
1570
2020
 
1571
2021
  return {
1572
- enter : function(element, animationCompleted) {
1573
- return animate('enter', element, 'ng-enter', animationCompleted);
2022
+ animate : function(element, className, from, to, animationCompleted, options) {
2023
+ options = options || {};
2024
+ options.from = from;
2025
+ options.to = to;
2026
+ return animate('animate', element, className, animationCompleted, options);
1574
2027
  },
1575
2028
 
1576
- leave : function(element, animationCompleted) {
1577
- return animate('leave', element, 'ng-leave', animationCompleted);
2029
+ enter : function(element, animationCompleted, options) {
2030
+ options = options || {};
2031
+ return animate('enter', element, 'ng-enter', animationCompleted, options);
1578
2032
  },
1579
2033
 
1580
- move : function(element, animationCompleted) {
1581
- return animate('move', element, 'ng-move', animationCompleted);
2034
+ leave : function(element, animationCompleted, options) {
2035
+ options = options || {};
2036
+ return animate('leave', element, 'ng-leave', animationCompleted, options);
1582
2037
  },
1583
2038
 
1584
- beforeSetClass : function(element, add, remove, animationCompleted) {
2039
+ move : function(element, animationCompleted, options) {
2040
+ options = options || {};
2041
+ return animate('move', element, 'ng-move', animationCompleted, options);
2042
+ },
2043
+
2044
+ beforeSetClass : function(element, add, remove, animationCompleted, options) {
2045
+ options = options || {};
1585
2046
  var className = suffixClasses(remove, '-remove') + ' ' +
1586
2047
  suffixClasses(add, '-add');
1587
- var cancellationMethod = animateBefore('setClass', element, className, function(fn) {
1588
- /* when classes are removed from an element then the transition style
1589
- * that is applied is the transition defined on the element without the
1590
- * CSS class being there. This is how CSS3 functions outside of ngAnimate.
1591
- * http://plnkr.co/edit/j8OzgTNxHTb4n3zLyjGW?p=preview */
1592
- var klass = element.attr('class');
1593
- element.removeClass(remove);
1594
- element.addClass(add);
1595
- var timings = fn();
1596
- element.attr('class', klass);
1597
- return timings;
1598
- });
1599
-
1600
- if(cancellationMethod) {
1601
- afterReflow(element, function() {
1602
- unblockTransitions(element, className);
1603
- unblockKeyframeAnimations(element);
1604
- animationCompleted();
1605
- });
2048
+ var cancellationMethod = animateBefore('setClass', element, className, options.from);
2049
+ if (cancellationMethod) {
2050
+ afterReflow(element, animationCompleted);
1606
2051
  return cancellationMethod;
1607
2052
  }
2053
+ clearCacheAfterReflow();
1608
2054
  animationCompleted();
1609
2055
  },
1610
2056
 
1611
- beforeAddClass : function(element, className, animationCompleted) {
1612
- var cancellationMethod = animateBefore('addClass', element, suffixClasses(className, '-add'), function(fn) {
1613
-
1614
- /* when a CSS class is added to an element then the transition style that
1615
- * is applied is the transition defined on the element when the CSS class
1616
- * is added at the time of the animation. This is how CSS3 functions
1617
- * outside of ngAnimate. */
1618
- element.addClass(className);
1619
- var timings = fn();
1620
- element.removeClass(className);
1621
- return timings;
1622
- });
2057
+ beforeAddClass : function(element, className, animationCompleted, options) {
2058
+ options = options || {};
2059
+ var cancellationMethod = animateBefore('addClass', element, suffixClasses(className, '-add'), options.from);
2060
+ if (cancellationMethod) {
2061
+ afterReflow(element, animationCompleted);
2062
+ return cancellationMethod;
2063
+ }
2064
+ clearCacheAfterReflow();
2065
+ animationCompleted();
2066
+ },
1623
2067
 
1624
- if(cancellationMethod) {
1625
- afterReflow(element, function() {
1626
- unblockTransitions(element, className);
1627
- unblockKeyframeAnimations(element);
1628
- animationCompleted();
1629
- });
2068
+ beforeRemoveClass : function(element, className, animationCompleted, options) {
2069
+ options = options || {};
2070
+ var cancellationMethod = animateBefore('removeClass', element, suffixClasses(className, '-remove'), options.from);
2071
+ if (cancellationMethod) {
2072
+ afterReflow(element, animationCompleted);
1630
2073
  return cancellationMethod;
1631
2074
  }
2075
+ clearCacheAfterReflow();
1632
2076
  animationCompleted();
1633
2077
  },
1634
2078
 
1635
- setClass : function(element, add, remove, animationCompleted) {
2079
+ setClass : function(element, add, remove, animationCompleted, options) {
2080
+ options = options || {};
1636
2081
  remove = suffixClasses(remove, '-remove');
1637
2082
  add = suffixClasses(add, '-add');
1638
2083
  var className = remove + ' ' + add;
1639
- return animateAfter('setClass', element, className, animationCompleted);
2084
+ return animateAfter('setClass', element, className, animationCompleted, options.to);
1640
2085
  },
1641
2086
 
1642
- addClass : function(element, className, animationCompleted) {
1643
- return animateAfter('addClass', element, suffixClasses(className, '-add'), animationCompleted);
1644
- },
1645
-
1646
- beforeRemoveClass : function(element, className, animationCompleted) {
1647
- var cancellationMethod = animateBefore('removeClass', element, suffixClasses(className, '-remove'), function(fn) {
1648
- /* when classes are removed from an element then the transition style
1649
- * that is applied is the transition defined on the element without the
1650
- * CSS class being there. This is how CSS3 functions outside of ngAnimate.
1651
- * http://plnkr.co/edit/j8OzgTNxHTb4n3zLyjGW?p=preview */
1652
- var klass = element.attr('class');
1653
- element.removeClass(className);
1654
- var timings = fn();
1655
- element.attr('class', klass);
1656
- return timings;
1657
- });
1658
-
1659
- if(cancellationMethod) {
1660
- afterReflow(element, function() {
1661
- unblockTransitions(element, className);
1662
- unblockKeyframeAnimations(element);
1663
- animationCompleted();
1664
- });
1665
- return cancellationMethod;
1666
- }
1667
- animationCompleted();
2087
+ addClass : function(element, className, animationCompleted, options) {
2088
+ options = options || {};
2089
+ return animateAfter('addClass', element, suffixClasses(className, '-add'), animationCompleted, options.to);
1668
2090
  },
1669
2091
 
1670
- removeClass : function(element, className, animationCompleted) {
1671
- return animateAfter('removeClass', element, suffixClasses(className, '-remove'), animationCompleted);
2092
+ removeClass : function(element, className, animationCompleted, options) {
2093
+ options = options || {};
2094
+ return animateAfter('removeClass', element, suffixClasses(className, '-remove'), animationCompleted, options.to);
1672
2095
  }
1673
2096
  };
1674
2097
 
1675
2098
  function suffixClasses(classes, suffix) {
1676
2099
  var className = '';
1677
- classes = angular.isArray(classes) ? classes : classes.split(/\s+/);
2100
+ classes = isArray(classes) ? classes : classes.split(/\s+/);
1678
2101
  forEach(classes, function(klass, i) {
1679
- if(klass && klass.length > 0) {
2102
+ if (klass && klass.length > 0) {
1680
2103
  className += (i > 0 ? ' ' : '') + klass + suffix;
1681
2104
  }
1682
2105
  });