material_raingular 0.0.7 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ddd28998b4e2f2bd41a91280caced0da45eae4b4
4
- data.tar.gz: dad9ec2912ce81cdef5a64996edf20be40b7076c
3
+ metadata.gz: 7420a6e01c283917cb97615df004d13474b5388a
4
+ data.tar.gz: dcab33fe3307b14c36b1e4f895c67d5983faccee
5
5
  SHA512:
6
- metadata.gz: 0772699cd84a41a42cab1d0fd59086eed6a24dec88d8657bfeb77caa1a57d1761988faa08916fa2f29d0bddacf641b2f2199091d98fab3865fced37f755564f2
7
- data.tar.gz: 70fb0fe98301c58b7c816abc6c300c7cc750ed0bc95eb0007f0bf5c06bf3f066820a8c6174479a329ba71efc081472b33ba7f3ebde2d7a38e73aca338c38a53e
6
+ metadata.gz: 0d0ea8125a33948cf5f406cd27110305ceb2e0475f1516784828599a45bd73eeac94e5c21b500906271938fd805a5c9a73e0c50642d1382202e7e6c9ea4eb39c
7
+ data.tar.gz: 30fb7edfd244066b7dd091bf405c9e470abfa6575650e4e5b0e178b3218635a21d5624e39eead40a13b290357ed0f009f3d561e66c193e8fcf336527d33e18bf
data/.gitignore CHANGED
@@ -11,4 +11,5 @@
11
11
  *.so
12
12
  *.o
13
13
  *.a
14
+ .DS_Store
14
15
  mkmf.log
@@ -1,3 +1,3 @@
1
1
  module MaterialRaingular
2
- VERSION = "0.0.7"
2
+ VERSION = "0.1.0"
3
3
  end
@@ -1,12 +1,13 @@
1
1
  /**
2
- * @license AngularJS v1.4.0
3
- * (c) 2010-2015 Google, Inc. http://angularjs.org
2
+ * @license AngularJS v1.5.3
3
+ * (c) 2010-2016 Google, Inc. http://angularjs.org
4
4
  * License: MIT
5
5
  */
6
6
  (function(window, angular, undefined) {'use strict';
7
7
 
8
8
  /* jshint ignore:start */
9
9
  var noop = angular.noop;
10
+ var copy = angular.copy;
10
11
  var extend = angular.extend;
11
12
  var jqLite = angular.element;
12
13
  var forEach = angular.forEach;
@@ -21,13 +22,63 @@ var isElement = angular.isElement;
21
22
  var ELEMENT_NODE = 1;
22
23
  var COMMENT_NODE = 8;
23
24
 
25
+ var ADD_CLASS_SUFFIX = '-add';
26
+ var REMOVE_CLASS_SUFFIX = '-remove';
27
+ var EVENT_CLASS_PREFIX = 'ng-';
28
+ var ACTIVE_CLASS_SUFFIX = '-active';
29
+ var PREPARE_CLASS_SUFFIX = '-prepare';
30
+
24
31
  var NG_ANIMATE_CLASSNAME = 'ng-animate';
25
32
  var NG_ANIMATE_CHILDREN_DATA = '$$ngAnimateChildren';
26
33
 
34
+ // Detect proper transitionend/animationend event names.
35
+ var CSS_PREFIX = '', TRANSITION_PROP, TRANSITIONEND_EVENT, ANIMATION_PROP, ANIMATIONEND_EVENT;
36
+
37
+ // If unprefixed events are not supported but webkit-prefixed are, use the latter.
38
+ // Otherwise, just use W3C names, browsers not supporting them at all will just ignore them.
39
+ // Note: Chrome implements `window.onwebkitanimationend` and doesn't implement `window.onanimationend`
40
+ // but at the same time dispatches the `animationend` event and not `webkitAnimationEnd`.
41
+ // Register both events in case `window.onanimationend` is not supported because of that,
42
+ // do the same for `transitionend` as Safari is likely to exhibit similar behavior.
43
+ // Also, the only modern browser that uses vendor prefixes for transitions/keyframes is webkit
44
+ // therefore there is no reason to test anymore for other vendor prefixes:
45
+ // http://caniuse.com/#search=transition
46
+ if (isUndefined(window.ontransitionend) && isDefined(window.onwebkittransitionend)) {
47
+ CSS_PREFIX = '-webkit-';
48
+ TRANSITION_PROP = 'WebkitTransition';
49
+ TRANSITIONEND_EVENT = 'webkitTransitionEnd transitionend';
50
+ } else {
51
+ TRANSITION_PROP = 'transition';
52
+ TRANSITIONEND_EVENT = 'transitionend';
53
+ }
54
+
55
+ if (isUndefined(window.onanimationend) && isDefined(window.onwebkitanimationend)) {
56
+ CSS_PREFIX = '-webkit-';
57
+ ANIMATION_PROP = 'WebkitAnimation';
58
+ ANIMATIONEND_EVENT = 'webkitAnimationEnd animationend';
59
+ } else {
60
+ ANIMATION_PROP = 'animation';
61
+ ANIMATIONEND_EVENT = 'animationend';
62
+ }
63
+
64
+ var DURATION_KEY = 'Duration';
65
+ var PROPERTY_KEY = 'Property';
66
+ var DELAY_KEY = 'Delay';
67
+ var TIMING_KEY = 'TimingFunction';
68
+ var ANIMATION_ITERATION_COUNT_KEY = 'IterationCount';
69
+ var ANIMATION_PLAYSTATE_KEY = 'PlayState';
70
+ var SAFE_FAST_FORWARD_DURATION_VALUE = 9999;
71
+
72
+ var ANIMATION_DELAY_PROP = ANIMATION_PROP + DELAY_KEY;
73
+ var ANIMATION_DURATION_PROP = ANIMATION_PROP + DURATION_KEY;
74
+ var TRANSITION_DELAY_PROP = TRANSITION_PROP + DELAY_KEY;
75
+ var TRANSITION_DURATION_PROP = TRANSITION_PROP + DURATION_KEY;
76
+
27
77
  var isPromiseLike = function(p) {
28
78
  return p && p.then ? true : false;
29
- }
79
+ };
30
80
 
81
+ var ngMinErr = angular.$$minErr('ng');
31
82
  function assertArg(arg, name, reason) {
32
83
  if (!arg) {
33
84
  throw ngMinErr('areq', "Argument '{0}' is {1}", (name || '?'), (reason || "required"));
@@ -172,13 +223,29 @@ function applyAnimationToStyles(element, options) {
172
223
  }
173
224
  }
174
225
 
175
- function mergeAnimationOptions(element, target, newOptions) {
226
+ function mergeAnimationDetails(element, oldAnimation, newAnimation) {
227
+ var target = oldAnimation.options || {};
228
+ var newOptions = newAnimation.options || {};
229
+
176
230
  var toAdd = (target.addClass || '') + ' ' + (newOptions.addClass || '');
177
231
  var toRemove = (target.removeClass || '') + ' ' + (newOptions.removeClass || '');
178
232
  var classes = resolveElementClasses(element.attr('class'), toAdd, toRemove);
179
233
 
234
+ if (newOptions.preparationClasses) {
235
+ target.preparationClasses = concatWithSpace(newOptions.preparationClasses, target.preparationClasses);
236
+ delete newOptions.preparationClasses;
237
+ }
238
+
239
+ // noop is basically when there is no callback; otherwise something has been set
240
+ var realDomOperation = target.domOperation !== noop ? target.domOperation : null;
241
+
180
242
  extend(target, newOptions);
181
243
 
244
+ // TODO(matsko or sreeramu): proper fix is to maintain all animation callback in array and call at last,but now only leave has the callback so no issue with this.
245
+ if (realDomOperation) {
246
+ target.domOperation = realDomOperation;
247
+ }
248
+
182
249
  if (classes.addClass) {
183
250
  target.addClass = classes.addClass;
184
251
  } else {
@@ -191,6 +258,9 @@ function mergeAnimationOptions(element, target, newOptions) {
191
258
  target.removeClass = null;
192
259
  }
193
260
 
261
+ oldAnimation.addClass = target.addClass;
262
+ oldAnimation.removeClass = target.removeClass;
263
+
194
264
  return target;
195
265
  }
196
266
 
@@ -256,20 +326,77 @@ function getDomNode(element) {
256
326
  return (element instanceof angular.element) ? element[0] : element;
257
327
  }
258
328
 
329
+ function applyGeneratedPreparationClasses(element, event, options) {
330
+ var classes = '';
331
+ if (event) {
332
+ classes = pendClasses(event, EVENT_CLASS_PREFIX, true);
333
+ }
334
+ if (options.addClass) {
335
+ classes = concatWithSpace(classes, pendClasses(options.addClass, ADD_CLASS_SUFFIX));
336
+ }
337
+ if (options.removeClass) {
338
+ classes = concatWithSpace(classes, pendClasses(options.removeClass, REMOVE_CLASS_SUFFIX));
339
+ }
340
+ if (classes.length) {
341
+ options.preparationClasses = classes;
342
+ element.addClass(classes);
343
+ }
344
+ }
345
+
346
+ function clearGeneratedClasses(element, options) {
347
+ if (options.preparationClasses) {
348
+ element.removeClass(options.preparationClasses);
349
+ options.preparationClasses = null;
350
+ }
351
+ if (options.activeClasses) {
352
+ element.removeClass(options.activeClasses);
353
+ options.activeClasses = null;
354
+ }
355
+ }
356
+
357
+ function blockTransitions(node, duration) {
358
+ // we use a negative delay value since it performs blocking
359
+ // yet it doesn't kill any existing transitions running on the
360
+ // same element which makes this safe for class-based animations
361
+ var value = duration ? '-' + duration + 's' : '';
362
+ applyInlineStyle(node, [TRANSITION_DELAY_PROP, value]);
363
+ return [TRANSITION_DELAY_PROP, value];
364
+ }
365
+
366
+ function blockKeyframeAnimations(node, applyBlock) {
367
+ var value = applyBlock ? 'paused' : '';
368
+ var key = ANIMATION_PROP + ANIMATION_PLAYSTATE_KEY;
369
+ applyInlineStyle(node, [key, value]);
370
+ return [key, value];
371
+ }
372
+
373
+ function applyInlineStyle(node, styleTuple) {
374
+ var prop = styleTuple[0];
375
+ var value = styleTuple[1];
376
+ node.style[prop] = value;
377
+ }
378
+
379
+ function concatWithSpace(a,b) {
380
+ if (!a) return b;
381
+ if (!b) return a;
382
+ return a + ' ' + b;
383
+ }
384
+
259
385
  var $$rAFSchedulerFactory = ['$$rAF', function($$rAF) {
260
- var tickQueue = [];
261
- var cancelFn;
386
+ var queue, cancelFn;
262
387
 
263
388
  function scheduler(tasks) {
264
389
  // we make a copy since RAFScheduler mutates the state
265
390
  // of the passed in array variable and this would be difficult
266
391
  // to track down on the outside code
267
- tickQueue.push([].concat(tasks));
392
+ queue = queue.concat(tasks);
268
393
  nextTick();
269
394
  }
270
395
 
396
+ queue = scheduler.queue = [];
397
+
271
398
  /* waitUntilQuiet does two things:
272
- * 1. It will run the FINAL `fn` value only when an uncancelled RAF has passed through
399
+ * 1. It will run the FINAL `fn` value only when an uncanceled RAF has passed through
273
400
  * 2. It will delay the next wave of tasks from running until the quiet `fn` has run.
274
401
  *
275
402
  * The motivation here is that animation code can request more time from the scheduler
@@ -289,17 +416,12 @@ var $$rAFSchedulerFactory = ['$$rAF', function($$rAF) {
289
416
  return scheduler;
290
417
 
291
418
  function nextTick() {
292
- if (!tickQueue.length) return;
419
+ if (!queue.length) return;
293
420
 
294
- var updatedQueue = [];
295
- for (var i = 0; i < tickQueue.length; i++) {
296
- var innerQueue = tickQueue[i];
297
- runNextTask(innerQueue);
298
- if (innerQueue.length) {
299
- updatedQueue.push(innerQueue);
300
- }
421
+ var items = queue.shift();
422
+ for (var i = 0; i < items.length; i++) {
423
+ items[i]();
301
424
  }
302
- tickQueue = updatedQueue;
303
425
 
304
426
  if (!cancelFn) {
305
427
  $$rAF(function() {
@@ -307,27 +429,109 @@ var $$rAFSchedulerFactory = ['$$rAF', function($$rAF) {
307
429
  });
308
430
  }
309
431
  }
310
-
311
- function runNextTask(tasks) {
312
- var nextTask = tasks.shift();
313
- nextTask();
314
- }
315
432
  }];
316
433
 
317
- var $$AnimateChildrenDirective = [function() {
318
- return function(scope, element, attrs) {
319
- var val = attrs.ngAnimateChildren;
320
- if (angular.isString(val) && val.length === 0) { //empty attribute
321
- element.data(NG_ANIMATE_CHILDREN_DATA, true);
322
- } else {
323
- attrs.$observe('ngAnimateChildren', function(value) {
434
+ /**
435
+ * @ngdoc directive
436
+ * @name ngAnimateChildren
437
+ * @restrict AE
438
+ * @element ANY
439
+ *
440
+ * @description
441
+ *
442
+ * ngAnimateChildren allows you to specify that children of this element should animate even if any
443
+ * of the children's parents are currently animating. By default, when an element has an active `enter`, `leave`, or `move`
444
+ * (structural) animation, child elements that also have an active structural animation are not animated.
445
+ *
446
+ * Note that even if `ngAnimteChildren` is set, no child animations will run when the parent element is removed from the DOM (`leave` animation).
447
+ *
448
+ *
449
+ * @param {string} ngAnimateChildren If the value is empty, `true` or `on`,
450
+ * then child animations are allowed. If the value is `false`, child animations are not allowed.
451
+ *
452
+ * @example
453
+ * <example module="ngAnimateChildren" name="ngAnimateChildren" deps="angular-animate.js" animations="true">
454
+ <file name="index.html">
455
+ <div ng-controller="mainController as main">
456
+ <label>Show container? <input type="checkbox" ng-model="main.enterElement" /></label>
457
+ <label>Animate children? <input type="checkbox" ng-model="main.animateChildren" /></label>
458
+ <hr>
459
+ <div ng-animate-children="{{main.animateChildren}}">
460
+ <div ng-if="main.enterElement" class="container">
461
+ List of items:
462
+ <div ng-repeat="item in [0, 1, 2, 3]" class="item">Item {{item}}</div>
463
+ </div>
464
+ </div>
465
+ </div>
466
+ </file>
467
+ <file name="animations.css">
468
+
469
+ .container.ng-enter,
470
+ .container.ng-leave {
471
+ transition: all ease 1.5s;
472
+ }
473
+
474
+ .container.ng-enter,
475
+ .container.ng-leave-active {
476
+ opacity: 0;
477
+ }
478
+
479
+ .container.ng-leave,
480
+ .container.ng-enter-active {
481
+ opacity: 1;
482
+ }
483
+
484
+ .item {
485
+ background: firebrick;
486
+ color: #FFF;
487
+ margin-bottom: 10px;
488
+ }
489
+
490
+ .item.ng-enter,
491
+ .item.ng-leave {
492
+ transition: transform 1.5s ease;
493
+ }
494
+
495
+ .item.ng-enter {
496
+ transform: translateX(50px);
497
+ }
498
+
499
+ .item.ng-enter-active {
500
+ transform: translateX(0);
501
+ }
502
+ </file>
503
+ <file name="script.js">
504
+ angular.module('ngAnimateChildren', ['ngAnimate'])
505
+ .controller('mainController', function() {
506
+ this.animateChildren = false;
507
+ this.enterElement = false;
508
+ });
509
+ </file>
510
+ </example>
511
+ */
512
+ var $$AnimateChildrenDirective = ['$interpolate', function($interpolate) {
513
+ return {
514
+ link: function(scope, element, attrs) {
515
+ var val = attrs.ngAnimateChildren;
516
+ if (angular.isString(val) && val.length === 0) { //empty attribute
517
+ element.data(NG_ANIMATE_CHILDREN_DATA, true);
518
+ } else {
519
+ // Interpolate and set the value, so that it is available to
520
+ // animations that run right after compilation
521
+ setData($interpolate(val)(scope));
522
+ attrs.$observe('ngAnimateChildren', setData);
523
+ }
524
+
525
+ function setData(value) {
324
526
  value = value === 'on' || value === 'true';
325
527
  element.data(NG_ANIMATE_CHILDREN_DATA, value);
326
- });
528
+ }
327
529
  }
328
530
  };
329
531
  }];
330
532
 
533
+ var ANIMATE_TIMER_KEY = '$$animateCss';
534
+
331
535
  /**
332
536
  * @ngdoc service
333
537
  * @name $animateCss
@@ -493,7 +697,7 @@ var $$AnimateChildrenDirective = [function() {
493
697
  * ```
494
698
  *
495
699
  * To actually start the animation we need to run `animation.start()` which will then return a promise that we can hook into to detect when the animation ends.
496
- * If we choose not to run the animation then we MUST run `animation.end()` to perform a cleanup on the element (since some CSS classes and stlyes may have been
700
+ * If we choose not to run the animation then we MUST run `animation.end()` to perform a cleanup on the element (since some CSS classes and styles may have been
497
701
  * applied to the element during the preparation phase). Note that all other properties such as duration, delay, transitions and keyframes are just properties
498
702
  * and that changing them will not reconfigure the parameters of the animation.
499
703
  *
@@ -504,16 +708,19 @@ var $$AnimateChildrenDirective = [function() {
504
708
  * unless you really need a digest to kick off afterwards.
505
709
  *
506
710
  * Keep in mind that, to make this easier, ngAnimate has tweaked the JS animations API to recognize when a runner instance is returned from $animateCss
507
- * (so there is no need to call `runner.done(doneFn)` inside of your JavaScript animation code). Check the [animation code above](#usage) to see how this works.
711
+ * (so there is no need to call `runner.done(doneFn)` inside of your JavaScript animation code).
712
+ * Check the {@link ngAnimate.$animateCss#usage animation code above} to see how this works.
508
713
  *
509
714
  * @param {DOMElement} element the element that will be animated
510
715
  * @param {object} options the animation-related options that will be applied during the animation
511
716
  *
512
717
  * * `event` - The DOM event (e.g. enter, leave, move). When used, a generated CSS class of `ng-EVENT` and `ng-EVENT-active` will be applied
513
718
  * to the element during the animation. Multiple events can be provided when spaces are used as a separator. (Note that this will not perform any DOM operation.)
719
+ * * `structural` - Indicates that the `ng-` prefix will be added to the event class. Setting to `false` or omitting will turn `ng-EVENT` and
720
+ * `ng-EVENT-active` in `EVENT` and `EVENT-active`. Unused if `event` is omitted.
514
721
  * * `easing` - The CSS easing value that will be applied to the transition or keyframe animation (or both).
515
- * * `transition` - The raw CSS transition style that will be used (e.g. `1s linear all`).
516
- * * `keyframe` - The raw CSS keyframe animation style that will be used (e.g. `1s my_animation linear`).
722
+ * * `transitionStyle` - The raw CSS transition style that will be used (e.g. `1s linear all`).
723
+ * * `keyframeStyle` - The raw CSS keyframe animation style that will be used (e.g. `1s my_animation linear`).
517
724
  * * `from` - The starting CSS styles (a key/value object) that will be applied at the start of the animation.
518
725
  * * `to` - The ending CSS styles (a key/value object) that will be applied across the animation via a CSS transition.
519
726
  * * `addClass` - A space separated list of CSS classes that will be added to the element and spread across the animation.
@@ -527,63 +734,23 @@ var $$AnimateChildrenDirective = [function() {
527
734
  * * `stagger` - A numeric time value representing the delay between successively animated elements
528
735
  * ({@link ngAnimate#css-staggering-animations Click here to learn how CSS-based staggering works in ngAnimate.})
529
736
  * * `staggerIndex` - The numeric index representing the stagger item (e.g. a value of 5 is equal to the sixth item in the stagger; therefore when a
530
- * `stagger` option value of `0.1` is used then there will be a stagger delay of `600ms`)
531
- * `applyClassesEarly` - Whether or not the classes being added or removed will be used when detecting the animation. This is set by `$animate` when enter/leave/move animations are fired to ensure that the CSS classes are resolved in time. (Note that this will prevent any transitions from occuring on the classes being added and removed.)
737
+ * `stagger` option value of `0.1` is used then there will be a stagger delay of `600ms`)
738
+ * * `applyClassesEarly` - Whether or not the classes being added or removed will be used when detecting the animation. This is set by `$animate` when enter/leave/move animations are fired to ensure that the CSS classes are resolved in time. (Note that this will prevent any transitions from occurring on the classes being added and removed.)
739
+ * * `cleanupStyles` - Whether or not the provided `from` and `to` styles will be removed once
740
+ * the animation is closed. This is useful for when the styles are used purely for the sake of
741
+ * the animation and do not have a lasting visual effect on the element (e.g. a collapse and open animation).
742
+ * By default this value is set to `false`.
532
743
  *
533
744
  * @return {object} an object with start and end methods and details about the animation.
534
745
  *
535
746
  * * `start` - The method to start the animation. This will return a `Promise` when called.
536
747
  * * `end` - This method will cancel the animation and remove all applied CSS classes and styles.
537
748
  */
538
-
539
- // Detect proper transitionend/animationend event names.
540
- var CSS_PREFIX = '', TRANSITION_PROP, TRANSITIONEND_EVENT, ANIMATION_PROP, ANIMATIONEND_EVENT;
541
-
542
- // If unprefixed events are not supported but webkit-prefixed are, use the latter.
543
- // Otherwise, just use W3C names, browsers not supporting them at all will just ignore them.
544
- // Note: Chrome implements `window.onwebkitanimationend` and doesn't implement `window.onanimationend`
545
- // but at the same time dispatches the `animationend` event and not `webkitAnimationEnd`.
546
- // Register both events in case `window.onanimationend` is not supported because of that,
547
- // do the same for `transitionend` as Safari is likely to exhibit similar behavior.
548
- // Also, the only modern browser that uses vendor prefixes for transitions/keyframes is webkit
549
- // therefore there is no reason to test anymore for other vendor prefixes:
550
- // http://caniuse.com/#search=transition
551
- if (window.ontransitionend === undefined && window.onwebkittransitionend !== undefined) {
552
- CSS_PREFIX = '-webkit-';
553
- TRANSITION_PROP = 'WebkitTransition';
554
- TRANSITIONEND_EVENT = 'webkitTransitionEnd transitionend';
555
- } else {
556
- TRANSITION_PROP = 'transition';
557
- TRANSITIONEND_EVENT = 'transitionend';
558
- }
559
-
560
- if (window.onanimationend === undefined && window.onwebkitanimationend !== undefined) {
561
- CSS_PREFIX = '-webkit-';
562
- ANIMATION_PROP = 'WebkitAnimation';
563
- ANIMATIONEND_EVENT = 'webkitAnimationEnd animationend';
564
- } else {
565
- ANIMATION_PROP = 'animation';
566
- ANIMATIONEND_EVENT = 'animationend';
567
- }
568
-
569
- var DURATION_KEY = 'Duration';
570
- var PROPERTY_KEY = 'Property';
571
- var DELAY_KEY = 'Delay';
572
- var TIMING_KEY = 'TimingFunction';
573
- var ANIMATION_ITERATION_COUNT_KEY = 'IterationCount';
574
- var ANIMATION_PLAYSTATE_KEY = 'PlayState';
575
- var ELAPSED_TIME_MAX_DECIMAL_PLACES = 3;
576
- var CLOSING_TIME_BUFFER = 1.5;
577
749
  var ONE_SECOND = 1000;
578
750
  var BASE_TEN = 10;
579
751
 
580
- var SAFE_FAST_FORWARD_DURATION_VALUE = 9999;
581
-
582
- var ANIMATION_DELAY_PROP = ANIMATION_PROP + DELAY_KEY;
583
- var ANIMATION_DURATION_PROP = ANIMATION_PROP + DURATION_KEY;
584
-
585
- var TRANSITION_DELAY_PROP = TRANSITION_PROP + DELAY_KEY;
586
- var TRANSITION_DURATION_PROP = TRANSITION_PROP + DURATION_KEY;
752
+ var ELAPSED_TIME_MAX_DECIMAL_PLACES = 3;
753
+ var CLOSING_TIME_BUFFER = 1.5;
587
754
 
588
755
  var DETECT_CSS_PROPERTIES = {
589
756
  transitionDuration: TRANSITION_DURATION_PROP,
@@ -601,6 +768,15 @@ var DETECT_STAGGER_CSS_PROPERTIES = {
601
768
  animationDelay: ANIMATION_DELAY_PROP
602
769
  };
603
770
 
771
+ function getCssKeyframeDurationStyle(duration) {
772
+ return [ANIMATION_DURATION_PROP, duration + 's'];
773
+ }
774
+
775
+ function getCssDelayStyle(delay, isKeyframeAnimation) {
776
+ var prop = isKeyframeAnimation ? ANIMATION_DELAY_PROP : TRANSITION_DELAY_PROP;
777
+ return [prop, delay + 's'];
778
+ }
779
+
604
780
  function computeCssStyles($window, element, properties) {
605
781
  var styles = Object.create(null);
606
782
  var detectedStyles = $window.getComputedStyle(element) || {};
@@ -615,7 +791,7 @@ function computeCssStyles($window, element, properties) {
615
791
  }
616
792
 
617
793
  // by setting this to null in the event that the delay is not set or is set directly as 0
618
- // then we can still allow for zegative values to be used later on and not mistake this
794
+ // then we can still allow for negative values to be used later on and not mistake this
619
795
  // value for being greater than any other negative value.
620
796
  if (val === 0) {
621
797
  val = null;
@@ -657,37 +833,6 @@ function getCssTransitionDurationStyle(duration, applyOnlyDuration) {
657
833
  return [style, value];
658
834
  }
659
835
 
660
- function getCssKeyframeDurationStyle(duration) {
661
- return [ANIMATION_DURATION_PROP, duration + 's'];
662
- }
663
-
664
- function getCssDelayStyle(delay, isKeyframeAnimation) {
665
- var prop = isKeyframeAnimation ? ANIMATION_DELAY_PROP : TRANSITION_DELAY_PROP;
666
- return [prop, delay + 's'];
667
- }
668
-
669
- function blockTransitions(node, duration) {
670
- // we use a negative delay value since it performs blocking
671
- // yet it doesn't kill any existing transitions running on the
672
- // same element which makes this safe for class-based animations
673
- var value = duration ? '-' + duration + 's' : '';
674
- applyInlineStyle(node, [TRANSITION_DELAY_PROP, value]);
675
- return [TRANSITION_DELAY_PROP, value];
676
- }
677
-
678
- function blockKeyframeAnimations(node, applyBlock) {
679
- var value = applyBlock ? 'paused' : '';
680
- var key = ANIMATION_PROP + ANIMATION_PLAYSTATE_KEY;
681
- applyInlineStyle(node, [key, value]);
682
- return [key, value];
683
- }
684
-
685
- function applyInlineStyle(node, styleTuple) {
686
- var prop = styleTuple[0];
687
- var value = styleTuple[1];
688
- node.style[prop] = value;
689
- }
690
-
691
836
  function createLocalCacheLookup() {
692
837
  var cache = Object.create(null);
693
838
  return {
@@ -715,14 +860,31 @@ function createLocalCacheLookup() {
715
860
  };
716
861
  }
717
862
 
863
+ // we do not reassign an already present style value since
864
+ // if we detect the style property value again we may be
865
+ // detecting styles that were added via the `from` styles.
866
+ // We make use of `isDefined` here since an empty string
867
+ // or null value (which is what getPropertyValue will return
868
+ // for a non-existing style) will still be marked as a valid
869
+ // value for the style (a falsy value implies that the style
870
+ // is to be removed at the end of the animation). If we had a simple
871
+ // "OR" statement then it would not be enough to catch that.
872
+ function registerRestorableStyles(backup, node, properties) {
873
+ forEach(properties, function(prop) {
874
+ backup[prop] = isDefined(backup[prop])
875
+ ? backup[prop]
876
+ : node.style.getPropertyValue(prop);
877
+ });
878
+ }
879
+
718
880
  var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
719
881
  var gcsLookup = createLocalCacheLookup();
720
882
  var gcsStaggerLookup = createLocalCacheLookup();
721
883
 
722
884
  this.$get = ['$window', '$$jqLite', '$$AnimateRunner', '$timeout',
723
- '$document', '$sniffer', '$$rAFScheduler',
885
+ '$$forceReflow', '$sniffer', '$$rAFScheduler', '$$animateQueue',
724
886
  function($window, $$jqLite, $$AnimateRunner, $timeout,
725
- $document, $sniffer, $$rAFScheduler) {
887
+ $$forceReflow, $sniffer, $$rAFScheduler, $$animateQueue) {
726
888
 
727
889
  var applyAnimationClasses = applyAnimationClassesFactory($$jqLite);
728
890
 
@@ -745,7 +907,7 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
745
907
  }
746
908
 
747
909
  // we keep putting this in multiple times even though the value and the cacheKey are the same
748
- // because we're keeping an interal tally of how many duplicate animations are detected.
910
+ // because we're keeping an internal tally of how many duplicate animations are detected.
749
911
  gcsLookup.put(cacheKey, timings);
750
912
  return timings;
751
913
  }
@@ -779,7 +941,7 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
779
941
  return stagger || {};
780
942
  }
781
943
 
782
- var bod = getDomNode($document).body;
944
+ var cancelLastRAFRequest;
783
945
  var rafWaitQueue = [];
784
946
  function waitUntilQuiet(callback) {
785
947
  rafWaitQueue.push(callback);
@@ -787,27 +949,19 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
787
949
  gcsLookup.flush();
788
950
  gcsStaggerLookup.flush();
789
951
 
790
- //the line below will force the browser to perform a repaint so
791
- //that all the animated elements within the animation frame will
792
- //be properly updated and drawn on screen. This is required to
793
- //ensure that the preparation animation is properly flushed so that
794
- //the active state picks up from there. DO NOT REMOVE THIS LINE.
795
- //DO NOT OPTIMIZE THIS LINE. THE MINIFIER WILL REMOVE IT OTHERWISE WHICH
796
- //WILL RESULT IN AN UNPREDICTABLE BUG THAT IS VERY HARD TO TRACK DOWN AND
797
- //WILL TAKE YEARS AWAY FROM YOUR LIFE.
798
- var width = bod.offsetWidth + 1;
952
+ // DO NOT REMOVE THIS LINE OR REFACTOR OUT THE `pageWidth` variable.
953
+ // PLEASE EXAMINE THE `$$forceReflow` service to understand why.
954
+ var pageWidth = $$forceReflow();
799
955
 
800
956
  // we use a for loop to ensure that if the queue is changed
801
957
  // during this looping then it will consider new requests
802
958
  for (var i = 0; i < rafWaitQueue.length; i++) {
803
- rafWaitQueue[i](width);
959
+ rafWaitQueue[i](pageWidth);
804
960
  }
805
961
  rafWaitQueue.length = 0;
806
962
  });
807
963
  }
808
964
 
809
- return init;
810
-
811
965
  function computeTimings(node, className, cacheKey) {
812
966
  var timings = computeCachedCssStyles(node, className, cacheKey, DETECT_CSS_PROPERTIES);
813
967
  var aD = timings.animationDelay;
@@ -822,9 +976,23 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
822
976
  return timings;
823
977
  }
824
978
 
825
- function init(element, options) {
979
+ return function init(element, initialOptions) {
980
+ // all of the animation functions should create
981
+ // a copy of the options data, however, if a
982
+ // parent service has already created a copy then
983
+ // we should stick to using that
984
+ var options = initialOptions || {};
985
+ if (!options.$$prepared) {
986
+ options = prepareAnimationOptions(copy(options));
987
+ }
988
+
989
+ var restoreStyles = {};
826
990
  var node = getDomNode(element);
827
- options = prepareAnimationOptions(options);
991
+ if (!node
992
+ || !node.parentNode
993
+ || !$$animateQueue.enabled()) {
994
+ return closeAndReturnNoopAnimator();
995
+ }
828
996
 
829
997
  var temporaryStyles = [];
830
998
  var classes = element.attr('class');
@@ -838,6 +1006,8 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
838
1006
  var maxDelayTime;
839
1007
  var maxDuration;
840
1008
  var maxDurationTime;
1009
+ var startTime;
1010
+ var events = [];
841
1011
 
842
1012
  if (options.duration === 0 || (!$sniffer.animations && !$sniffer.transitions)) {
843
1013
  return closeAndReturnNoopAnimator();
@@ -852,20 +1022,20 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
852
1022
  var addRemoveClassName = '';
853
1023
 
854
1024
  if (isStructural) {
855
- structuralClassName = pendClasses(method, 'ng-', true);
1025
+ structuralClassName = pendClasses(method, EVENT_CLASS_PREFIX, true);
856
1026
  } else if (method) {
857
1027
  structuralClassName = method;
858
1028
  }
859
1029
 
860
1030
  if (options.addClass) {
861
- addRemoveClassName += pendClasses(options.addClass, '-add');
1031
+ addRemoveClassName += pendClasses(options.addClass, ADD_CLASS_SUFFIX);
862
1032
  }
863
1033
 
864
1034
  if (options.removeClass) {
865
1035
  if (addRemoveClassName.length) {
866
1036
  addRemoveClassName += ' ';
867
1037
  }
868
- addRemoveClassName += pendClasses(options.removeClass, '-remove');
1038
+ addRemoveClassName += pendClasses(options.removeClass, REMOVE_CLASS_SUFFIX);
869
1039
  }
870
1040
 
871
1041
  // there may be a situation where a structural animation is combined together
@@ -876,17 +1046,20 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
876
1046
  // there actually is a detected transition or keyframe animation
877
1047
  if (options.applyClassesEarly && addRemoveClassName.length) {
878
1048
  applyAnimationClasses(element, options);
879
- addRemoveClassName = '';
880
1049
  }
881
1050
 
882
- var setupClasses = [structuralClassName, addRemoveClassName].join(' ').trim();
883
- var fullClassName = classes + ' ' + setupClasses;
884
- var activeClasses = pendClasses(setupClasses, '-active');
1051
+ var preparationClasses = [structuralClassName, addRemoveClassName].join(' ').trim();
1052
+ var fullClassName = classes + ' ' + preparationClasses;
1053
+ var activeClasses = pendClasses(preparationClasses, ACTIVE_CLASS_SUFFIX);
885
1054
  var hasToStyles = styles.to && Object.keys(styles.to).length > 0;
886
-
887
- // there is no way we can trigger an animation since no styles and
888
- // no classes are being applied which would then trigger a transition
889
- if (!hasToStyles && !setupClasses) {
1055
+ var containsKeyframeAnimation = (options.keyframeStyle || '').length > 0;
1056
+
1057
+ // there is no way we can trigger an animation if no styles and
1058
+ // no classes are being applied which would then trigger a transition,
1059
+ // unless there a is raw keyframe value that is applied to the element.
1060
+ if (!containsKeyframeAnimation
1061
+ && !hasToStyles
1062
+ && !preparationClasses) {
890
1063
  return closeAndReturnNoopAnimator();
891
1064
  }
892
1065
 
@@ -901,10 +1074,12 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
901
1074
  };
902
1075
  } else {
903
1076
  cacheKey = gcsHashFn(node, fullClassName);
904
- stagger = computeCachedCssStaggerStyles(node, setupClasses, cacheKey, DETECT_STAGGER_CSS_PROPERTIES);
1077
+ stagger = computeCachedCssStaggerStyles(node, preparationClasses, cacheKey, DETECT_STAGGER_CSS_PROPERTIES);
905
1078
  }
906
1079
 
907
- $$jqLite.addClass(element, setupClasses);
1080
+ if (!options.$$skipPreparationClasses) {
1081
+ $$jqLite.addClass(element, preparationClasses);
1082
+ }
908
1083
 
909
1084
  var applyOnlyDuration;
910
1085
 
@@ -943,7 +1118,7 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
943
1118
  // transition delay to allow for the transition to naturally do it's thing. The beauty here is
944
1119
  // that if there is no transition defined then nothing will happen and this will also allow
945
1120
  // other transitions to be stacked on top of each other without any chopping them out.
946
- if (isFirst) {
1121
+ if (isFirst && !options.skipBlocking) {
947
1122
  blockTransitions(node, SAFE_FAST_FORWARD_DURATION_VALUE);
948
1123
  }
949
1124
 
@@ -985,6 +1160,23 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
985
1160
  return closeAndReturnNoopAnimator();
986
1161
  }
987
1162
 
1163
+ if (options.delay != null) {
1164
+ var delayStyle;
1165
+ if (typeof options.delay !== "boolean") {
1166
+ delayStyle = parseFloat(options.delay);
1167
+ // number in options.delay means we have to recalculate the delay for the closing timeout
1168
+ maxDelay = Math.max(delayStyle, 0);
1169
+ }
1170
+
1171
+ if (flags.applyTransitionDelay) {
1172
+ temporaryStyles.push(getCssDelayStyle(delayStyle));
1173
+ }
1174
+
1175
+ if (flags.applyAnimationDelay) {
1176
+ temporaryStyles.push(getCssDelayStyle(delayStyle, true));
1177
+ }
1178
+ }
1179
+
988
1180
  // we need to recalculate the delay value since we used a pre-emptive negative
989
1181
  // delay value and the delay value is required for the final event checking. This
990
1182
  // property will ensure that this will happen after the RAF phase has passed.
@@ -1001,12 +1193,18 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
1001
1193
  stagger.animationDuration === 0;
1002
1194
  }
1003
1195
 
1004
- applyAnimationFromStyles(element, options);
1005
- if (!flags.blockTransition) {
1006
- blockTransitions(node, false);
1196
+ if (options.from) {
1197
+ if (options.cleanupStyles) {
1198
+ registerRestorableStyles(restoreStyles, node, Object.keys(options.from));
1199
+ }
1200
+ applyAnimationFromStyles(element, options);
1007
1201
  }
1008
1202
 
1009
- applyBlocking(maxDuration);
1203
+ if (flags.blockTransition || flags.blockKeyframeAnimation) {
1204
+ applyBlocking(maxDuration);
1205
+ } else if (!options.skipBlocking) {
1206
+ blockTransitions(node, false);
1207
+ }
1010
1208
 
1011
1209
  // TODO(matsko): for 1.5 change this code to have an animator object for better debugging
1012
1210
  return {
@@ -1049,7 +1247,9 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
1049
1247
  animationClosed = true;
1050
1248
  animationPaused = false;
1051
1249
 
1052
- $$jqLite.removeClass(element, setupClasses);
1250
+ if (!options.$$skipPreparationClasses) {
1251
+ $$jqLite.removeClass(element, preparationClasses);
1252
+ }
1053
1253
  $$jqLite.removeClass(element, activeClasses);
1054
1254
 
1055
1255
  blockKeyframeAnimations(node, false);
@@ -1065,6 +1265,13 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
1065
1265
  applyAnimationClasses(element, options);
1066
1266
  applyAnimationStyles(element, options);
1067
1267
 
1268
+ if (Object.keys(restoreStyles).length) {
1269
+ forEach(restoreStyles, function(value, prop) {
1270
+ value ? node.style.setProperty(prop, value)
1271
+ : node.style.removeProperty(prop);
1272
+ });
1273
+ }
1274
+
1068
1275
  // the reason why we have this option is to allow a synchronous closing callback
1069
1276
  // that is fired as SOON as the animation ends (when the CSS is removed) or if
1070
1277
  // the animation never takes off at all. A good example is a leave animation since
@@ -1074,6 +1281,18 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
1074
1281
  options.onDone();
1075
1282
  }
1076
1283
 
1284
+ if (events && events.length) {
1285
+ // Remove the transitionend / animationend listener(s)
1286
+ element.off(events.join(' '), onAnimationProgress);
1287
+ }
1288
+
1289
+ //Cancel the fallback closing timeout and remove the timer data
1290
+ var animationTimerData = element.data(ANIMATE_TIMER_KEY);
1291
+ if (animationTimerData) {
1292
+ $timeout.cancel(animationTimerData[0].timer);
1293
+ element.removeData(ANIMATE_TIMER_KEY);
1294
+ }
1295
+
1077
1296
  // if the preparation function fails then the promise is not setup
1078
1297
  if (runner) {
1079
1298
  runner.complete(!rejected);
@@ -1096,6 +1315,8 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
1096
1315
  cancel: cancelFn
1097
1316
  });
1098
1317
 
1318
+ // should flush the cache animation
1319
+ waitUntilQuiet(noop);
1099
1320
  close();
1100
1321
 
1101
1322
  return {
@@ -1107,10 +1328,39 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
1107
1328
  };
1108
1329
  }
1109
1330
 
1331
+ function onAnimationProgress(event) {
1332
+ event.stopPropagation();
1333
+ var ev = event.originalEvent || event;
1334
+
1335
+ // we now always use `Date.now()` due to the recent changes with
1336
+ // event.timeStamp in Firefox, Webkit and Chrome (see #13494 for more info)
1337
+ var timeStamp = ev.$manualTimeStamp || Date.now();
1338
+
1339
+ /* Firefox (or possibly just Gecko) likes to not round values up
1340
+ * when a ms measurement is used for the animation */
1341
+ var elapsedTime = parseFloat(ev.elapsedTime.toFixed(ELAPSED_TIME_MAX_DECIMAL_PLACES));
1342
+
1343
+ /* $manualTimeStamp is a mocked timeStamp value which is set
1344
+ * within browserTrigger(). This is only here so that tests can
1345
+ * mock animations properly. Real events fallback to event.timeStamp,
1346
+ * or, if they don't, then a timeStamp is automatically created for them.
1347
+ * We're checking to see if the timeStamp surpasses the expected delay,
1348
+ * but we're using elapsedTime instead of the timeStamp on the 2nd
1349
+ * pre-condition since animationPauseds sometimes close off early */
1350
+ if (Math.max(timeStamp - startTime, 0) >= maxDelayTime && elapsedTime >= maxDuration) {
1351
+ // we set this flag to ensure that if the transition is paused then, when resumed,
1352
+ // the animation will automatically close itself since transitions cannot be paused.
1353
+ animationCompleted = true;
1354
+ close();
1355
+ }
1356
+ }
1357
+
1110
1358
  function start() {
1111
1359
  if (animationClosed) return;
1112
-
1113
- var startTime, events = [];
1360
+ if (!node.parentNode) {
1361
+ close();
1362
+ return;
1363
+ }
1114
1364
 
1115
1365
  // even though we only pause keyframe animations here the pause flag
1116
1366
  // will still happen when transitions are used. Only the transition will
@@ -1131,9 +1381,9 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
1131
1381
  }
1132
1382
  };
1133
1383
 
1134
- // checking the stagger duration prevents an accidently cascade of the CSS delay style
1384
+ // checking the stagger duration prevents an accidentally cascade of the CSS delay style
1135
1385
  // being inherited from the parent. If the transition duration is zero then we can safely
1136
- // rely that the delay value is an intential stagger delay style.
1386
+ // rely that the delay value is an intentional stagger delay style.
1137
1387
  var maxStagger = itemIndex > 0
1138
1388
  && ((timings.transitionDuration && stagger.transitionDuration === 0) ||
1139
1389
  (timings.animationDuration && stagger.animationDuration === 0))
@@ -1172,7 +1422,7 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
1172
1422
  $$jqLite.addClass(element, activeClasses);
1173
1423
 
1174
1424
  if (flags.recalculateTimingStyles) {
1175
- fullClassName = node.className + ' ' + setupClasses;
1425
+ fullClassName = node.className + ' ' + preparationClasses;
1176
1426
  cacheKey = gcsHashFn(node, fullClassName);
1177
1427
 
1178
1428
  timings = computeTimings(node, fullClassName, cacheKey);
@@ -1189,27 +1439,16 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
1189
1439
  flags.hasAnimations = timings.animationDuration > 0;
1190
1440
  }
1191
1441
 
1192
- if (flags.applyTransitionDelay || flags.applyAnimationDelay) {
1442
+ if (flags.applyAnimationDelay) {
1193
1443
  relativeDelay = typeof options.delay !== "boolean" && truthyTimingValue(options.delay)
1194
1444
  ? parseFloat(options.delay)
1195
1445
  : relativeDelay;
1196
1446
 
1197
1447
  maxDelay = Math.max(relativeDelay, 0);
1198
-
1199
- var delayStyle;
1200
- if (flags.applyTransitionDelay) {
1201
- timings.transitionDelay = relativeDelay;
1202
- delayStyle = getCssDelayStyle(relativeDelay);
1203
- temporaryStyles.push(delayStyle);
1204
- node.style[delayStyle[0]] = delayStyle[1];
1205
- }
1206
-
1207
- if (flags.applyAnimationDelay) {
1208
- timings.animationDelay = relativeDelay;
1209
- delayStyle = getCssDelayStyle(relativeDelay, true);
1210
- temporaryStyles.push(delayStyle);
1211
- node.style[delayStyle[0]] = delayStyle[1];
1212
- }
1448
+ timings.animationDelay = relativeDelay;
1449
+ delayStyle = getCssDelayStyle(relativeDelay, true);
1450
+ temporaryStyles.push(delayStyle);
1451
+ node.style[delayStyle[0]] = delayStyle[1];
1213
1452
  }
1214
1453
 
1215
1454
  maxDelayTime = maxDelay * ONE_SECOND;
@@ -1238,44 +1477,58 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
1238
1477
  }
1239
1478
 
1240
1479
  startTime = Date.now();
1241
- element.on(events.join(' '), onAnimationProgress);
1242
- $timeout(onAnimationExpired, maxDelayTime + CLOSING_TIME_BUFFER * maxDurationTime);
1480
+ var timerTime = maxDelayTime + CLOSING_TIME_BUFFER * maxDurationTime;
1481
+ var endTime = startTime + timerTime;
1482
+
1483
+ var animationsData = element.data(ANIMATE_TIMER_KEY) || [];
1484
+ var setupFallbackTimer = true;
1485
+ if (animationsData.length) {
1486
+ var currentTimerData = animationsData[0];
1487
+ setupFallbackTimer = endTime > currentTimerData.expectedEndTime;
1488
+ if (setupFallbackTimer) {
1489
+ $timeout.cancel(currentTimerData.timer);
1490
+ } else {
1491
+ animationsData.push(close);
1492
+ }
1493
+ }
1243
1494
 
1244
- applyAnimationToStyles(element, options);
1245
- }
1495
+ if (setupFallbackTimer) {
1496
+ var timer = $timeout(onAnimationExpired, timerTime, false);
1497
+ animationsData[0] = {
1498
+ timer: timer,
1499
+ expectedEndTime: endTime
1500
+ };
1501
+ animationsData.push(close);
1502
+ element.data(ANIMATE_TIMER_KEY, animationsData);
1503
+ }
1246
1504
 
1247
- function onAnimationExpired() {
1248
- // although an expired animation is a failed animation, getting to
1249
- // this outcome is very easy if the CSS code screws up. Therefore we
1250
- // should still continue normally as if the animation completed correctly.
1251
- close();
1505
+ if (events.length) {
1506
+ element.on(events.join(' '), onAnimationProgress);
1507
+ }
1508
+
1509
+ if (options.to) {
1510
+ if (options.cleanupStyles) {
1511
+ registerRestorableStyles(restoreStyles, node, Object.keys(options.to));
1512
+ }
1513
+ applyAnimationToStyles(element, options);
1514
+ }
1252
1515
  }
1253
1516
 
1254
- function onAnimationProgress(event) {
1255
- event.stopPropagation();
1256
- var ev = event.originalEvent || event;
1257
- var timeStamp = ev.$manualTimeStamp || ev.timeStamp || Date.now();
1258
-
1259
- /* Firefox (or possibly just Gecko) likes to not round values up
1260
- * when a ms measurement is used for the animation */
1261
- var elapsedTime = parseFloat(ev.elapsedTime.toFixed(ELAPSED_TIME_MAX_DECIMAL_PLACES));
1262
-
1263
- /* $manualTimeStamp is a mocked timeStamp value which is set
1264
- * within browserTrigger(). This is only here so that tests can
1265
- * mock animations properly. Real events fallback to event.timeStamp,
1266
- * or, if they don't, then a timeStamp is automatically created for them.
1267
- * We're checking to see if the timeStamp surpasses the expected delay,
1268
- * but we're using elapsedTime instead of the timeStamp on the 2nd
1269
- * pre-condition since animations sometimes close off early */
1270
- if (Math.max(timeStamp - startTime, 0) >= maxDelayTime && elapsedTime >= maxDuration) {
1271
- // we set this flag to ensure that if the transition is paused then, when resumed,
1272
- // the animation will automatically close itself since transitions cannot be paused.
1273
- animationCompleted = true;
1274
- close();
1517
+ function onAnimationExpired() {
1518
+ var animationsData = element.data(ANIMATE_TIMER_KEY);
1519
+
1520
+ // this will be false in the event that the element was
1521
+ // removed from the DOM (via a leave animation or something
1522
+ // similar)
1523
+ if (animationsData) {
1524
+ for (var i = 1; i < animationsData.length; i++) {
1525
+ animationsData[i]();
1526
+ }
1527
+ element.removeData(ANIMATE_TIMER_KEY);
1275
1528
  }
1276
1529
  }
1277
1530
  }
1278
- }
1531
+ };
1279
1532
  }];
1280
1533
  }];
1281
1534
 
@@ -1288,16 +1541,27 @@ var $$AnimateCssDriverProvider = ['$$animationProvider', function($$animationPro
1288
1541
  var NG_OUT_ANCHOR_CLASS_NAME = 'ng-anchor-out';
1289
1542
  var NG_IN_ANCHOR_CLASS_NAME = 'ng-anchor-in';
1290
1543
 
1291
- this.$get = ['$animateCss', '$rootScope', '$$AnimateRunner', '$rootElement', '$document', '$sniffer',
1292
- function($animateCss, $rootScope, $$AnimateRunner, $rootElement, $document, $sniffer) {
1544
+ function isDocumentFragment(node) {
1545
+ return node.parentNode && node.parentNode.nodeType === 11;
1546
+ }
1547
+
1548
+ this.$get = ['$animateCss', '$rootScope', '$$AnimateRunner', '$rootElement', '$sniffer', '$$jqLite', '$document',
1549
+ function($animateCss, $rootScope, $$AnimateRunner, $rootElement, $sniffer, $$jqLite, $document) {
1293
1550
 
1294
1551
  // only browsers that support these properties can render animations
1295
1552
  if (!$sniffer.animations && !$sniffer.transitions) return noop;
1296
1553
 
1297
- var bodyNode = getDomNode($document).body;
1554
+ var bodyNode = $document[0].body;
1298
1555
  var rootNode = getDomNode($rootElement);
1299
1556
 
1300
- var rootBodyElement = jqLite(bodyNode.parentNode === rootNode ? bodyNode : rootNode);
1557
+ var rootBodyElement = jqLite(
1558
+ // this is to avoid using something that exists outside of the body
1559
+ // we also special case the doc fragment case because our unit test code
1560
+ // appends the $rootElement to the body after the app has been bootstrapped
1561
+ isDocumentFragment(rootNode) || bodyNode.contains(rootNode) ? rootNode : bodyNode
1562
+ );
1563
+
1564
+ var applyAnimationClasses = applyAnimationClassesFactory($$jqLite);
1301
1565
 
1302
1566
  return function initDriverFn(animationDetails) {
1303
1567
  return animationDetails.from && animationDetails.to
@@ -1392,7 +1656,7 @@ var $$AnimateCssDriverProvider = ['$$animationProvider', function($$animationPro
1392
1656
  var coords = getDomNode(anchor).getBoundingClientRect();
1393
1657
 
1394
1658
  // we iterate directly since safari messes up and doesn't return
1395
- // all the keys for the coods object when iterated
1659
+ // all the keys for the coords object when iterated
1396
1660
  forEach(['width','height','top','left'], function(key) {
1397
1661
  var value = coords[key];
1398
1662
  switch (key) {
@@ -1449,8 +1713,8 @@ var $$AnimateCssDriverProvider = ['$$animationProvider', function($$animationPro
1449
1713
  }
1450
1714
 
1451
1715
  function prepareFromToAnchorAnimation(from, to, classes, anchors) {
1452
- var fromAnimation = prepareRegularAnimation(from);
1453
- var toAnimation = prepareRegularAnimation(to);
1716
+ var fromAnimation = prepareRegularAnimation(from, noop);
1717
+ var toAnimation = prepareRegularAnimation(to, noop);
1454
1718
 
1455
1719
  var anchorAnimations = [];
1456
1720
  forEach(anchors, function(anchor) {
@@ -1506,19 +1770,23 @@ var $$AnimateCssDriverProvider = ['$$animationProvider', function($$animationPro
1506
1770
  var options = animationDetails.options || {};
1507
1771
 
1508
1772
  if (animationDetails.structural) {
1509
- // structural animations ensure that the CSS classes are always applied
1510
- // before the detection starts.
1511
- options.structural = options.applyClassesEarly = true;
1773
+ options.event = animationDetails.event;
1774
+ options.structural = true;
1775
+ options.applyClassesEarly = true;
1512
1776
 
1513
1777
  // we special case the leave animation since we want to ensure that
1514
1778
  // the element is removed as soon as the animation is over. Otherwise
1515
1779
  // a flicker might appear or the element may not be removed at all
1516
- options.event = animationDetails.event;
1517
- if (options.event === 'leave') {
1780
+ if (animationDetails.event === 'leave') {
1518
1781
  options.onDone = options.domOperation;
1519
1782
  }
1520
- } else {
1521
- options.event = null;
1783
+ }
1784
+
1785
+ // We assign the preparationClasses as the actual animation event since
1786
+ // the internals of $animateCss will just suffix the event token values
1787
+ // with `-active` to trigger the animation.
1788
+ if (options.preparationClasses) {
1789
+ options.event = concatWithSpace(options.event, options.preparationClasses);
1522
1790
  }
1523
1791
 
1524
1792
  var animator = $animateCss(element, options);
@@ -1537,12 +1805,14 @@ var $$AnimateCssDriverProvider = ['$$animationProvider', function($$animationPro
1537
1805
  // by the time...
1538
1806
 
1539
1807
  var $$AnimateJsProvider = ['$animateProvider', function($animateProvider) {
1540
- this.$get = ['$injector', '$$AnimateRunner', '$$rAFMutex', '$$jqLite',
1541
- function($injector, $$AnimateRunner, $$rAFMutex, $$jqLite) {
1808
+ this.$get = ['$injector', '$$AnimateRunner', '$$jqLite',
1809
+ function($injector, $$AnimateRunner, $$jqLite) {
1542
1810
 
1543
1811
  var applyAnimationClasses = applyAnimationClassesFactory($$jqLite);
1544
1812
  // $animateJs(element, 'enter');
1545
1813
  return function(element, event, classes, options) {
1814
+ var animationClosed = false;
1815
+
1546
1816
  // the `classes` argument is optional and if it is not used
1547
1817
  // then the classes will be resolved from the element's className
1548
1818
  // property as well as options.addClass/options.removeClass.
@@ -1595,8 +1865,32 @@ var $$AnimateJsProvider = ['$animateProvider', function($animateProvider) {
1595
1865
  applyAnimationClasses(element, options);
1596
1866
  }
1597
1867
 
1868
+ function close() {
1869
+ animationClosed = true;
1870
+ applyOptions();
1871
+ applyAnimationStyles(element, options);
1872
+ }
1873
+
1874
+ var runner;
1875
+
1598
1876
  return {
1877
+ $$willAnimate: true,
1878
+ end: function() {
1879
+ if (runner) {
1880
+ runner.end();
1881
+ } else {
1882
+ close();
1883
+ runner = new $$AnimateRunner();
1884
+ runner.complete(true);
1885
+ }
1886
+ return runner;
1887
+ },
1599
1888
  start: function() {
1889
+ if (runner) {
1890
+ return runner;
1891
+ }
1892
+
1893
+ runner = new $$AnimateRunner();
1600
1894
  var closeActiveAnimations;
1601
1895
  var chain = [];
1602
1896
 
@@ -1621,8 +1915,7 @@ var $$AnimateJsProvider = ['$animateProvider', function($animateProvider) {
1621
1915
  });
1622
1916
  }
1623
1917
 
1624
- var animationClosed = false;
1625
- var runner = new $$AnimateRunner({
1918
+ runner.setHost({
1626
1919
  end: function() {
1627
1920
  endAnimations();
1628
1921
  },
@@ -1635,9 +1928,7 @@ var $$AnimateJsProvider = ['$animateProvider', function($animateProvider) {
1635
1928
  return runner;
1636
1929
 
1637
1930
  function onComplete(success) {
1638
- animationClosed = true;
1639
- applyOptions();
1640
- applyAnimationStyles(element, options);
1931
+ close(success);
1641
1932
  runner.complete(success);
1642
1933
  }
1643
1934
 
@@ -1857,6 +2148,7 @@ var NG_ANIMATE_PIN_DATA = '$ngAnimatePin';
1857
2148
  var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
1858
2149
  var PRE_DIGEST_STATE = 1;
1859
2150
  var RUNNING_STATE = 2;
2151
+ var ONE_SPACE = ' ';
1860
2152
 
1861
2153
  var rules = this.rules = {
1862
2154
  skip: [],
@@ -1864,28 +2156,50 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
1864
2156
  join: []
1865
2157
  };
1866
2158
 
2159
+ function makeTruthyCssClassMap(classString) {
2160
+ if (!classString) {
2161
+ return null;
2162
+ }
2163
+
2164
+ var keys = classString.split(ONE_SPACE);
2165
+ var map = Object.create(null);
2166
+
2167
+ forEach(keys, function(key) {
2168
+ map[key] = true;
2169
+ });
2170
+ return map;
2171
+ }
2172
+
2173
+ function hasMatchingClasses(newClassString, currentClassString) {
2174
+ if (newClassString && currentClassString) {
2175
+ var currentClassMap = makeTruthyCssClassMap(currentClassString);
2176
+ return newClassString.split(ONE_SPACE).some(function(className) {
2177
+ return currentClassMap[className];
2178
+ });
2179
+ }
2180
+ }
2181
+
1867
2182
  function isAllowed(ruleType, element, currentAnimation, previousAnimation) {
1868
2183
  return rules[ruleType].some(function(fn) {
1869
2184
  return fn(element, currentAnimation, previousAnimation);
1870
2185
  });
1871
2186
  }
1872
2187
 
1873
- function hasAnimationClasses(options, and) {
1874
- options = options || {};
1875
- var a = (options.addClass || '').length > 0;
1876
- var b = (options.removeClass || '').length > 0;
2188
+ function hasAnimationClasses(animation, and) {
2189
+ var a = (animation.addClass || '').length > 0;
2190
+ var b = (animation.removeClass || '').length > 0;
1877
2191
  return and ? a && b : a || b;
1878
2192
  }
1879
2193
 
1880
2194
  rules.join.push(function(element, newAnimation, currentAnimation) {
1881
2195
  // if the new animation is class-based then we can just tack that on
1882
- return !newAnimation.structural && hasAnimationClasses(newAnimation.options);
2196
+ return !newAnimation.structural && hasAnimationClasses(newAnimation);
1883
2197
  });
1884
2198
 
1885
2199
  rules.skip.push(function(element, newAnimation, currentAnimation) {
1886
2200
  // there is no need to animate anything if no classes are being added and
1887
2201
  // there is no structural animation that will be triggered
1888
- return !newAnimation.structural && !hasAnimationClasses(newAnimation.options);
2202
+ return !newAnimation.structural && !hasAnimationClasses(newAnimation);
1889
2203
  });
1890
2204
 
1891
2205
  rules.skip.push(function(element, newAnimation, currentAnimation) {
@@ -1895,8 +2209,8 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
1895
2209
  });
1896
2210
 
1897
2211
  rules.skip.push(function(element, newAnimation, currentAnimation) {
1898
- // if there is a current animation then skip the class-based animation
1899
- return currentAnimation.structural && !newAnimation.structural;
2212
+ // if there is an ongoing current animation then don't even bother running the class-based animation
2213
+ return currentAnimation.structural && currentAnimation.state === RUNNING_STATE && !newAnimation.structural;
1900
2214
  });
1901
2215
 
1902
2216
  rules.cancel.push(function(element, newAnimation, currentAnimation) {
@@ -1911,23 +2225,51 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
1911
2225
  });
1912
2226
 
1913
2227
  rules.cancel.push(function(element, newAnimation, currentAnimation) {
1914
- var nO = newAnimation.options;
1915
- var cO = currentAnimation.options;
2228
+ // cancel the animation if classes added / removed in both animation cancel each other out,
2229
+ // but only if the current animation isn't structural
2230
+
2231
+ if (currentAnimation.structural) return false;
1916
2232
 
1917
- // if the exact same CSS class is added/removed then it's safe to cancel it
1918
- return (nO.addClass && nO.addClass === cO.removeClass) || (nO.removeClass && nO.removeClass === cO.addClass);
2233
+ var nA = newAnimation.addClass;
2234
+ var nR = newAnimation.removeClass;
2235
+ var cA = currentAnimation.addClass;
2236
+ var cR = currentAnimation.removeClass;
2237
+
2238
+ // early detection to save the global CPU shortage :)
2239
+ if ((isUndefined(nA) && isUndefined(nR)) || (isUndefined(cA) && isUndefined(cR))) {
2240
+ return false;
2241
+ }
2242
+
2243
+ return hasMatchingClasses(nA, cR) || hasMatchingClasses(nR, cA);
1919
2244
  });
1920
2245
 
1921
2246
  this.$get = ['$$rAF', '$rootScope', '$rootElement', '$document', '$$HashMap',
1922
- '$$animation', '$$AnimateRunner', '$templateRequest', '$$jqLite',
2247
+ '$$animation', '$$AnimateRunner', '$templateRequest', '$$jqLite', '$$forceReflow',
1923
2248
  function($$rAF, $rootScope, $rootElement, $document, $$HashMap,
1924
- $$animation, $$AnimateRunner, $templateRequest, $$jqLite) {
2249
+ $$animation, $$AnimateRunner, $templateRequest, $$jqLite, $$forceReflow) {
1925
2250
 
1926
2251
  var activeAnimationsLookup = new $$HashMap();
1927
2252
  var disabledElementsLookup = new $$HashMap();
1928
-
1929
2253
  var animationsEnabled = null;
1930
2254
 
2255
+ function postDigestTaskFactory() {
2256
+ var postDigestCalled = false;
2257
+ return function(fn) {
2258
+ // we only issue a call to postDigest before
2259
+ // it has first passed. This prevents any callbacks
2260
+ // from not firing once the animation has completed
2261
+ // since it will be out of the digest cycle.
2262
+ if (postDigestCalled) {
2263
+ fn();
2264
+ } else {
2265
+ $rootScope.$$postDigest(function() {
2266
+ postDigestCalled = true;
2267
+ fn();
2268
+ });
2269
+ }
2270
+ };
2271
+ }
2272
+
1931
2273
  // Wait until all directive and route-related templates are downloaded and
1932
2274
  // compiled. The $templateRequest.totalPendingRequests variable keeps track of
1933
2275
  // all of the remote templates being currently downloaded. If there are no
@@ -1957,8 +2299,6 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
1957
2299
  }
1958
2300
  );
1959
2301
 
1960
- var bodyElement = jqLite($document[0].body);
1961
-
1962
2302
  var callbackRegistry = {};
1963
2303
 
1964
2304
  // remember that the classNameFilter is set during the provider/config
@@ -1972,18 +2312,28 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
1972
2312
 
1973
2313
  var applyAnimationClasses = applyAnimationClassesFactory($$jqLite);
1974
2314
 
1975
- function normalizeAnimationOptions(element, options) {
1976
- return mergeAnimationOptions(element, options, {});
2315
+ function normalizeAnimationDetails(element, animation) {
2316
+ return mergeAnimationDetails(element, animation, {});
1977
2317
  }
1978
2318
 
1979
- function findCallbacks(element, event) {
2319
+ // IE9-11 has no method "contains" in SVG element and in Node.prototype. Bug #10259.
2320
+ var contains = Node.prototype.contains || function(arg) {
2321
+ // jshint bitwise: false
2322
+ return this === arg || !!(this.compareDocumentPosition(arg) & 16);
2323
+ // jshint bitwise: true
2324
+ };
2325
+
2326
+ function findCallbacks(parent, element, event) {
1980
2327
  var targetNode = getDomNode(element);
2328
+ var targetParentNode = getDomNode(parent);
1981
2329
 
1982
2330
  var matches = [];
1983
2331
  var entries = callbackRegistry[event];
1984
2332
  if (entries) {
1985
2333
  forEach(entries, function(entry) {
1986
- if (entry.node.contains(targetNode)) {
2334
+ if (contains.call(entry.node, targetNode)) {
2335
+ matches.push(entry.callback);
2336
+ } else if (event === 'leave' && contains.call(entry.node, targetParentNode)) {
1987
2337
  matches.push(entry.callback);
1988
2338
  }
1989
2339
  });
@@ -1992,15 +2342,7 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
1992
2342
  return matches;
1993
2343
  }
1994
2344
 
1995
- function triggerCallback(event, element, phase, data) {
1996
- $$rAF(function() {
1997
- forEach(findCallbacks(element, event), function(callback) {
1998
- callback(element, phase, data);
1999
- });
2000
- });
2001
- }
2002
-
2003
- return {
2345
+ var $animate = {
2004
2346
  on: function(event, container, callback) {
2005
2347
  var node = extractElementNode(container);
2006
2348
  callbackRegistry[event] = callbackRegistry[event] || [];
@@ -2008,6 +2350,11 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
2008
2350
  node: node,
2009
2351
  callback: callback
2010
2352
  });
2353
+
2354
+ // Remove the callback when the element is removed from the DOM
2355
+ jqLite(container).on('$destroy', function() {
2356
+ $animate.off(event, container, callback);
2357
+ });
2011
2358
  },
2012
2359
 
2013
2360
  off: function(event, container, callback) {
@@ -2066,12 +2413,7 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
2066
2413
  bool = !recordExists;
2067
2414
  } else {
2068
2415
  // (element, bool) - Element setter
2069
- bool = !!bool;
2070
- if (!bool) {
2071
- disabledElementsLookup.put(node, true);
2072
- } else if (recordExists) {
2073
- disabledElementsLookup.remove(node);
2074
- }
2416
+ disabledElementsLookup.put(node, !bool);
2075
2417
  }
2076
2418
  }
2077
2419
  }
@@ -2080,7 +2422,14 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
2080
2422
  }
2081
2423
  };
2082
2424
 
2083
- function queueAnimation(element, event, options) {
2425
+ return $animate;
2426
+
2427
+ function queueAnimation(element, event, initialOptions) {
2428
+ // we always make a copy of the options since
2429
+ // there should never be any side effects on
2430
+ // the input data when running `$animateCss`.
2431
+ var options = copy(initialOptions);
2432
+
2084
2433
  var node, parent;
2085
2434
  element = stripCommentsFromElement(element);
2086
2435
  if (element) {
@@ -2094,22 +2443,25 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
2094
2443
  // These methods will become available after the digest has passed
2095
2444
  var runner = new $$AnimateRunner();
2096
2445
 
2097
- // there are situations where a directive issues an animation for
2098
- // a jqLite wrapper that contains only comment nodes... If this
2099
- // happens then there is no way we can perform an animation
2100
- if (!node) {
2101
- close();
2102
- return runner;
2103
- }
2446
+ // this is used to trigger callbacks in postDigest mode
2447
+ var runInNextPostDigestOrNow = postDigestTaskFactory();
2104
2448
 
2105
2449
  if (isArray(options.addClass)) {
2106
2450
  options.addClass = options.addClass.join(' ');
2107
2451
  }
2108
2452
 
2453
+ if (options.addClass && !isString(options.addClass)) {
2454
+ options.addClass = null;
2455
+ }
2456
+
2109
2457
  if (isArray(options.removeClass)) {
2110
2458
  options.removeClass = options.removeClass.join(' ');
2111
2459
  }
2112
2460
 
2461
+ if (options.removeClass && !isString(options.removeClass)) {
2462
+ options.removeClass = null;
2463
+ }
2464
+
2113
2465
  if (options.from && !isObject(options.from)) {
2114
2466
  options.from = null;
2115
2467
  }
@@ -2118,6 +2470,14 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
2118
2470
  options.to = null;
2119
2471
  }
2120
2472
 
2473
+ // there are situations where a directive issues an animation for
2474
+ // a jqLite wrapper that contains only comment nodes... If this
2475
+ // happens then there is no way we can perform an animation
2476
+ if (!node) {
2477
+ close();
2478
+ return runner;
2479
+ }
2480
+
2121
2481
  var className = [node.className, options.addClass, options.removeClass].join(' ');
2122
2482
  if (!isAnimatableClassName(className)) {
2123
2483
  close();
@@ -2129,7 +2489,9 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
2129
2489
  // this is a hard disable of all animations for the application or on
2130
2490
  // the element itself, therefore there is no need to continue further
2131
2491
  // past this point if not enabled
2132
- var skipAnimations = !animationsEnabled || disabledElementsLookup.get(node);
2492
+ // Animations are also disabled if the document is currently hidden (page is not visible
2493
+ // to the user), because browsers slow down or do not flush calls to requestAnimationFrame
2494
+ var skipAnimations = !animationsEnabled || $document[0].hidden || disabledElementsLookup.get(node);
2133
2495
  var existingAnimation = (!skipAnimations && activeAnimationsLookup.get(node)) || {};
2134
2496
  var hasExistingAnimation = !!existingAnimation.state;
2135
2497
 
@@ -2152,6 +2514,8 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
2152
2514
  structural: isStructural,
2153
2515
  element: element,
2154
2516
  event: event,
2517
+ addClass: options.addClass,
2518
+ removeClass: options.removeClass,
2155
2519
  close: close,
2156
2520
  options: options,
2157
2521
  runner: runner
@@ -2164,11 +2528,10 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
2164
2528
  close();
2165
2529
  return runner;
2166
2530
  } else {
2167
- mergeAnimationOptions(element, existingAnimation.options, options);
2531
+ mergeAnimationDetails(element, existingAnimation, newAnimation);
2168
2532
  return existingAnimation.runner;
2169
2533
  }
2170
2534
  }
2171
-
2172
2535
  var cancelAnimationFlag = isAllowed('cancel', element, newAnimation, existingAnimation);
2173
2536
  if (cancelAnimationFlag) {
2174
2537
  if (existingAnimation.state === RUNNING_STATE) {
@@ -2182,8 +2545,10 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
2182
2545
  // method which will call the runner methods in async.
2183
2546
  existingAnimation.close();
2184
2547
  } else {
2185
- // this will merge the existing animation options into this new follow-up animation
2186
- mergeAnimationOptions(element, newAnimation.options, existingAnimation.options);
2548
+ // this will merge the new animation options into existing animation options
2549
+ mergeAnimationDetails(element, existingAnimation, newAnimation);
2550
+
2551
+ return existingAnimation.runner;
2187
2552
  }
2188
2553
  } else {
2189
2554
  // a joined animation means that this animation will take over the existing one
@@ -2192,18 +2557,23 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
2192
2557
  var joinAnimationFlag = isAllowed('join', element, newAnimation, existingAnimation);
2193
2558
  if (joinAnimationFlag) {
2194
2559
  if (existingAnimation.state === RUNNING_STATE) {
2195
- normalizeAnimationOptions(element, options);
2560
+ normalizeAnimationDetails(element, newAnimation);
2196
2561
  } else {
2562
+ applyGeneratedPreparationClasses(element, isStructural ? event : null, options);
2563
+
2197
2564
  event = newAnimation.event = existingAnimation.event;
2198
- options = mergeAnimationOptions(element, existingAnimation.options, newAnimation.options);
2199
- return runner;
2565
+ options = mergeAnimationDetails(element, existingAnimation, newAnimation);
2566
+
2567
+ //we return the same runner since only the option values of this animation will
2568
+ //be fed into the `existingAnimation`.
2569
+ return existingAnimation.runner;
2200
2570
  }
2201
2571
  }
2202
2572
  }
2203
2573
  } else {
2204
2574
  // normalization in this case means that it removes redundant CSS classes that
2205
2575
  // already exist (addClass) or do not exist (removeClass) on the element
2206
- normalizeAnimationOptions(element, options);
2576
+ normalizeAnimationDetails(element, newAnimation);
2207
2577
  }
2208
2578
 
2209
2579
  // when the options are merged and cleaned up we may end up not having to do
@@ -2213,7 +2583,7 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
2213
2583
  if (!isValidAnimation) {
2214
2584
  // animate (from/to) can be quickly checked first, otherwise we check if any classes are present
2215
2585
  isValidAnimation = (newAnimation.event === 'animate' && Object.keys(newAnimation.options.to || {}).length > 0)
2216
- || hasAnimationClasses(newAnimation.options);
2586
+ || hasAnimationClasses(newAnimation);
2217
2587
  }
2218
2588
 
2219
2589
  if (!isValidAnimation) {
@@ -2222,10 +2592,6 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
2222
2592
  return runner;
2223
2593
  }
2224
2594
 
2225
- if (isStructural) {
2226
- closeParentClassBasedAnimations(parent);
2227
- }
2228
-
2229
2595
  // the counter keeps track of cancelled animations
2230
2596
  var counter = (existingAnimation.counter || 0) + 1;
2231
2597
  newAnimation.counter = counter;
@@ -2247,7 +2613,7 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
2247
2613
  var isValidAnimation = parentElement.length > 0
2248
2614
  && (animationDetails.event === 'animate'
2249
2615
  || animationDetails.structural
2250
- || hasAnimationClasses(animationDetails.options));
2616
+ || hasAnimationClasses(animationDetails));
2251
2617
 
2252
2618
  // this means that the previous animation was cancelled
2253
2619
  // even if the follow-up animation is the same event
@@ -2279,16 +2645,13 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
2279
2645
 
2280
2646
  // this combined multiple class to addClass / removeClass into a setClass event
2281
2647
  // so long as a structural event did not take over the animation
2282
- event = !animationDetails.structural && hasAnimationClasses(animationDetails.options, true)
2648
+ event = !animationDetails.structural && hasAnimationClasses(animationDetails, true)
2283
2649
  ? 'setClass'
2284
2650
  : animationDetails.event;
2285
2651
 
2286
- if (animationDetails.structural) {
2287
- closeParentClassBasedAnimations(parentElement);
2288
- }
2289
-
2290
2652
  markElementAnimationState(element, RUNNING_STATE);
2291
2653
  var realRunner = $$animation(element, event, animationDetails.options);
2654
+
2292
2655
  realRunner.done(function(status) {
2293
2656
  close(!status);
2294
2657
  var animationDetails = activeAnimationsLookup.get(node);
@@ -2307,11 +2670,25 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
2307
2670
  return runner;
2308
2671
 
2309
2672
  function notifyProgress(runner, event, phase, data) {
2310
- triggerCallback(event, element, phase, data);
2673
+ runInNextPostDigestOrNow(function() {
2674
+ var callbacks = findCallbacks(parent, element, event);
2675
+ if (callbacks.length) {
2676
+ // do not optimize this call here to RAF because
2677
+ // we don't know how heavy the callback code here will
2678
+ // be and if this code is buffered then this can
2679
+ // lead to a performance regression.
2680
+ $$rAF(function() {
2681
+ forEach(callbacks, function(callback) {
2682
+ callback(element, phase, data);
2683
+ });
2684
+ });
2685
+ }
2686
+ });
2311
2687
  runner.progress(event, phase, data);
2312
2688
  }
2313
2689
 
2314
2690
  function close(reject) { // jshint ignore:line
2691
+ clearGeneratedClasses(element, options);
2315
2692
  applyAnimationClasses(element, options);
2316
2693
  applyAnimationStyles(element, options);
2317
2694
  options.domOperation();
@@ -2325,15 +2702,15 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
2325
2702
  forEach(children, function(child) {
2326
2703
  var state = parseInt(child.getAttribute(NG_ANIMATE_ATTR_NAME));
2327
2704
  var animationDetails = activeAnimationsLookup.get(child);
2328
- switch (state) {
2329
- case RUNNING_STATE:
2330
- animationDetails.runner.end();
2331
- /* falls through */
2332
- case PRE_DIGEST_STATE:
2333
- if (animationDetails) {
2705
+ if (animationDetails) {
2706
+ switch (state) {
2707
+ case RUNNING_STATE:
2708
+ animationDetails.runner.end();
2709
+ /* falls through */
2710
+ case PRE_DIGEST_STATE:
2334
2711
  activeAnimationsLookup.remove(child);
2335
- }
2336
- break;
2712
+ break;
2713
+ }
2337
2714
  }
2338
2715
  });
2339
2716
  }
@@ -2348,67 +2725,61 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
2348
2725
  return getDomNode(nodeOrElmA) === getDomNode(nodeOrElmB);
2349
2726
  }
2350
2727
 
2351
- function closeParentClassBasedAnimations(startingElement) {
2352
- var parentNode = getDomNode(startingElement);
2353
- do {
2354
- if (!parentNode || parentNode.nodeType !== ELEMENT_NODE) break;
2355
-
2356
- var animationDetails = activeAnimationsLookup.get(parentNode);
2357
- if (animationDetails) {
2358
- examineParentAnimation(parentNode, animationDetails);
2359
- }
2360
-
2361
- parentNode = parentNode.parentNode;
2362
- } while (true);
2363
-
2364
- // since animations are detected from CSS classes, we need to flush all parent
2365
- // class-based animations so that the parent classes are all present for child
2366
- // animations to properly function (otherwise any CSS selectors may not work)
2367
- function examineParentAnimation(node, animationDetails) {
2368
- // enter/leave/move always have priority
2369
- if (animationDetails.structural || !hasAnimationClasses(animationDetails.options)) return;
2370
-
2371
- if (animationDetails.state === RUNNING_STATE) {
2372
- animationDetails.runner.end();
2373
- }
2374
- clearElementAnimationState(node);
2375
- }
2376
- }
2377
-
2728
+ /**
2729
+ * This fn returns false if any of the following is true:
2730
+ * a) animations on any parent element are disabled, and animations on the element aren't explicitly allowed
2731
+ * b) a parent element has an ongoing structural animation, and animateChildren is false
2732
+ * c) the element is not a child of the body
2733
+ * d) the element is not a child of the $rootElement
2734
+ */
2378
2735
  function areAnimationsAllowed(element, parentElement, event) {
2379
- var bodyElementDetected = false;
2380
- var rootElementDetected = false;
2736
+ var bodyElement = jqLite($document[0].body);
2737
+ var bodyElementDetected = isMatchingElement(element, bodyElement) || element[0].nodeName === 'HTML';
2738
+ var rootElementDetected = isMatchingElement(element, $rootElement);
2381
2739
  var parentAnimationDetected = false;
2382
2740
  var animateChildren;
2741
+ var elementDisabled = disabledElementsLookup.get(getDomNode(element));
2383
2742
 
2384
- var parentHost = element.data(NG_ANIMATE_PIN_DATA);
2743
+ var parentHost = jqLite.data(element[0], NG_ANIMATE_PIN_DATA);
2385
2744
  if (parentHost) {
2386
2745
  parentElement = parentHost;
2387
2746
  }
2388
2747
 
2389
- while (parentElement && parentElement.length) {
2748
+ parentElement = getDomNode(parentElement);
2749
+
2750
+ while (parentElement) {
2390
2751
  if (!rootElementDetected) {
2391
2752
  // angular doesn't want to attempt to animate elements outside of the application
2392
2753
  // therefore we need to ensure that the rootElement is an ancestor of the current element
2393
2754
  rootElementDetected = isMatchingElement(parentElement, $rootElement);
2394
2755
  }
2395
2756
 
2396
- var parentNode = parentElement[0];
2397
- if (parentNode.nodeType !== ELEMENT_NODE) {
2757
+ if (parentElement.nodeType !== ELEMENT_NODE) {
2398
2758
  // no point in inspecting the #document element
2399
2759
  break;
2400
2760
  }
2401
2761
 
2402
- var details = activeAnimationsLookup.get(parentNode) || {};
2762
+ var details = activeAnimationsLookup.get(parentElement) || {};
2403
2763
  // either an enter, leave or move animation will commence
2404
2764
  // therefore we can't allow any animations to take place
2405
2765
  // but if a parent animation is class-based then that's ok
2406
2766
  if (!parentAnimationDetected) {
2407
- parentAnimationDetected = details.structural || disabledElementsLookup.get(parentNode);
2767
+ var parentElementDisabled = disabledElementsLookup.get(parentElement);
2768
+
2769
+ if (parentElementDisabled === true && elementDisabled !== false) {
2770
+ // disable animations if the user hasn't explicitly enabled animations on the
2771
+ // current element
2772
+ elementDisabled = true;
2773
+ // element is disabled via parent element, no need to check anything else
2774
+ break;
2775
+ } else if (parentElementDisabled === false) {
2776
+ elementDisabled = false;
2777
+ }
2778
+ parentAnimationDetected = details.structural;
2408
2779
  }
2409
2780
 
2410
2781
  if (isUndefined(animateChildren) || animateChildren === true) {
2411
- var value = parentElement.data(NG_ANIMATE_CHILDREN_DATA);
2782
+ var value = jqLite.data(parentElement, NG_ANIMATE_CHILDREN_DATA);
2412
2783
  if (isDefined(value)) {
2413
2784
  animateChildren = value;
2414
2785
  }
@@ -2417,28 +2788,32 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
2417
2788
  // there is no need to continue traversing at this point
2418
2789
  if (parentAnimationDetected && animateChildren === false) break;
2419
2790
 
2420
- if (!rootElementDetected) {
2421
- // angular doesn't want to attempt to animate elements outside of the application
2422
- // therefore we need to ensure that the rootElement is an ancestor of the current element
2423
- rootElementDetected = isMatchingElement(parentElement, $rootElement);
2424
- if (!rootElementDetected) {
2425
- parentHost = parentElement.data(NG_ANIMATE_PIN_DATA);
2426
- if (parentHost) {
2427
- parentElement = parentHost;
2428
- }
2429
- }
2430
- }
2431
-
2432
2791
  if (!bodyElementDetected) {
2433
- // we also need to ensure that the element is or will be apart of the body element
2792
+ // we also need to ensure that the element is or will be a part of the body element
2434
2793
  // otherwise it is pointless to even issue an animation to be rendered
2435
2794
  bodyElementDetected = isMatchingElement(parentElement, bodyElement);
2436
2795
  }
2437
2796
 
2438
- parentElement = parentElement.parent();
2797
+ if (bodyElementDetected && rootElementDetected) {
2798
+ // If both body and root have been found, any other checks are pointless,
2799
+ // as no animation data should live outside the application
2800
+ break;
2801
+ }
2802
+
2803
+ if (!rootElementDetected) {
2804
+ // If no rootElement is detected, check if the parentElement is pinned to another element
2805
+ parentHost = jqLite.data(parentElement, NG_ANIMATE_PIN_DATA);
2806
+ if (parentHost) {
2807
+ // The pin target element becomes the next parent element
2808
+ parentElement = getDomNode(parentHost);
2809
+ continue;
2810
+ }
2811
+ }
2812
+
2813
+ parentElement = parentElement.parentNode;
2439
2814
  }
2440
2815
 
2441
- var allowAnimation = !parentAnimationDetected || animateChildren;
2816
+ var allowAnimation = (!parentAnimationDetected || animateChildren) && elementDisabled !== true;
2442
2817
  return allowAnimation && rootElementDetected && bodyElementDetected;
2443
2818
  }
2444
2819
 
@@ -2458,184 +2833,112 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
2458
2833
  }];
2459
2834
  }];
2460
2835
 
2461
- var $$rAFMutexFactory = ['$$rAF', function($$rAF) {
2462
- return function() {
2463
- var passed = false;
2464
- $$rAF(function() {
2465
- passed = true;
2466
- });
2467
- return function(fn) {
2468
- passed ? fn() : $$rAF(fn);
2469
- };
2470
- };
2471
- }];
2472
-
2473
- var $$AnimateRunnerFactory = ['$q', '$$rAFMutex', function($q, $$rAFMutex) {
2474
- var INITIAL_STATE = 0;
2475
- var DONE_PENDING_STATE = 1;
2476
- var DONE_COMPLETE_STATE = 2;
2477
-
2478
- AnimateRunner.chain = function(chain, callback) {
2479
- var index = 0;
2480
-
2481
- next();
2482
- function next() {
2483
- if (index === chain.length) {
2484
- callback(true);
2485
- return;
2486
- }
2487
-
2488
- chain[index](function(response) {
2489
- if (response === false) {
2490
- callback(false);
2491
- return;
2492
- }
2493
- index++;
2494
- next();
2495
- });
2496
- }
2497
- };
2836
+ var $$AnimationProvider = ['$animateProvider', function($animateProvider) {
2837
+ var NG_ANIMATE_REF_ATTR = 'ng-animate-ref';
2498
2838
 
2499
- AnimateRunner.all = function(runners, callback) {
2500
- var count = 0;
2501
- var status = true;
2502
- forEach(runners, function(runner) {
2503
- runner.done(onProgress);
2504
- });
2839
+ var drivers = this.drivers = [];
2505
2840
 
2506
- function onProgress(response) {
2507
- status = status && response;
2508
- if (++count === runners.length) {
2509
- callback(status);
2510
- }
2511
- }
2512
- };
2841
+ var RUNNER_STORAGE_KEY = '$$animationRunner';
2513
2842
 
2514
- function AnimateRunner(host) {
2515
- this.setHost(host);
2843
+ function setRunner(element, runner) {
2844
+ element.data(RUNNER_STORAGE_KEY, runner);
2845
+ }
2516
2846
 
2517
- this._doneCallbacks = [];
2518
- this._runInAnimationFrame = $$rAFMutex();
2519
- this._state = 0;
2847
+ function removeRunner(element) {
2848
+ element.removeData(RUNNER_STORAGE_KEY);
2520
2849
  }
2521
2850
 
2522
- AnimateRunner.prototype = {
2523
- setHost: function(host) {
2524
- this.host = host || {};
2525
- },
2851
+ function getRunner(element) {
2852
+ return element.data(RUNNER_STORAGE_KEY);
2853
+ }
2526
2854
 
2527
- done: function(fn) {
2528
- if (this._state === DONE_COMPLETE_STATE) {
2529
- fn();
2530
- } else {
2531
- this._doneCallbacks.push(fn);
2532
- }
2533
- },
2855
+ this.$get = ['$$jqLite', '$rootScope', '$injector', '$$AnimateRunner', '$$HashMap', '$$rAFScheduler',
2856
+ function($$jqLite, $rootScope, $injector, $$AnimateRunner, $$HashMap, $$rAFScheduler) {
2534
2857
 
2535
- progress: noop,
2858
+ var animationQueue = [];
2859
+ var applyAnimationClasses = applyAnimationClassesFactory($$jqLite);
2536
2860
 
2537
- getPromise: function() {
2538
- if (!this.promise) {
2539
- var self = this;
2540
- this.promise = $q(function(resolve, reject) {
2541
- self.done(function(status) {
2542
- status === false ? reject() : resolve();
2543
- });
2861
+ function sortAnimations(animations) {
2862
+ var tree = { children: [] };
2863
+ var i, lookup = new $$HashMap();
2864
+
2865
+ // this is done first beforehand so that the hashmap
2866
+ // is filled with a list of the elements that will be animated
2867
+ for (i = 0; i < animations.length; i++) {
2868
+ var animation = animations[i];
2869
+ lookup.put(animation.domNode, animations[i] = {
2870
+ domNode: animation.domNode,
2871
+ fn: animation.fn,
2872
+ children: []
2544
2873
  });
2545
2874
  }
2546
- return this.promise;
2547
- },
2548
-
2549
- then: function(resolveHandler, rejectHandler) {
2550
- return this.getPromise().then(resolveHandler, rejectHandler);
2551
- },
2552
-
2553
- 'catch': function(handler) {
2554
- return this.getPromise()['catch'](handler);
2555
- },
2556
-
2557
- 'finally': function(handler) {
2558
- return this.getPromise()['finally'](handler);
2559
- },
2560
2875
 
2561
- pause: function() {
2562
- if (this.host.pause) {
2563
- this.host.pause();
2876
+ for (i = 0; i < animations.length; i++) {
2877
+ processNode(animations[i]);
2564
2878
  }
2565
- },
2566
2879
 
2567
- resume: function() {
2568
- if (this.host.resume) {
2569
- this.host.resume();
2570
- }
2571
- },
2880
+ return flatten(tree);
2572
2881
 
2573
- end: function() {
2574
- if (this.host.end) {
2575
- this.host.end();
2576
- }
2577
- this._resolve(true);
2578
- },
2882
+ function processNode(entry) {
2883
+ if (entry.processed) return entry;
2884
+ entry.processed = true;
2579
2885
 
2580
- cancel: function() {
2581
- if (this.host.cancel) {
2582
- this.host.cancel();
2583
- }
2584
- this._resolve(false);
2585
- },
2886
+ var elementNode = entry.domNode;
2887
+ var parentNode = elementNode.parentNode;
2888
+ lookup.put(elementNode, entry);
2586
2889
 
2587
- complete: function(response) {
2588
- var self = this;
2589
- if (self._state === INITIAL_STATE) {
2590
- self._state = DONE_PENDING_STATE;
2591
- self._runInAnimationFrame(function() {
2592
- self._resolve(response);
2593
- });
2594
- }
2595
- },
2890
+ var parentEntry;
2891
+ while (parentNode) {
2892
+ parentEntry = lookup.get(parentNode);
2893
+ if (parentEntry) {
2894
+ if (!parentEntry.processed) {
2895
+ parentEntry = processNode(parentEntry);
2896
+ }
2897
+ break;
2898
+ }
2899
+ parentNode = parentNode.parentNode;
2900
+ }
2596
2901
 
2597
- _resolve: function(response) {
2598
- if (this._state !== DONE_COMPLETE_STATE) {
2599
- forEach(this._doneCallbacks, function(fn) {
2600
- fn(response);
2601
- });
2602
- this._doneCallbacks.length = 0;
2603
- this._state = DONE_COMPLETE_STATE;
2902
+ (parentEntry || tree).children.push(entry);
2903
+ return entry;
2604
2904
  }
2605
- }
2606
- };
2607
-
2608
- return AnimateRunner;
2609
- }];
2610
-
2611
- var $$AnimationProvider = ['$animateProvider', function($animateProvider) {
2612
- var NG_ANIMATE_REF_ATTR = 'ng-animate-ref';
2613
-
2614
- var drivers = this.drivers = [];
2615
-
2616
- var RUNNER_STORAGE_KEY = '$$animationRunner';
2617
-
2618
- function setRunner(element, runner) {
2619
- element.data(RUNNER_STORAGE_KEY, runner);
2620
- }
2621
2905
 
2622
- function removeRunner(element) {
2623
- element.removeData(RUNNER_STORAGE_KEY);
2624
- }
2906
+ function flatten(tree) {
2907
+ var result = [];
2908
+ var queue = [];
2909
+ var i;
2625
2910
 
2626
- function getRunner(element) {
2627
- return element.data(RUNNER_STORAGE_KEY);
2628
- }
2911
+ for (i = 0; i < tree.children.length; i++) {
2912
+ queue.push(tree.children[i]);
2913
+ }
2629
2914
 
2630
- this.$get = ['$$jqLite', '$rootScope', '$injector', '$$AnimateRunner', '$$rAFScheduler',
2631
- function($$jqLite, $rootScope, $injector, $$AnimateRunner, $$rAFScheduler) {
2915
+ var remainingLevelEntries = queue.length;
2916
+ var nextLevelEntries = 0;
2917
+ var row = [];
2918
+
2919
+ for (i = 0; i < queue.length; i++) {
2920
+ var entry = queue[i];
2921
+ if (remainingLevelEntries <= 0) {
2922
+ remainingLevelEntries = nextLevelEntries;
2923
+ nextLevelEntries = 0;
2924
+ result.push(row);
2925
+ row = [];
2926
+ }
2927
+ row.push(entry.fn);
2928
+ entry.children.forEach(function(childEntry) {
2929
+ nextLevelEntries++;
2930
+ queue.push(childEntry);
2931
+ });
2932
+ remainingLevelEntries--;
2933
+ }
2632
2934
 
2633
- var animationQueue = [];
2634
- var applyAnimationClasses = applyAnimationClassesFactory($$jqLite);
2935
+ if (row.length) {
2936
+ result.push(row);
2937
+ }
2635
2938
 
2636
- var totalPendingClassBasedAnimations = 0;
2637
- var totalActiveClassBasedAnimations = 0;
2638
- var classBasedAnimationsQueue = [];
2939
+ return result;
2940
+ }
2941
+ }
2639
2942
 
2640
2943
  // TODO(matsko): document the signature in a better way
2641
2944
  return function(element, event, options) {
@@ -2665,10 +2968,10 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) {
2665
2968
  options.tempClasses = null;
2666
2969
  }
2667
2970
 
2668
- var classBasedIndex;
2669
- if (!isStructural) {
2670
- classBasedIndex = totalPendingClassBasedAnimations;
2671
- totalPendingClassBasedAnimations += 1;
2971
+ var prepareClassName;
2972
+ if (isStructural) {
2973
+ prepareClassName = 'ng-' + event + PREPARE_CLASS_SUFFIX;
2974
+ $$jqLite.addClass(element, prepareClassName);
2672
2975
  }
2673
2976
 
2674
2977
  animationQueue.push({
@@ -2677,7 +2980,6 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) {
2677
2980
  element: element,
2678
2981
  classes: classes,
2679
2982
  event: event,
2680
- classBasedIndex: classBasedIndex,
2681
2983
  structural: isStructural,
2682
2984
  options: options,
2683
2985
  beforeStart: beforeStart,
@@ -2692,10 +2994,6 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) {
2692
2994
  if (animationQueue.length > 1) return runner;
2693
2995
 
2694
2996
  $rootScope.$$postDigest(function() {
2695
- totalActiveClassBasedAnimations = totalPendingClassBasedAnimations;
2696
- totalPendingClassBasedAnimations = 0;
2697
- classBasedAnimationsQueue.length = 0;
2698
-
2699
2997
  var animations = [];
2700
2998
  forEach(animationQueue, function(entry) {
2701
2999
  // the element was destroyed early on which removed the runner
@@ -2703,67 +3001,58 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) {
2703
3001
  // at all and it already has been closed due to destruction.
2704
3002
  if (getRunner(entry.element)) {
2705
3003
  animations.push(entry);
3004
+ } else {
3005
+ entry.close();
2706
3006
  }
2707
3007
  });
2708
3008
 
2709
3009
  // now any future animations will be in another postDigest
2710
3010
  animationQueue.length = 0;
2711
3011
 
2712
- forEach(groupAnimations(animations), function(animationEntry) {
2713
- if (animationEntry.structural) {
2714
- triggerAnimationStart();
2715
- } else {
2716
- classBasedAnimationsQueue.push({
2717
- node: getDomNode(animationEntry.element),
2718
- fn: triggerAnimationStart
2719
- });
2720
-
2721
- if (animationEntry.classBasedIndex === totalActiveClassBasedAnimations - 1) {
2722
- // we need to sort each of the animations in order of parent to child
2723
- // relationships. This ensures that the child classes are applied at the
2724
- // right time.
2725
- classBasedAnimationsQueue = classBasedAnimationsQueue.sort(function(a,b) {
2726
- return b.node.contains(a.node);
2727
- }).map(function(entry) {
2728
- return entry.fn;
2729
- });
2730
-
2731
- $$rAFScheduler(classBasedAnimationsQueue);
2732
- }
2733
- }
2734
-
2735
- function triggerAnimationStart() {
2736
- // it's important that we apply the `ng-animate` CSS class and the
2737
- // temporary classes before we do any driver invoking since these
2738
- // CSS classes may be required for proper CSS detection.
2739
- animationEntry.beforeStart();
2740
-
2741
- var startAnimationFn, closeFn = animationEntry.close;
2742
-
2743
- // in the event that the element was removed before the digest runs or
2744
- // during the RAF sequencing then we should not trigger the animation.
2745
- var targetElement = animationEntry.anchors
2746
- ? (animationEntry.from.element || animationEntry.to.element)
2747
- : animationEntry.element;
2748
-
2749
- if (getRunner(targetElement)) {
2750
- var operation = invokeFirstDriver(animationEntry);
2751
- if (operation) {
2752
- startAnimationFn = operation.start;
3012
+ var groupedAnimations = groupAnimations(animations);
3013
+ var toBeSortedAnimations = [];
3014
+
3015
+ forEach(groupedAnimations, function(animationEntry) {
3016
+ toBeSortedAnimations.push({
3017
+ domNode: getDomNode(animationEntry.from ? animationEntry.from.element : animationEntry.element),
3018
+ fn: function triggerAnimationStart() {
3019
+ // it's important that we apply the `ng-animate` CSS class and the
3020
+ // temporary classes before we do any driver invoking since these
3021
+ // CSS classes may be required for proper CSS detection.
3022
+ animationEntry.beforeStart();
3023
+
3024
+ var startAnimationFn, closeFn = animationEntry.close;
3025
+
3026
+ // in the event that the element was removed before the digest runs or
3027
+ // during the RAF sequencing then we should not trigger the animation.
3028
+ var targetElement = animationEntry.anchors
3029
+ ? (animationEntry.from.element || animationEntry.to.element)
3030
+ : animationEntry.element;
3031
+
3032
+ if (getRunner(targetElement)) {
3033
+ var operation = invokeFirstDriver(animationEntry);
3034
+ if (operation) {
3035
+ startAnimationFn = operation.start;
3036
+ }
2753
3037
  }
2754
- }
2755
3038
 
2756
- if (!startAnimationFn) {
2757
- closeFn();
2758
- } else {
2759
- var animationRunner = startAnimationFn();
2760
- animationRunner.done(function(status) {
2761
- closeFn(!status);
2762
- });
2763
- updateAnimationRunners(animationEntry, animationRunner);
3039
+ if (!startAnimationFn) {
3040
+ closeFn();
3041
+ } else {
3042
+ var animationRunner = startAnimationFn();
3043
+ animationRunner.done(function(status) {
3044
+ closeFn(!status);
3045
+ });
3046
+ updateAnimationRunners(animationEntry, animationRunner);
3047
+ }
2764
3048
  }
2765
- }
3049
+ });
2766
3050
  });
3051
+
3052
+ // we need to sort each of the animations in order of parent to child
3053
+ // relationships. This ensures that the child classes are applied at the
3054
+ // right time.
3055
+ $$rAFScheduler(sortAnimations(toBeSortedAnimations));
2767
3056
  });
2768
3057
 
2769
3058
  return runner;
@@ -2849,7 +3138,7 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) {
2849
3138
  };
2850
3139
 
2851
3140
  // the anchor animations require that the from and to elements both have at least
2852
- // one shared CSS class which effictively marries the two elements together to use
3141
+ // one shared CSS class which effectively marries the two elements together to use
2853
3142
  // the same animation driver and to properly sequence the anchor animation.
2854
3143
  if (group.classes.length) {
2855
3144
  preparedAnimations.push(group);
@@ -2907,6 +3196,10 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) {
2907
3196
  if (tempClasses) {
2908
3197
  $$jqLite.addClass(element, tempClasses);
2909
3198
  }
3199
+ if (prepareClassName) {
3200
+ $$jqLite.removeClass(element, prepareClassName);
3201
+ prepareClassName = null;
3202
+ }
2910
3203
  }
2911
3204
 
2912
3205
  function updateAnimationRunners(animation, newRunner) {
@@ -2948,12 +3241,127 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) {
2948
3241
  }];
2949
3242
  }];
2950
3243
 
3244
+ /**
3245
+ * @ngdoc directive
3246
+ * @name ngAnimateSwap
3247
+ * @restrict A
3248
+ * @scope
3249
+ *
3250
+ * @description
3251
+ *
3252
+ * ngAnimateSwap is a animation-oriented directive that allows for the container to
3253
+ * be removed and entered in whenever the associated expression changes. A
3254
+ * common usecase for this directive is a rotating banner or slider component which
3255
+ * contains one image being present at a time. When the active image changes
3256
+ * then the old image will perform a `leave` animation and the new element
3257
+ * will be inserted via an `enter` animation.
3258
+ *
3259
+ * @animations
3260
+ * | Animation | Occurs |
3261
+ * |----------------------------------|--------------------------------------|
3262
+ * | {@link ng.$animate#enter enter} | when the new element is inserted to the DOM |
3263
+ * | {@link ng.$animate#leave leave} | when the old element is removed from the DOM |
3264
+ *
3265
+ * @example
3266
+ * <example name="ngAnimateSwap-directive" module="ngAnimateSwapExample"
3267
+ * deps="angular-animate.js"
3268
+ * animations="true" fixBase="true">
3269
+ * <file name="index.html">
3270
+ * <div class="container" ng-controller="AppCtrl">
3271
+ * <div ng-animate-swap="number" class="cell swap-animation" ng-class="colorClass(number)">
3272
+ * {{ number }}
3273
+ * </div>
3274
+ * </div>
3275
+ * </file>
3276
+ * <file name="script.js">
3277
+ * angular.module('ngAnimateSwapExample', ['ngAnimate'])
3278
+ * .controller('AppCtrl', ['$scope', '$interval', function($scope, $interval) {
3279
+ * $scope.number = 0;
3280
+ * $interval(function() {
3281
+ * $scope.number++;
3282
+ * }, 1000);
3283
+ *
3284
+ * var colors = ['red','blue','green','yellow','orange'];
3285
+ * $scope.colorClass = function(number) {
3286
+ * return colors[number % colors.length];
3287
+ * };
3288
+ * }]);
3289
+ * </file>
3290
+ * <file name="animations.css">
3291
+ * .container {
3292
+ * height:250px;
3293
+ * width:250px;
3294
+ * position:relative;
3295
+ * overflow:hidden;
3296
+ * border:2px solid black;
3297
+ * }
3298
+ * .container .cell {
3299
+ * font-size:150px;
3300
+ * text-align:center;
3301
+ * line-height:250px;
3302
+ * position:absolute;
3303
+ * top:0;
3304
+ * left:0;
3305
+ * right:0;
3306
+ * border-bottom:2px solid black;
3307
+ * }
3308
+ * .swap-animation.ng-enter, .swap-animation.ng-leave {
3309
+ * transition:0.5s linear all;
3310
+ * }
3311
+ * .swap-animation.ng-enter {
3312
+ * top:-250px;
3313
+ * }
3314
+ * .swap-animation.ng-enter-active {
3315
+ * top:0px;
3316
+ * }
3317
+ * .swap-animation.ng-leave {
3318
+ * top:0px;
3319
+ * }
3320
+ * .swap-animation.ng-leave-active {
3321
+ * top:250px;
3322
+ * }
3323
+ * .red { background:red; }
3324
+ * .green { background:green; }
3325
+ * .blue { background:blue; }
3326
+ * .yellow { background:yellow; }
3327
+ * .orange { background:orange; }
3328
+ * </file>
3329
+ * </example>
3330
+ */
3331
+ var ngAnimateSwapDirective = ['$animate', '$rootScope', function($animate, $rootScope) {
3332
+ return {
3333
+ restrict: 'A',
3334
+ transclude: 'element',
3335
+ terminal: true,
3336
+ priority: 600, // we use 600 here to ensure that the directive is caught before others
3337
+ link: function(scope, $element, attrs, ctrl, $transclude) {
3338
+ var previousElement, previousScope;
3339
+ scope.$watchCollection(attrs.ngAnimateSwap || attrs['for'], function(value) {
3340
+ if (previousElement) {
3341
+ $animate.leave(previousElement);
3342
+ }
3343
+ if (previousScope) {
3344
+ previousScope.$destroy();
3345
+ previousScope = null;
3346
+ }
3347
+ if (value || value === 0) {
3348
+ previousScope = scope.$new();
3349
+ $transclude(previousScope, function(element) {
3350
+ previousElement = element;
3351
+ $animate.enter(element, null, $element);
3352
+ });
3353
+ }
3354
+ });
3355
+ }
3356
+ };
3357
+ }];
3358
+
2951
3359
  /* global angularAnimateModule: true,
2952
3360
 
2953
- $$rAFMutexFactory,
3361
+ ngAnimateSwapDirective,
3362
+ $$AnimateAsyncRunFactory,
2954
3363
  $$rAFSchedulerFactory,
2955
3364
  $$AnimateChildrenDirective,
2956
- $$AnimateRunnerFactory,
2957
3365
  $$AnimateQueueProvider,
2958
3366
  $$AnimationProvider,
2959
3367
  $AnimateCssProvider,
@@ -2968,7 +3376,7 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) {
2968
3376
  * @description
2969
3377
  *
2970
3378
  * The `ngAnimate` module provides support for CSS-based animations (keyframes and transitions) as well as JavaScript-based animations via
2971
- * callback hooks. Animations are not enabled by default, however, by including `ngAnimate` then the animation hooks are enabled for an Angular app.
3379
+ * callback hooks. Animations are not enabled by default, however, by including `ngAnimate` the animation hooks are enabled for an Angular app.
2972
3380
  *
2973
3381
  * <div doc-module-components="ngAnimate"></div>
2974
3382
  *
@@ -3001,7 +3409,7 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) {
3001
3409
  * CSS-based animations with ngAnimate are unique since they require no JavaScript code at all. By using a CSS class that we reference between our HTML
3002
3410
  * and CSS code we can create an animation that will be picked up by Angular when an the underlying directive performs an operation.
3003
3411
  *
3004
- * The example below shows how an `enter` animation can be made possible on a element using `ng-if`:
3412
+ * The example below shows how an `enter` animation can be made possible on an element using `ng-if`:
3005
3413
  *
3006
3414
  * ```html
3007
3415
  * <div ng-if="bool" class="fade">
@@ -3020,7 +3428,7 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) {
3020
3428
  * opacity:0;
3021
3429
  * }
3022
3430
  *
3023
- * /&#42; The starting CSS styles for the enter animation &#42;/
3431
+ * /&#42; The finishing CSS styles for the enter animation &#42;/
3024
3432
  * .fade.ng-enter.ng-enter-active {
3025
3433
  * opacity:1;
3026
3434
  * }
@@ -3136,8 +3544,8 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) {
3136
3544
  * /&#42; this will have a 100ms delay between each successive leave animation &#42;/
3137
3545
  * transition-delay: 0.1s;
3138
3546
  *
3139
- * /&#42; in case the stagger doesn't work then the duration value
3140
- * must be set to 0 to avoid an accidental CSS inheritance &#42;/
3547
+ * /&#42; As of 1.4.4, this must always be set: it signals ngAnimate
3548
+ * to not accidentally inherit a delay property from another CSS class &#42;/
3141
3549
  * transition-duration: 0s;
3142
3550
  * }
3143
3551
  * .my-animation.ng-enter.ng-enter-active {
@@ -3202,11 +3610,39 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) {
3202
3610
  * the CSS class once an animation has completed.)
3203
3611
  *
3204
3612
  *
3613
+ * ### The `ng-[event]-prepare` class
3614
+ *
3615
+ * This is a special class that can be used to prevent unwanted flickering / flash of content before
3616
+ * the actual animation starts. The class is added as soon as an animation is initialized, but removed
3617
+ * before the actual animation starts (after waiting for a $digest).
3618
+ * It is also only added for *structural* animations (`enter`, `move`, and `leave`).
3619
+ *
3620
+ * In practice, flickering can appear when nesting elements with structural animations such as `ngIf`
3621
+ * into elements that have class-based animations such as `ngClass`.
3622
+ *
3623
+ * ```html
3624
+ * <div ng-class="{red: myProp}">
3625
+ * <div ng-class="{blue: myProp}">
3626
+ * <div class="message" ng-if="myProp"></div>
3627
+ * </div>
3628
+ * </div>
3629
+ * ```
3630
+ *
3631
+ * It is possible that during the `enter` animation, the `.message` div will be briefly visible before it starts animating.
3632
+ * In that case, you can add styles to the CSS that make sure the element stays hidden before the animation starts:
3633
+ *
3634
+ * ```css
3635
+ * .message.ng-enter-prepare {
3636
+ * opacity: 0;
3637
+ * }
3638
+ *
3639
+ * ```
3640
+ *
3205
3641
  * ## JavaScript-based Animations
3206
3642
  *
3207
3643
  * ngAnimate also allows for animations to be consumed by JavaScript code. The approach is similar to CSS-based animations (where there is a shared
3208
3644
  * CSS class that is referenced in our HTML code) but in addition we need to register the JavaScript animation on the module. By making use of the
3209
- * `module.animation()` module function we can register the ainmation.
3645
+ * `module.animation()` module function we can register the animation.
3210
3646
  *
3211
3647
  * Let's see an example of a enter/leave animation using `ngRepeat`:
3212
3648
  *
@@ -3238,7 +3674,7 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) {
3238
3674
  * jQuery(element).fadeOut(1000, doneFn);
3239
3675
  * }
3240
3676
  * }
3241
- * }]
3677
+ * }]);
3242
3678
  * ```
3243
3679
  *
3244
3680
  * The nice thing about JS-based animations is that we can inject other services and make use of advanced animation libraries such as
@@ -3269,13 +3705,13 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) {
3269
3705
  * // do some cool animation and call the doneFn
3270
3706
  * }
3271
3707
  * }
3272
- * }]
3708
+ * }]);
3273
3709
  * ```
3274
3710
  *
3275
3711
  * ## CSS + JS Animations Together
3276
3712
  *
3277
3713
  * AngularJS 1.4 and higher has taken steps to make the amalgamation of CSS and JS animations more flexible. However, unlike earlier versions of Angular,
3278
- * defining CSS and JS animations to work off of the same CSS class will not work anymore. Therefore example below will only result in **JS animations taking
3714
+ * defining CSS and JS animations to work off of the same CSS class will not work anymore. Therefore the example below will only result in **JS animations taking
3279
3715
  * charge of the animation**:
3280
3716
  *
3281
3717
  * ```html
@@ -3291,7 +3727,7 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) {
3291
3727
  * jQuery(element).slideIn(1000, doneFn);
3292
3728
  * }
3293
3729
  * }
3294
- * }]
3730
+ * }]);
3295
3731
  * ```
3296
3732
  *
3297
3733
  * ```css
@@ -3304,23 +3740,22 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) {
3304
3740
  * }
3305
3741
  * ```
3306
3742
  *
3307
- * Does this mean that CSS and JS animations cannot be used together? Do JS-based animations always have higher priority? We can suppliment for the
3308
- * lack of CSS animations by making use of the `$animateCss` service to trigger our own tweaked-out, CSS-based animations directly from
3743
+ * Does this mean that CSS and JS animations cannot be used together? Do JS-based animations always have higher priority? We can make up for the
3744
+ * lack of CSS animations by using the `$animateCss` service to trigger our own tweaked-out, CSS-based animations directly from
3309
3745
  * our own JS-based animation code:
3310
3746
  *
3311
3747
  * ```js
3312
3748
  * myModule.animation('.slide', ['$animateCss', function($animateCss) {
3313
3749
  * return {
3314
- * enter: function(element, doneFn) {
3750
+ * enter: function(element) {
3315
3751
  * // this will trigger `.slide.ng-enter` and `.slide.ng-enter-active`.
3316
- * var runner = $animateCss(element, {
3752
+ * return $animateCss(element, {
3317
3753
  * event: 'enter',
3318
3754
  * structural: true
3319
- * }).start();
3320
- * runner.done(doneFn);
3755
+ * });
3321
3756
  * }
3322
3757
  * }
3323
- * }]
3758
+ * }]);
3324
3759
  * ```
3325
3760
  *
3326
3761
  * The nice thing here is that we can save bandwidth by sticking to our CSS-based animation code and we don't need to rely on a 3rd-party animation framework.
@@ -3332,18 +3767,17 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) {
3332
3767
  * ```js
3333
3768
  * myModule.animation('.slide', ['$animateCss', function($animateCss) {
3334
3769
  * return {
3335
- * enter: function(element, doneFn) {
3336
- * var runner = $animateCss(element, {
3770
+ * enter: function(element) {
3771
+ * return $animateCss(element, {
3337
3772
  * event: 'enter',
3773
+ * structural: true,
3338
3774
  * addClass: 'maroon-setting',
3339
3775
  * from: { height:0 },
3340
3776
  * to: { height: 200 }
3341
- * }).start();
3342
- *
3343
- * runner.done(doneFn);
3777
+ * });
3344
3778
  * }
3345
3779
  * }
3346
- * }]
3780
+ * }]);
3347
3781
  * ```
3348
3782
  *
3349
3783
  * Now we can fill in the rest via our transition CSS code:
@@ -3603,7 +4037,7 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) {
3603
4037
  * ngModule.directive('greetingBox', ['$animate', function($animate) {
3604
4038
  * return function(scope, element, attrs) {
3605
4039
  * attrs.$observe('active', function(value) {
3606
- * value ? $animate.addClass(element, 'on') ? $animate.removeClass(element, 'on');
4040
+ * value ? $animate.addClass(element, 'on') : $animate.removeClass(element, 'on');
3607
4041
  * });
3608
4042
  * });
3609
4043
  * }]);
@@ -3614,38 +4048,13 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) {
3614
4048
  *
3615
4049
  * ```css
3616
4050
  * /&#42; normally we would create a CSS class to reference on the element &#42;/
3617
- * [greeting-box].on { transition:0.5s linear all; background:green; color:white; }
4051
+ * greeting-box.on { transition:0.5s linear all; background:green; color:white; }
3618
4052
  * ```
3619
4053
  *
3620
4054
  * The `$animate` service contains a variety of other methods like `enter`, `leave`, `animate` and `setClass`. To learn more about what's
3621
4055
  * possible be sure to visit the {@link ng.$animate $animate service API page}.
3622
4056
  *
3623
4057
  *
3624
- * ### Preventing Collisions With Third Party Libraries
3625
- *
3626
- * Some third-party frameworks place animation duration defaults across many element or className
3627
- * selectors in order to make their code small and reuseable. This can lead to issues with ngAnimate, which
3628
- * is expecting actual animations on these elements and has to wait for their completion.
3629
- *
3630
- * You can prevent this unwanted behavior by using a prefix on all your animation classes:
3631
- *
3632
- * ```css
3633
- * /&#42; prefixed with animate- &#42;/
3634
- * .animate-fade-add.animate-fade-add-active {
3635
- * transition:1s linear all;
3636
- * opacity:0;
3637
- * }
3638
- * ```
3639
- *
3640
- * You then configure `$animate` to enforce this prefix:
3641
- *
3642
- * ```js
3643
- * $animateProvider.classNameFilter(/animate-/);
3644
- * ```
3645
- *
3646
- * This also may provide your application with a speed boost since only specific elements containing CSS class prefix
3647
- * will be evaluated for animation when any DOM changes occur in the application.
3648
- *
3649
4058
  * ## Callbacks and Promises
3650
4059
  *
3651
4060
  * When `$animate` is called it returns a promise that can be used to capture when the animation has ended. Therefore if we were to trigger
@@ -3685,16 +4094,14 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) {
3685
4094
  * @description
3686
4095
  * The ngAnimate `$animate` service documentation is the same for the core `$animate` service.
3687
4096
  *
3688
- * Click here {@link ng.$animate $animate to learn more about animations with `$animate`}.
4097
+ * Click here {@link ng.$animate to learn more about animations with `$animate`}.
3689
4098
  */
3690
4099
  angular.module('ngAnimate', [])
3691
- .directive('ngAnimateChildren', $$AnimateChildrenDirective)
4100
+ .directive('ngAnimateSwap', ngAnimateSwapDirective)
3692
4101
 
3693
- .factory('$$rAFMutex', $$rAFMutexFactory)
4102
+ .directive('ngAnimateChildren', $$AnimateChildrenDirective)
3694
4103
  .factory('$$rAFScheduler', $$rAFSchedulerFactory)
3695
4104
 
3696
- .factory('$$AnimateRunner', $$AnimateRunnerFactory)
3697
-
3698
4105
  .provider('$$animateQueue', $$AnimateQueueProvider)
3699
4106
  .provider('$$animation', $$AnimationProvider)
3700
4107