Project

General

Profile

1 46770 sandro.lab
/**
2
 * @license AngularJS v1.4.7
3
 * (c) 2010-2015 Google, Inc. http://angularjs.org
4
 * License: MIT
5
 */
6
(function(window, angular, undefined) {'use strict';
7
8
/* jshint ignore:start */
9
var noop        = angular.noop;
10
var extend      = angular.extend;
11
var jqLite      = angular.element;
12
var forEach     = angular.forEach;
13
var isArray     = angular.isArray;
14
var isString    = angular.isString;
15
var isObject    = angular.isObject;
16
var isUndefined = angular.isUndefined;
17
var isDefined   = angular.isDefined;
18
var isFunction  = angular.isFunction;
19
var isElement   = angular.isElement;
20
21
var ELEMENT_NODE = 1;
22
var COMMENT_NODE = 8;
23
24
var ADD_CLASS_SUFFIX = '-add';
25
var REMOVE_CLASS_SUFFIX = '-remove';
26
var EVENT_CLASS_PREFIX = 'ng-';
27
var ACTIVE_CLASS_SUFFIX = '-active';
28
29
var NG_ANIMATE_CLASSNAME = 'ng-animate';
30
var NG_ANIMATE_CHILDREN_DATA = '$$ngAnimateChildren';
31
32
// Detect proper transitionend/animationend event names.
33
var CSS_PREFIX = '', TRANSITION_PROP, TRANSITIONEND_EVENT, ANIMATION_PROP, ANIMATIONEND_EVENT;
34
35
// If unprefixed events are not supported but webkit-prefixed are, use the latter.
36
// Otherwise, just use W3C names, browsers not supporting them at all will just ignore them.
37
// Note: Chrome implements `window.onwebkitanimationend` and doesn't implement `window.onanimationend`
38
// but at the same time dispatches the `animationend` event and not `webkitAnimationEnd`.
39
// Register both events in case `window.onanimationend` is not supported because of that,
40
// do the same for `transitionend` as Safari is likely to exhibit similar behavior.
41
// Also, the only modern browser that uses vendor prefixes for transitions/keyframes is webkit
42
// therefore there is no reason to test anymore for other vendor prefixes:
43
// http://caniuse.com/#search=transition
44
if (isUndefined(window.ontransitionend) && isDefined(window.onwebkittransitionend)) {
45
  CSS_PREFIX = '-webkit-';
46
  TRANSITION_PROP = 'WebkitTransition';
47
  TRANSITIONEND_EVENT = 'webkitTransitionEnd transitionend';
48
} else {
49
  TRANSITION_PROP = 'transition';
50
  TRANSITIONEND_EVENT = 'transitionend';
51
}
52
53
if (isUndefined(window.onanimationend) && isDefined(window.onwebkitanimationend)) {
54
  CSS_PREFIX = '-webkit-';
55
  ANIMATION_PROP = 'WebkitAnimation';
56
  ANIMATIONEND_EVENT = 'webkitAnimationEnd animationend';
57
} else {
58
  ANIMATION_PROP = 'animation';
59
  ANIMATIONEND_EVENT = 'animationend';
60
}
61
62
var DURATION_KEY = 'Duration';
63
var PROPERTY_KEY = 'Property';
64
var DELAY_KEY = 'Delay';
65
var TIMING_KEY = 'TimingFunction';
66
var ANIMATION_ITERATION_COUNT_KEY = 'IterationCount';
67
var ANIMATION_PLAYSTATE_KEY = 'PlayState';
68
var SAFE_FAST_FORWARD_DURATION_VALUE = 9999;
69
70
var ANIMATION_DELAY_PROP = ANIMATION_PROP + DELAY_KEY;
71
var ANIMATION_DURATION_PROP = ANIMATION_PROP + DURATION_KEY;
72
var TRANSITION_DELAY_PROP = TRANSITION_PROP + DELAY_KEY;
73
var TRANSITION_DURATION_PROP = TRANSITION_PROP + DURATION_KEY;
74
75
var isPromiseLike = function(p) {
76
  return p && p.then ? true : false;
77
};
78
79
function assertArg(arg, name, reason) {
80
  if (!arg) {
81
    throw ngMinErr('areq', "Argument '{0}' is {1}", (name || '?'), (reason || "required"));
82
  }
83
  return arg;
84
}
85
86
function mergeClasses(a,b) {
87
  if (!a && !b) return '';
88
  if (!a) return b;
89
  if (!b) return a;
90
  if (isArray(a)) a = a.join(' ');
91
  if (isArray(b)) b = b.join(' ');
92
  return a + ' ' + b;
93
}
94
95
function packageStyles(options) {
96
  var styles = {};
97
  if (options && (options.to || options.from)) {
98
    styles.to = options.to;
99
    styles.from = options.from;
100
  }
101
  return styles;
102
}
103
104
function pendClasses(classes, fix, isPrefix) {
105
  var className = '';
106
  classes = isArray(classes)
107
      ? classes
108
      : classes && isString(classes) && classes.length
109
          ? classes.split(/\s+/)
110
          : [];
111
  forEach(classes, function(klass, i) {
112
    if (klass && klass.length > 0) {
113
      className += (i > 0) ? ' ' : '';
114
      className += isPrefix ? fix + klass
115
                            : klass + fix;
116
    }
117
  });
118
  return className;
119
}
120
121
function removeFromArray(arr, val) {
122
  var index = arr.indexOf(val);
123
  if (val >= 0) {
124
    arr.splice(index, 1);
125
  }
126
}
127
128
function stripCommentsFromElement(element) {
129
  if (element instanceof jqLite) {
130
    switch (element.length) {
131
      case 0:
132
        return [];
133
        break;
134
135
      case 1:
136
        // there is no point of stripping anything if the element
137
        // is the only element within the jqLite wrapper.
138
        // (it's important that we retain the element instance.)
139
        if (element[0].nodeType === ELEMENT_NODE) {
140
          return element;
141
        }
142
        break;
143
144
      default:
145
        return jqLite(extractElementNode(element));
146
        break;
147
    }
148
  }
149
150
  if (element.nodeType === ELEMENT_NODE) {
151
    return jqLite(element);
152
  }
153
}
154
155
function extractElementNode(element) {
156
  if (!element[0]) return element;
157
  for (var i = 0; i < element.length; i++) {
158
    var elm = element[i];
159
    if (elm.nodeType == ELEMENT_NODE) {
160
      return elm;
161
    }
162
  }
163
}
164
165
function $$addClass($$jqLite, element, className) {
166
  forEach(element, function(elm) {
167
    $$jqLite.addClass(elm, className);
168
  });
169
}
170
171
function $$removeClass($$jqLite, element, className) {
172
  forEach(element, function(elm) {
173
    $$jqLite.removeClass(elm, className);
174
  });
175
}
176
177
function applyAnimationClassesFactory($$jqLite) {
178
  return function(element, options) {
179
    if (options.addClass) {
180
      $$addClass($$jqLite, element, options.addClass);
181
      options.addClass = null;
182
    }
183
    if (options.removeClass) {
184
      $$removeClass($$jqLite, element, options.removeClass);
185
      options.removeClass = null;
186
    }
187
  }
188
}
189
190
function prepareAnimationOptions(options) {
191
  options = options || {};
192
  if (!options.$$prepared) {
193
    var domOperation = options.domOperation || noop;
194
    options.domOperation = function() {
195
      options.$$domOperationFired = true;
196
      domOperation();
197
      domOperation = noop;
198
    };
199
    options.$$prepared = true;
200
  }
201
  return options;
202
}
203
204
function applyAnimationStyles(element, options) {
205
  applyAnimationFromStyles(element, options);
206
  applyAnimationToStyles(element, options);
207
}
208
209
function applyAnimationFromStyles(element, options) {
210
  if (options.from) {
211
    element.css(options.from);
212
    options.from = null;
213
  }
214
}
215
216
function applyAnimationToStyles(element, options) {
217
  if (options.to) {
218
    element.css(options.to);
219
    options.to = null;
220
  }
221
}
222
223
function mergeAnimationOptions(element, target, newOptions) {
224
  var toAdd = (target.addClass || '') + ' ' + (newOptions.addClass || '');
225
  var toRemove = (target.removeClass || '') + ' ' + (newOptions.removeClass || '');
226
  var classes = resolveElementClasses(element.attr('class'), toAdd, toRemove);
227
228
  if (newOptions.preparationClasses) {
229
    target.preparationClasses = concatWithSpace(newOptions.preparationClasses, target.preparationClasses);
230
    delete newOptions.preparationClasses;
231
  }
232
233
  // noop is basically when there is no callback; otherwise something has been set
234
  var realDomOperation = target.domOperation !== noop ? target.domOperation : null;
235
236
  extend(target, newOptions);
237
238
  // 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.
239
  if (realDomOperation) {
240
    target.domOperation = realDomOperation;
241
  }
242
243
  if (classes.addClass) {
244
    target.addClass = classes.addClass;
245
  } else {
246
    target.addClass = null;
247
  }
248
249
  if (classes.removeClass) {
250
    target.removeClass = classes.removeClass;
251
  } else {
252
    target.removeClass = null;
253
  }
254
255
  return target;
256
}
257
258
function resolveElementClasses(existing, toAdd, toRemove) {
259
  var ADD_CLASS = 1;
260
  var REMOVE_CLASS = -1;
261
262
  var flags = {};
263
  existing = splitClassesToLookup(existing);
264
265
  toAdd = splitClassesToLookup(toAdd);
266
  forEach(toAdd, function(value, key) {
267
    flags[key] = ADD_CLASS;
268
  });
269
270
  toRemove = splitClassesToLookup(toRemove);
271
  forEach(toRemove, function(value, key) {
272
    flags[key] = flags[key] === ADD_CLASS ? null : REMOVE_CLASS;
273
  });
274
275
  var classes = {
276
    addClass: '',
277
    removeClass: ''
278
  };
279
280
  forEach(flags, function(val, klass) {
281
    var prop, allow;
282
    if (val === ADD_CLASS) {
283
      prop = 'addClass';
284
      allow = !existing[klass];
285
    } else if (val === REMOVE_CLASS) {
286
      prop = 'removeClass';
287
      allow = existing[klass];
288
    }
289
    if (allow) {
290
      if (classes[prop].length) {
291
        classes[prop] += ' ';
292
      }
293
      classes[prop] += klass;
294
    }
295
  });
296
297
  function splitClassesToLookup(classes) {
298
    if (isString(classes)) {
299
      classes = classes.split(' ');
300
    }
301
302
    var obj = {};
303
    forEach(classes, function(klass) {
304
      // sometimes the split leaves empty string values
305
      // incase extra spaces were applied to the options
306
      if (klass.length) {
307
        obj[klass] = true;
308
      }
309
    });
310
    return obj;
311
  }
312
313
  return classes;
314
}
315
316
function getDomNode(element) {
317
  return (element instanceof angular.element) ? element[0] : element;
318
}
319
320
function applyGeneratedPreparationClasses(element, event, options) {
321
  var classes = '';
322
  if (event) {
323
    classes = pendClasses(event, EVENT_CLASS_PREFIX, true);
324
  }
325
  if (options.addClass) {
326
    classes = concatWithSpace(classes, pendClasses(options.addClass, ADD_CLASS_SUFFIX));
327
  }
328
  if (options.removeClass) {
329
    classes = concatWithSpace(classes, pendClasses(options.removeClass, REMOVE_CLASS_SUFFIX));
330
  }
331
  if (classes.length) {
332
    options.preparationClasses = classes;
333
    element.addClass(classes);
334
  }
335
}
336
337
function clearGeneratedClasses(element, options) {
338
  if (options.preparationClasses) {
339
    element.removeClass(options.preparationClasses);
340
    options.preparationClasses = null;
341
  }
342
  if (options.activeClasses) {
343
    element.removeClass(options.activeClasses);
344
    options.activeClasses = null;
345
  }
346
}
347
348
function blockTransitions(node, duration) {
349
  // we use a negative delay value since it performs blocking
350
  // yet it doesn't kill any existing transitions running on the
351
  // same element which makes this safe for class-based animations
352
  var value = duration ? '-' + duration + 's' : '';
353
  applyInlineStyle(node, [TRANSITION_DELAY_PROP, value]);
354
  return [TRANSITION_DELAY_PROP, value];
355
}
356
357
function blockKeyframeAnimations(node, applyBlock) {
358
  var value = applyBlock ? 'paused' : '';
359
  var key = ANIMATION_PROP + ANIMATION_PLAYSTATE_KEY;
360
  applyInlineStyle(node, [key, value]);
361
  return [key, value];
362
}
363
364
function applyInlineStyle(node, styleTuple) {
365
  var prop = styleTuple[0];
366
  var value = styleTuple[1];
367
  node.style[prop] = value;
368
}
369
370
function concatWithSpace(a,b) {
371
  if (!a) return b;
372
  if (!b) return a;
373
  return a + ' ' + b;
374
}
375
376
var $$rAFSchedulerFactory = ['$$rAF', function($$rAF) {
377
  var queue, cancelFn;
378
379
  function scheduler(tasks) {
380
    // we make a copy since RAFScheduler mutates the state
381
    // of the passed in array variable and this would be difficult
382
    // to track down on the outside code
383
    queue = queue.concat(tasks);
384
    nextTick();
385
  }
386
387
  queue = scheduler.queue = [];
388
389
  /* waitUntilQuiet does two things:
390
   * 1. It will run the FINAL `fn` value only when an uncancelled RAF has passed through
391
   * 2. It will delay the next wave of tasks from running until the quiet `fn` has run.
392
   *
393
   * The motivation here is that animation code can request more time from the scheduler
394
   * before the next wave runs. This allows for certain DOM properties such as classes to
395
   * be resolved in time for the next animation to run.
396
   */
397
  scheduler.waitUntilQuiet = function(fn) {
398
    if (cancelFn) cancelFn();
399
400
    cancelFn = $$rAF(function() {
401
      cancelFn = null;
402
      fn();
403
      nextTick();
404
    });
405
  };
406
407
  return scheduler;
408
409
  function nextTick() {
410
    if (!queue.length) return;
411
412
    var items = queue.shift();
413
    for (var i = 0; i < items.length; i++) {
414
      items[i]();
415
    }
416
417
    if (!cancelFn) {
418
      $$rAF(function() {
419
        if (!cancelFn) nextTick();
420
      });
421
    }
422
  }
423
}];
424
425
var $$AnimateChildrenDirective = [function() {
426
  return function(scope, element, attrs) {
427
    var val = attrs.ngAnimateChildren;
428
    if (angular.isString(val) && val.length === 0) { //empty attribute
429
      element.data(NG_ANIMATE_CHILDREN_DATA, true);
430
    } else {
431
      attrs.$observe('ngAnimateChildren', function(value) {
432
        value = value === 'on' || value === 'true';
433
        element.data(NG_ANIMATE_CHILDREN_DATA, value);
434
      });
435
    }
436
  };
437
}];
438
439
var ANIMATE_TIMER_KEY = '$$animateCss';
440
441
/**
442
 * @ngdoc service
443
 * @name $animateCss
444
 * @kind object
445
 *
446
 * @description
447
 * The `$animateCss` service is a useful utility to trigger customized CSS-based transitions/keyframes
448
 * from a JavaScript-based animation or directly from a directive. The purpose of `$animateCss` is NOT
449
 * to side-step how `$animate` and ngAnimate work, but the goal is to allow pre-existing animations or
450
 * directives to create more complex animations that can be purely driven using CSS code.
451
 *
452
 * Note that only browsers that support CSS transitions and/or keyframe animations are capable of
453
 * rendering animations triggered via `$animateCss` (bad news for IE9 and lower).
454
 *
455
 * ## Usage
456
 * Once again, `$animateCss` is designed to be used inside of a registered JavaScript animation that
457
 * is powered by ngAnimate. It is possible to use `$animateCss` directly inside of a directive, however,
458
 * any automatic control over cancelling animations and/or preventing animations from being run on
459
 * child elements will not be handled by Angular. For this to work as expected, please use `$animate` to
460
 * trigger the animation and then setup a JavaScript animation that injects `$animateCss` to trigger
461
 * the CSS animation.
462
 *
463
 * The example below shows how we can create a folding animation on an element using `ng-if`:
464
 *
465
 * ```html
466
 * <!-- notice the `fold-animation` CSS class -->
467
 * <div ng-if="onOff" class="fold-animation">
468
 *   This element will go BOOM
469
 * </div>
470
 * <button ng-click="onOff=true">Fold In</button>
471
 * ```
472
 *
473
 * Now we create the **JavaScript animation** that will trigger the CSS transition:
474
 *
475
 * ```js
476
 * ngModule.animation('.fold-animation', ['$animateCss', function($animateCss) {
477
 *   return {
478
 *     enter: function(element, doneFn) {
479
 *       var height = element[0].offsetHeight;
480
 *       return $animateCss(element, {
481
 *         from: { height:'0px' },
482
 *         to: { height:height + 'px' },
483
 *         duration: 1 // one second
484
 *       });
485
 *     }
486
 *   }
487
 * }]);
488
 * ```
489
 *
490
 * ## More Advanced Uses
491
 *
492
 * `$animateCss` is the underlying code that ngAnimate uses to power **CSS-based animations** behind the scenes. Therefore CSS hooks
493
 * like `.ng-EVENT`, `.ng-EVENT-active`, `.ng-EVENT-stagger` are all features that can be triggered using `$animateCss` via JavaScript code.
494
 *
495
 * This also means that just about any combination of adding classes, removing classes, setting styles, dynamically setting a keyframe animation,
496
 * applying a hardcoded duration or delay value, changing the animation easing or applying a stagger animation are all options that work with
497
 * `$animateCss`. The service itself is smart enough to figure out the combination of options and examine the element styling properties in order
498
 * to provide a working animation that will run in CSS.
499
 *
500
 * The example below showcases a more advanced version of the `.fold-animation` from the example above:
501
 *
502
 * ```js
503
 * ngModule.animation('.fold-animation', ['$animateCss', function($animateCss) {
504
 *   return {
505
 *     enter: function(element, doneFn) {
506
 *       var height = element[0].offsetHeight;
507
 *       return $animateCss(element, {
508
 *         addClass: 'red large-text pulse-twice',
509
 *         easing: 'ease-out',
510
 *         from: { height:'0px' },
511
 *         to: { height:height + 'px' },
512
 *         duration: 1 // one second
513
 *       });
514
 *     }
515
 *   }
516
 * }]);
517
 * ```
518
 *
519
 * Since we're adding/removing CSS classes then the CSS transition will also pick those up:
520
 *
521
 * ```css
522
 * /&#42; since a hardcoded duration value of 1 was provided in the JavaScript animation code,
523
 * the CSS classes below will be transitioned despite them being defined as regular CSS classes &#42;/
524
 * .red { background:red; }
525
 * .large-text { font-size:20px; }
526
 *
527
 * /&#42; we can also use a keyframe animation and $animateCss will make it work alongside the transition &#42;/
528
 * .pulse-twice {
529
 *   animation: 0.5s pulse linear 2;
530
 *   -webkit-animation: 0.5s pulse linear 2;
531
 * }
532
 *
533
 * @keyframes pulse {
534
 *   from { transform: scale(0.5); }
535
 *   to { transform: scale(1.5); }
536
 * }
537
 *
538
 * @-webkit-keyframes pulse {
539
 *   from { -webkit-transform: scale(0.5); }
540
 *   to { -webkit-transform: scale(1.5); }
541
 * }
542
 * ```
543
 *
544
 * Given this complex combination of CSS classes, styles and options, `$animateCss` will figure everything out and make the animation happen.
545
 *
546
 * ## How the Options are handled
547
 *
548
 * `$animateCss` is very versatile and intelligent when it comes to figuring out what configurations to apply to the element to ensure the animation
549
 * works with the options provided. Say for example we were adding a class that contained a keyframe value and we wanted to also animate some inline
550
 * styles using the `from` and `to` properties.
551
 *
552
 * ```js
553
 * var animator = $animateCss(element, {
554
 *   from: { background:'red' },
555
 *   to: { background:'blue' }
556
 * });
557
 * animator.start();
558
 * ```
559
 *
560
 * ```css
561
 * .rotating-animation {
562
 *   animation:0.5s rotate linear;
563
 *   -webkit-animation:0.5s rotate linear;
564
 * }
565
 *
566
 * @keyframes rotate {
567
 *   from { transform: rotate(0deg); }
568
 *   to { transform: rotate(360deg); }
569
 * }
570
 *
571
 * @-webkit-keyframes rotate {
572
 *   from { -webkit-transform: rotate(0deg); }
573
 *   to { -webkit-transform: rotate(360deg); }
574
 * }
575
 * ```
576
 *
577
 * The missing pieces here are that we do not have a transition set (within the CSS code nor within the `$animateCss` options) and the duration of the animation is
578
 * going to be detected from what the keyframe styles on the CSS class are. In this event, `$animateCss` will automatically create an inline transition
579
 * style matching the duration detected from the keyframe style (which is present in the CSS class that is being added) and then prepare both the transition
580
 * and keyframe animations to run in parallel on the element. Then when the animation is underway the provided `from` and `to` CSS styles will be applied
581
 * and spread across the transition and keyframe animation.
582
 *
583
 * ## What is returned
584
 *
585
 * `$animateCss` works in two stages: a preparation phase and an animation phase. Therefore when `$animateCss` is first called it will NOT actually
586
 * start the animation. All that is going on here is that the element is being prepared for the animation (which means that the generated CSS classes are
587
 * added and removed on the element). Once `$animateCss` is called it will return an object with the following properties:
588
 *
589
 * ```js
590
 * var animator = $animateCss(element, { ... });
591
 * ```
592
 *
593
 * Now what do the contents of our `animator` variable look like:
594
 *
595
 * ```js
596
 * {
597
 *   // starts the animation
598
 *   start: Function,
599
 *
600
 *   // ends (aborts) the animation
601
 *   end: Function
602
 * }
603
 * ```
604
 *
605
 * To actually start the animation we need to run `animation.start()` which will then return a promise that we can hook into to detect when the animation ends.
606
 * If we choose not to run the animation then we MUST run `animation.end()` to perform a cleanup on the element (since some CSS classes and stlyes may have been
607
 * applied to the element during the preparation phase). Note that all other properties such as duration, delay, transitions and keyframes are just properties
608
 * and that changing them will not reconfigure the parameters of the animation.
609
 *
610
 * ### runner.done() vs runner.then()
611
 * It is documented that `animation.start()` will return a promise object and this is true, however, there is also an additional method available on the
612
 * runner called `.done(callbackFn)`. The done method works the same as `.finally(callbackFn)`, however, it does **not trigger a digest to occur**.
613
 * Therefore, for performance reasons, it's always best to use `runner.done(callback)` instead of `runner.then()`, `runner.catch()` or `runner.finally()`
614
 * unless you really need a digest to kick off afterwards.
615
 *
616
 * 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
617
 * (so there is no need to call `runner.done(doneFn)` inside of your JavaScript animation code).
618
 * Check the {@link ngAnimate.$animateCss#usage animation code above} to see how this works.
619
 *
620
 * @param {DOMElement} element the element that will be animated
621
 * @param {object} options the animation-related options that will be applied during the animation
622
 *
623
 * * `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
624
 * 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.)
625
 * * `easing` - The CSS easing value that will be applied to the transition or keyframe animation (or both).
626
 * * `transitionStyle` - The raw CSS transition style that will be used (e.g. `1s linear all`).
627
 * * `keyframeStyle` - The raw CSS keyframe animation style that will be used (e.g. `1s my_animation linear`).
628
 * * `from` - The starting CSS styles (a key/value object) that will be applied at the start of the animation.
629
 * * `to` - The ending CSS styles (a key/value object) that will be applied across the animation via a CSS transition.
630
 * * `addClass` - A space separated list of CSS classes that will be added to the element and spread across the animation.
631
 * * `removeClass` - A space separated list of CSS classes that will be removed from the element and spread across the animation.
632
 * * `duration` - A number value representing the total duration of the transition and/or keyframe (note that a value of 1 is 1000ms). If a value of `0`
633
 * is provided then the animation will be skipped entirely.
634
 * * `delay` - A number value representing the total delay of the transition and/or keyframe (note that a value of 1 is 1000ms). If a value of `true` is
635
 * used then whatever delay value is detected from the CSS classes will be mirrored on the elements styles (e.g. by setting delay true then the style value
636
 * of the element will be `transition-delay: DETECTED_VALUE`). Using `true` is useful when you want the CSS classes and inline styles to all share the same
637
 * CSS delay value.
638
 * * `stagger` - A numeric time value representing the delay between successively animated elements
639
 * ({@link ngAnimate#css-staggering-animations Click here to learn how CSS-based staggering works in ngAnimate.})
640
 * * `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
641
 * * `stagger` option value of `0.1` is used then there will be a stagger delay of `600ms`)
642
 * * `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.)
643
 * * `cleanupStyles` - Whether or not the provided `from` and `to` styles will be removed once
644
 *    the animation is closed. This is useful for when the styles are used purely for the sake of
645
 *    the animation and do not have a lasting visual effect on the element (e.g. a colapse and open animation).
646
 *    By default this value is set to `false`.
647
 *
648
 * @return {object} an object with start and end methods and details about the animation.
649
 *
650
 * * `start` - The method to start the animation. This will return a `Promise` when called.
651
 * * `end` - This method will cancel the animation and remove all applied CSS classes and styles.
652
 */
653
var ONE_SECOND = 1000;
654
var BASE_TEN = 10;
655
656
var ELAPSED_TIME_MAX_DECIMAL_PLACES = 3;
657
var CLOSING_TIME_BUFFER = 1.5;
658
659
var DETECT_CSS_PROPERTIES = {
660
  transitionDuration:      TRANSITION_DURATION_PROP,
661
  transitionDelay:         TRANSITION_DELAY_PROP,
662
  transitionProperty:      TRANSITION_PROP + PROPERTY_KEY,
663
  animationDuration:       ANIMATION_DURATION_PROP,
664
  animationDelay:          ANIMATION_DELAY_PROP,
665
  animationIterationCount: ANIMATION_PROP + ANIMATION_ITERATION_COUNT_KEY
666
};
667
668
var DETECT_STAGGER_CSS_PROPERTIES = {
669
  transitionDuration:      TRANSITION_DURATION_PROP,
670
  transitionDelay:         TRANSITION_DELAY_PROP,
671
  animationDuration:       ANIMATION_DURATION_PROP,
672
  animationDelay:          ANIMATION_DELAY_PROP
673
};
674
675
function getCssKeyframeDurationStyle(duration) {
676
  return [ANIMATION_DURATION_PROP, duration + 's'];
677
}
678
679
function getCssDelayStyle(delay, isKeyframeAnimation) {
680
  var prop = isKeyframeAnimation ? ANIMATION_DELAY_PROP : TRANSITION_DELAY_PROP;
681
  return [prop, delay + 's'];
682
}
683
684
function computeCssStyles($window, element, properties) {
685
  var styles = Object.create(null);
686
  var detectedStyles = $window.getComputedStyle(element) || {};
687
  forEach(properties, function(formalStyleName, actualStyleName) {
688
    var val = detectedStyles[formalStyleName];
689
    if (val) {
690
      var c = val.charAt(0);
691
692
      // only numerical-based values have a negative sign or digit as the first value
693
      if (c === '-' || c === '+' || c >= 0) {
694
        val = parseMaxTime(val);
695
      }
696
697
      // by setting this to null in the event that the delay is not set or is set directly as 0
698
      // then we can still allow for zegative values to be used later on and not mistake this
699
      // value for being greater than any other negative value.
700
      if (val === 0) {
701
        val = null;
702
      }
703
      styles[actualStyleName] = val;
704
    }
705
  });
706
707
  return styles;
708
}
709
710
function parseMaxTime(str) {
711
  var maxValue = 0;
712
  var values = str.split(/\s*,\s*/);
713
  forEach(values, function(value) {
714
    // it's always safe to consider only second values and omit `ms` values since
715
    // getComputedStyle will always handle the conversion for us
716
    if (value.charAt(value.length - 1) == 's') {
717
      value = value.substring(0, value.length - 1);
718
    }
719
    value = parseFloat(value) || 0;
720
    maxValue = maxValue ? Math.max(value, maxValue) : value;
721
  });
722
  return maxValue;
723
}
724
725
function truthyTimingValue(val) {
726
  return val === 0 || val != null;
727
}
728
729
function getCssTransitionDurationStyle(duration, applyOnlyDuration) {
730
  var style = TRANSITION_PROP;
731
  var value = duration + 's';
732
  if (applyOnlyDuration) {
733
    style += DURATION_KEY;
734
  } else {
735
    value += ' linear all';
736
  }
737
  return [style, value];
738
}
739
740
function createLocalCacheLookup() {
741
  var cache = Object.create(null);
742
  return {
743
    flush: function() {
744
      cache = Object.create(null);
745
    },
746
747
    count: function(key) {
748
      var entry = cache[key];
749
      return entry ? entry.total : 0;
750
    },
751
752
    get: function(key) {
753
      var entry = cache[key];
754
      return entry && entry.value;
755
    },
756
757
    put: function(key, value) {
758
      if (!cache[key]) {
759
        cache[key] = { total: 1, value: value };
760
      } else {
761
        cache[key].total++;
762
      }
763
    }
764
  };
765
}
766
767
// we do not reassign an already present style value since
768
// if we detect the style property value again we may be
769
// detecting styles that were added via the `from` styles.
770
// We make use of `isDefined` here since an empty string
771
// or null value (which is what getPropertyValue will return
772
// for a non-existing style) will still be marked as a valid
773
// value for the style (a falsy value implies that the style
774
// is to be removed at the end of the animation). If we had a simple
775
// "OR" statement then it would not be enough to catch that.
776
function registerRestorableStyles(backup, node, properties) {
777
  forEach(properties, function(prop) {
778
    backup[prop] = isDefined(backup[prop])
779
        ? backup[prop]
780
        : node.style.getPropertyValue(prop);
781
  });
782
}
783
784
var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
785
  var gcsLookup = createLocalCacheLookup();
786
  var gcsStaggerLookup = createLocalCacheLookup();
787
788
  this.$get = ['$window', '$$jqLite', '$$AnimateRunner', '$timeout',
789
               '$$forceReflow', '$sniffer', '$$rAFScheduler', '$animate',
790
       function($window,   $$jqLite,   $$AnimateRunner,   $timeout,
791
                $$forceReflow,   $sniffer,   $$rAFScheduler, $animate) {
792
793
    var applyAnimationClasses = applyAnimationClassesFactory($$jqLite);
794
795
    var parentCounter = 0;
796
    function gcsHashFn(node, extraClasses) {
797
      var KEY = "$$ngAnimateParentKey";
798
      var parentNode = node.parentNode;
799
      var parentID = parentNode[KEY] || (parentNode[KEY] = ++parentCounter);
800
      return parentID + '-' + node.getAttribute('class') + '-' + extraClasses;
801
    }
802
803
    function computeCachedCssStyles(node, className, cacheKey, properties) {
804
      var timings = gcsLookup.get(cacheKey);
805
806
      if (!timings) {
807
        timings = computeCssStyles($window, node, properties);
808
        if (timings.animationIterationCount === 'infinite') {
809
          timings.animationIterationCount = 1;
810
        }
811
      }
812
813
      // we keep putting this in multiple times even though the value and the cacheKey are the same
814
      // because we're keeping an interal tally of how many duplicate animations are detected.
815
      gcsLookup.put(cacheKey, timings);
816
      return timings;
817
    }
818
819
    function computeCachedCssStaggerStyles(node, className, cacheKey, properties) {
820
      var stagger;
821
822
      // if we have one or more existing matches of matching elements
823
      // containing the same parent + CSS styles (which is how cacheKey works)
824
      // then staggering is possible
825
      if (gcsLookup.count(cacheKey) > 0) {
826
        stagger = gcsStaggerLookup.get(cacheKey);
827
828
        if (!stagger) {
829
          var staggerClassName = pendClasses(className, '-stagger');
830
831
          $$jqLite.addClass(node, staggerClassName);
832
833
          stagger = computeCssStyles($window, node, properties);
834
835
          // force the conversion of a null value to zero incase not set
836
          stagger.animationDuration = Math.max(stagger.animationDuration, 0);
837
          stagger.transitionDuration = Math.max(stagger.transitionDuration, 0);
838
839
          $$jqLite.removeClass(node, staggerClassName);
840
841
          gcsStaggerLookup.put(cacheKey, stagger);
842
        }
843
      }
844
845
      return stagger || {};
846
    }
847
848
    var cancelLastRAFRequest;
849
    var rafWaitQueue = [];
850
    function waitUntilQuiet(callback) {
851
      rafWaitQueue.push(callback);
852
      $$rAFScheduler.waitUntilQuiet(function() {
853
        gcsLookup.flush();
854
        gcsStaggerLookup.flush();
855
856
        // DO NOT REMOVE THIS LINE OR REFACTOR OUT THE `pageWidth` variable.
857
        // PLEASE EXAMINE THE `$$forceReflow` service to understand why.
858
        var pageWidth = $$forceReflow();
859
860
        // we use a for loop to ensure that if the queue is changed
861
        // during this looping then it will consider new requests
862
        for (var i = 0; i < rafWaitQueue.length; i++) {
863
          rafWaitQueue[i](pageWidth);
864
        }
865
        rafWaitQueue.length = 0;
866
      });
867
    }
868
869
    function computeTimings(node, className, cacheKey) {
870
      var timings = computeCachedCssStyles(node, className, cacheKey, DETECT_CSS_PROPERTIES);
871
      var aD = timings.animationDelay;
872
      var tD = timings.transitionDelay;
873
      timings.maxDelay = aD && tD
874
          ? Math.max(aD, tD)
875
          : (aD || tD);
876
      timings.maxDuration = Math.max(
877
          timings.animationDuration * timings.animationIterationCount,
878
          timings.transitionDuration);
879
880
      return timings;
881
    }
882
883
    return function init(element, options) {
884
      var restoreStyles = {};
885
      var node = getDomNode(element);
886
      if (!node
887
          || !node.parentNode
888
          || !$animate.enabled()) {
889
        return closeAndReturnNoopAnimator();
890
      }
891
892
      options = prepareAnimationOptions(options);
893
894
      var temporaryStyles = [];
895
      var classes = element.attr('class');
896
      var styles = packageStyles(options);
897
      var animationClosed;
898
      var animationPaused;
899
      var animationCompleted;
900
      var runner;
901
      var runnerHost;
902
      var maxDelay;
903
      var maxDelayTime;
904
      var maxDuration;
905
      var maxDurationTime;
906
907
      if (options.duration === 0 || (!$sniffer.animations && !$sniffer.transitions)) {
908
        return closeAndReturnNoopAnimator();
909
      }
910
911
      var method = options.event && isArray(options.event)
912
            ? options.event.join(' ')
913
            : options.event;
914
915
      var isStructural = method && options.structural;
916
      var structuralClassName = '';
917
      var addRemoveClassName = '';
918
919
      if (isStructural) {
920
        structuralClassName = pendClasses(method, EVENT_CLASS_PREFIX, true);
921
      } else if (method) {
922
        structuralClassName = method;
923
      }
924
925
      if (options.addClass) {
926
        addRemoveClassName += pendClasses(options.addClass, ADD_CLASS_SUFFIX);
927
      }
928
929
      if (options.removeClass) {
930
        if (addRemoveClassName.length) {
931
          addRemoveClassName += ' ';
932
        }
933
        addRemoveClassName += pendClasses(options.removeClass, REMOVE_CLASS_SUFFIX);
934
      }
935
936
      // there may be a situation where a structural animation is combined together
937
      // with CSS classes that need to resolve before the animation is computed.
938
      // However this means that there is no explicit CSS code to block the animation
939
      // from happening (by setting 0s none in the class name). If this is the case
940
      // we need to apply the classes before the first rAF so we know to continue if
941
      // there actually is a detected transition or keyframe animation
942
      if (options.applyClassesEarly && addRemoveClassName.length) {
943
        applyAnimationClasses(element, options);
944
      }
945
946
      var preparationClasses = [structuralClassName, addRemoveClassName].join(' ').trim();
947
      var fullClassName = classes + ' ' + preparationClasses;
948
      var activeClasses = pendClasses(preparationClasses, ACTIVE_CLASS_SUFFIX);
949
      var hasToStyles = styles.to && Object.keys(styles.to).length > 0;
950
      var containsKeyframeAnimation = (options.keyframeStyle || '').length > 0;
951
952
      // there is no way we can trigger an animation if no styles and
953
      // no classes are being applied which would then trigger a transition,
954
      // unless there a is raw keyframe value that is applied to the element.
955
      if (!containsKeyframeAnimation
956
           && !hasToStyles
957
           && !preparationClasses) {
958
        return closeAndReturnNoopAnimator();
959
      }
960
961
      var cacheKey, stagger;
962
      if (options.stagger > 0) {
963
        var staggerVal = parseFloat(options.stagger);
964
        stagger = {
965
          transitionDelay: staggerVal,
966
          animationDelay: staggerVal,
967
          transitionDuration: 0,
968
          animationDuration: 0
969
        };
970
      } else {
971
        cacheKey = gcsHashFn(node, fullClassName);
972
        stagger = computeCachedCssStaggerStyles(node, preparationClasses, cacheKey, DETECT_STAGGER_CSS_PROPERTIES);
973
      }
974
975
      if (!options.$$skipPreparationClasses) {
976
        $$jqLite.addClass(element, preparationClasses);
977
      }
978
979
      var applyOnlyDuration;
980
981
      if (options.transitionStyle) {
982
        var transitionStyle = [TRANSITION_PROP, options.transitionStyle];
983
        applyInlineStyle(node, transitionStyle);
984
        temporaryStyles.push(transitionStyle);
985
      }
986
987
      if (options.duration >= 0) {
988
        applyOnlyDuration = node.style[TRANSITION_PROP].length > 0;
989
        var durationStyle = getCssTransitionDurationStyle(options.duration, applyOnlyDuration);
990
991
        // we set the duration so that it will be picked up by getComputedStyle later
992
        applyInlineStyle(node, durationStyle);
993
        temporaryStyles.push(durationStyle);
994
      }
995
996
      if (options.keyframeStyle) {
997
        var keyframeStyle = [ANIMATION_PROP, options.keyframeStyle];
998
        applyInlineStyle(node, keyframeStyle);
999
        temporaryStyles.push(keyframeStyle);
1000
      }
1001
1002
      var itemIndex = stagger
1003
          ? options.staggerIndex >= 0
1004
              ? options.staggerIndex
1005
              : gcsLookup.count(cacheKey)
1006
          : 0;
1007
1008
      var isFirst = itemIndex === 0;
1009
1010
      // this is a pre-emptive way of forcing the setup classes to be added and applied INSTANTLY
1011
      // without causing any combination of transitions to kick in. By adding a negative delay value
1012
      // it forces the setup class' transition to end immediately. We later then remove the negative
1013
      // transition delay to allow for the transition to naturally do it's thing. The beauty here is
1014
      // that if there is no transition defined then nothing will happen and this will also allow
1015
      // other transitions to be stacked on top of each other without any chopping them out.
1016
      if (isFirst && !options.skipBlocking) {
1017
        blockTransitions(node, SAFE_FAST_FORWARD_DURATION_VALUE);
1018
      }
1019
1020
      var timings = computeTimings(node, fullClassName, cacheKey);
1021
      var relativeDelay = timings.maxDelay;
1022
      maxDelay = Math.max(relativeDelay, 0);
1023
      maxDuration = timings.maxDuration;
1024
1025
      var flags = {};
1026
      flags.hasTransitions          = timings.transitionDuration > 0;
1027
      flags.hasAnimations           = timings.animationDuration > 0;
1028
      flags.hasTransitionAll        = flags.hasTransitions && timings.transitionProperty == 'all';
1029
      flags.applyTransitionDuration = hasToStyles && (
1030
                                        (flags.hasTransitions && !flags.hasTransitionAll)
1031
                                         || (flags.hasAnimations && !flags.hasTransitions));
1032
      flags.applyAnimationDuration  = options.duration && flags.hasAnimations;
1033
      flags.applyTransitionDelay    = truthyTimingValue(options.delay) && (flags.applyTransitionDuration || flags.hasTransitions);
1034
      flags.applyAnimationDelay     = truthyTimingValue(options.delay) && flags.hasAnimations;
1035
      flags.recalculateTimingStyles = addRemoveClassName.length > 0;
1036
1037
      if (flags.applyTransitionDuration || flags.applyAnimationDuration) {
1038
        maxDuration = options.duration ? parseFloat(options.duration) : maxDuration;
1039
1040
        if (flags.applyTransitionDuration) {
1041
          flags.hasTransitions = true;
1042
          timings.transitionDuration = maxDuration;
1043
          applyOnlyDuration = node.style[TRANSITION_PROP + PROPERTY_KEY].length > 0;
1044
          temporaryStyles.push(getCssTransitionDurationStyle(maxDuration, applyOnlyDuration));
1045
        }
1046
1047
        if (flags.applyAnimationDuration) {
1048
          flags.hasAnimations = true;
1049
          timings.animationDuration = maxDuration;
1050
          temporaryStyles.push(getCssKeyframeDurationStyle(maxDuration));
1051
        }
1052
      }
1053
1054
      if (maxDuration === 0 && !flags.recalculateTimingStyles) {
1055
        return closeAndReturnNoopAnimator();
1056
      }
1057
1058
      if (options.delay != null) {
1059
        var delayStyle = parseFloat(options.delay);
1060
1061
        if (flags.applyTransitionDelay) {
1062
          temporaryStyles.push(getCssDelayStyle(delayStyle));
1063
        }
1064
1065
        if (flags.applyAnimationDelay) {
1066
          temporaryStyles.push(getCssDelayStyle(delayStyle, true));
1067
        }
1068
      }
1069
1070
      // we need to recalculate the delay value since we used a pre-emptive negative
1071
      // delay value and the delay value is required for the final event checking. This
1072
      // property will ensure that this will happen after the RAF phase has passed.
1073
      if (options.duration == null && timings.transitionDuration > 0) {
1074
        flags.recalculateTimingStyles = flags.recalculateTimingStyles || isFirst;
1075
      }
1076
1077
      maxDelayTime = maxDelay * ONE_SECOND;
1078
      maxDurationTime = maxDuration * ONE_SECOND;
1079
      if (!options.skipBlocking) {
1080
        flags.blockTransition = timings.transitionDuration > 0;
1081
        flags.blockKeyframeAnimation = timings.animationDuration > 0 &&
1082
                                       stagger.animationDelay > 0 &&
1083
                                       stagger.animationDuration === 0;
1084
      }
1085
1086
      if (options.from) {
1087
        if (options.cleanupStyles) {
1088
          registerRestorableStyles(restoreStyles, node, Object.keys(options.from));
1089
        }
1090
        applyAnimationFromStyles(element, options);
1091
      }
1092
1093
      if (flags.blockTransition || flags.blockKeyframeAnimation) {
1094
        applyBlocking(maxDuration);
1095
      } else if (!options.skipBlocking) {
1096
        blockTransitions(node, false);
1097
      }
1098
1099
      // TODO(matsko): for 1.5 change this code to have an animator object for better debugging
1100
      return {
1101
        $$willAnimate: true,
1102
        end: endFn,
1103
        start: function() {
1104
          if (animationClosed) return;
1105
1106
          runnerHost = {
1107
            end: endFn,
1108
            cancel: cancelFn,
1109
            resume: null, //this will be set during the start() phase
1110
            pause: null
1111
          };
1112
1113
          runner = new $$AnimateRunner(runnerHost);
1114
1115
          waitUntilQuiet(start);
1116
1117
          // we don't have access to pause/resume the animation
1118
          // since it hasn't run yet. AnimateRunner will therefore
1119
          // set noop functions for resume and pause and they will
1120
          // later be overridden once the animation is triggered
1121
          return runner;
1122
        }
1123
      };
1124
1125
      function endFn() {
1126
        close();
1127
      }
1128
1129
      function cancelFn() {
1130
        close(true);
1131
      }
1132
1133
      function close(rejected) { // jshint ignore:line
1134
        // if the promise has been called already then we shouldn't close
1135
        // the animation again
1136
        if (animationClosed || (animationCompleted && animationPaused)) return;
1137
        animationClosed = true;
1138
        animationPaused = false;
1139
1140
        if (!options.$$skipPreparationClasses) {
1141
          $$jqLite.removeClass(element, preparationClasses);
1142
        }
1143
        $$jqLite.removeClass(element, activeClasses);
1144
1145
        blockKeyframeAnimations(node, false);
1146
        blockTransitions(node, false);
1147
1148
        forEach(temporaryStyles, function(entry) {
1149
          // There is only one way to remove inline style properties entirely from elements.
1150
          // By using `removeProperty` this works, but we need to convert camel-cased CSS
1151
          // styles down to hyphenated values.
1152
          node.style[entry[0]] = '';
1153
        });
1154
1155
        applyAnimationClasses(element, options);
1156
        applyAnimationStyles(element, options);
1157
1158
        if (Object.keys(restoreStyles).length) {
1159
          forEach(restoreStyles, function(value, prop) {
1160
            value ? node.style.setProperty(prop, value)
1161
                  : node.style.removeProperty(prop);
1162
          });
1163
        }
1164
1165
        // the reason why we have this option is to allow a synchronous closing callback
1166
        // that is fired as SOON as the animation ends (when the CSS is removed) or if
1167
        // the animation never takes off at all. A good example is a leave animation since
1168
        // the element must be removed just after the animation is over or else the element
1169
        // will appear on screen for one animation frame causing an overbearing flicker.
1170
        if (options.onDone) {
1171
          options.onDone();
1172
        }
1173
1174
        // if the preparation function fails then the promise is not setup
1175
        if (runner) {
1176
          runner.complete(!rejected);
1177
        }
1178
      }
1179
1180
      function applyBlocking(duration) {
1181
        if (flags.blockTransition) {
1182
          blockTransitions(node, duration);
1183
        }
1184
1185
        if (flags.blockKeyframeAnimation) {
1186
          blockKeyframeAnimations(node, !!duration);
1187
        }
1188
      }
1189
1190
      function closeAndReturnNoopAnimator() {
1191
        runner = new $$AnimateRunner({
1192
          end: endFn,
1193
          cancel: cancelFn
1194
        });
1195
1196
        // should flush the cache animation
1197
        waitUntilQuiet(noop);
1198
        close();
1199
1200
        return {
1201
          $$willAnimate: false,
1202
          start: function() {
1203
            return runner;
1204
          },
1205
          end: endFn
1206
        };
1207
      }
1208
1209
      function start() {
1210
        if (animationClosed) return;
1211
        if (!node.parentNode) {
1212
          close();
1213
          return;
1214
        }
1215
1216
        var startTime, events = [];
1217
1218
        // even though we only pause keyframe animations here the pause flag
1219
        // will still happen when transitions are used. Only the transition will
1220
        // not be paused since that is not possible. If the animation ends when
1221
        // paused then it will not complete until unpaused or cancelled.
1222
        var playPause = function(playAnimation) {
1223
          if (!animationCompleted) {
1224
            animationPaused = !playAnimation;
1225
            if (timings.animationDuration) {
1226
              var value = blockKeyframeAnimations(node, animationPaused);
1227
              animationPaused
1228
                  ? temporaryStyles.push(value)
1229
                  : removeFromArray(temporaryStyles, value);
1230
            }
1231
          } else if (animationPaused && playAnimation) {
1232
            animationPaused = false;
1233
            close();
1234
          }
1235
        };
1236
1237
        // checking the stagger duration prevents an accidently cascade of the CSS delay style
1238
        // being inherited from the parent. If the transition duration is zero then we can safely
1239
        // rely that the delay value is an intential stagger delay style.
1240
        var maxStagger = itemIndex > 0
1241
                         && ((timings.transitionDuration && stagger.transitionDuration === 0) ||
1242
                            (timings.animationDuration && stagger.animationDuration === 0))
1243
                         && Math.max(stagger.animationDelay, stagger.transitionDelay);
1244
        if (maxStagger) {
1245
          $timeout(triggerAnimationStart,
1246
                   Math.floor(maxStagger * itemIndex * ONE_SECOND),
1247
                   false);
1248
        } else {
1249
          triggerAnimationStart();
1250
        }
1251
1252
        // this will decorate the existing promise runner with pause/resume methods
1253
        runnerHost.resume = function() {
1254
          playPause(true);
1255
        };
1256
1257
        runnerHost.pause = function() {
1258
          playPause(false);
1259
        };
1260
1261
        function triggerAnimationStart() {
1262
          // just incase a stagger animation kicks in when the animation
1263
          // itself was cancelled entirely
1264
          if (animationClosed) return;
1265
1266
          applyBlocking(false);
1267
1268
          forEach(temporaryStyles, function(entry) {
1269
            var key = entry[0];
1270
            var value = entry[1];
1271
            node.style[key] = value;
1272
          });
1273
1274
          applyAnimationClasses(element, options);
1275
          $$jqLite.addClass(element, activeClasses);
1276
1277
          if (flags.recalculateTimingStyles) {
1278
            fullClassName = node.className + ' ' + preparationClasses;
1279
            cacheKey = gcsHashFn(node, fullClassName);
1280
1281
            timings = computeTimings(node, fullClassName, cacheKey);
1282
            relativeDelay = timings.maxDelay;
1283
            maxDelay = Math.max(relativeDelay, 0);
1284
            maxDuration = timings.maxDuration;
1285
1286
            if (maxDuration === 0) {
1287
              close();
1288
              return;
1289
            }
1290
1291
            flags.hasTransitions = timings.transitionDuration > 0;
1292
            flags.hasAnimations = timings.animationDuration > 0;
1293
          }
1294
1295
          if (flags.applyAnimationDelay) {
1296
            relativeDelay = typeof options.delay !== "boolean" && truthyTimingValue(options.delay)
1297
                  ? parseFloat(options.delay)
1298
                  : relativeDelay;
1299
1300
            maxDelay = Math.max(relativeDelay, 0);
1301
            timings.animationDelay = relativeDelay;
1302
            delayStyle = getCssDelayStyle(relativeDelay, true);
1303
            temporaryStyles.push(delayStyle);
1304
            node.style[delayStyle[0]] = delayStyle[1];
1305
          }
1306
1307
          maxDelayTime = maxDelay * ONE_SECOND;
1308
          maxDurationTime = maxDuration * ONE_SECOND;
1309
1310
          if (options.easing) {
1311
            var easeProp, easeVal = options.easing;
1312
            if (flags.hasTransitions) {
1313
              easeProp = TRANSITION_PROP + TIMING_KEY;
1314
              temporaryStyles.push([easeProp, easeVal]);
1315
              node.style[easeProp] = easeVal;
1316
            }
1317
            if (flags.hasAnimations) {
1318
              easeProp = ANIMATION_PROP + TIMING_KEY;
1319
              temporaryStyles.push([easeProp, easeVal]);
1320
              node.style[easeProp] = easeVal;
1321
            }
1322
          }
1323
1324
          if (timings.transitionDuration) {
1325
            events.push(TRANSITIONEND_EVENT);
1326
          }
1327
1328
          if (timings.animationDuration) {
1329
            events.push(ANIMATIONEND_EVENT);
1330
          }
1331
1332
          startTime = Date.now();
1333
          var timerTime = maxDelayTime + CLOSING_TIME_BUFFER * maxDurationTime;
1334
          var endTime = startTime + timerTime;
1335
1336
          var animationsData = element.data(ANIMATE_TIMER_KEY) || [];
1337
          var setupFallbackTimer = true;
1338
          if (animationsData.length) {
1339
            var currentTimerData = animationsData[0];
1340
            setupFallbackTimer = endTime > currentTimerData.expectedEndTime;
1341
            if (setupFallbackTimer) {
1342
              $timeout.cancel(currentTimerData.timer);
1343
            } else {
1344
              animationsData.push(close);
1345
            }
1346
          }
1347
1348
          if (setupFallbackTimer) {
1349
            var timer = $timeout(onAnimationExpired, timerTime, false);
1350
            animationsData[0] = {
1351
              timer: timer,
1352
              expectedEndTime: endTime
1353
            };
1354
            animationsData.push(close);
1355
            element.data(ANIMATE_TIMER_KEY, animationsData);
1356
          }
1357
1358
          element.on(events.join(' '), onAnimationProgress);
1359
          if (options.to) {
1360
            if (options.cleanupStyles) {
1361
              registerRestorableStyles(restoreStyles, node, Object.keys(options.to));
1362
            }
1363
            applyAnimationToStyles(element, options);
1364
          }
1365
        }
1366
1367
        function onAnimationExpired() {
1368
          var animationsData = element.data(ANIMATE_TIMER_KEY);
1369
1370
          // this will be false in the event that the element was
1371
          // removed from the DOM (via a leave animation or something
1372
          // similar)
1373
          if (animationsData) {
1374
            for (var i = 1; i < animationsData.length; i++) {
1375
              animationsData[i]();
1376
            }
1377
            element.removeData(ANIMATE_TIMER_KEY);
1378
          }
1379
        }
1380
1381
        function onAnimationProgress(event) {
1382
          event.stopPropagation();
1383
          var ev = event.originalEvent || event;
1384
          var timeStamp = ev.$manualTimeStamp || ev.timeStamp || Date.now();
1385
1386
          /* Firefox (or possibly just Gecko) likes to not round values up
1387
           * when a ms measurement is used for the animation */
1388
          var elapsedTime = parseFloat(ev.elapsedTime.toFixed(ELAPSED_TIME_MAX_DECIMAL_PLACES));
1389
1390
          /* $manualTimeStamp is a mocked timeStamp value which is set
1391
           * within browserTrigger(). This is only here so that tests can
1392
           * mock animations properly. Real events fallback to event.timeStamp,
1393
           * or, if they don't, then a timeStamp is automatically created for them.
1394
           * We're checking to see if the timeStamp surpasses the expected delay,
1395
           * but we're using elapsedTime instead of the timeStamp on the 2nd
1396
           * pre-condition since animations sometimes close off early */
1397
          if (Math.max(timeStamp - startTime, 0) >= maxDelayTime && elapsedTime >= maxDuration) {
1398
            // we set this flag to ensure that if the transition is paused then, when resumed,
1399
            // the animation will automatically close itself since transitions cannot be paused.
1400
            animationCompleted = true;
1401
            close();
1402
          }
1403
        }
1404
      }
1405
    };
1406
  }];
1407
}];
1408
1409
var $$AnimateCssDriverProvider = ['$$animationProvider', function($$animationProvider) {
1410
  $$animationProvider.drivers.push('$$animateCssDriver');
1411
1412
  var NG_ANIMATE_SHIM_CLASS_NAME = 'ng-animate-shim';
1413
  var NG_ANIMATE_ANCHOR_CLASS_NAME = 'ng-anchor';
1414
1415
  var NG_OUT_ANCHOR_CLASS_NAME = 'ng-anchor-out';
1416
  var NG_IN_ANCHOR_CLASS_NAME = 'ng-anchor-in';
1417
1418
  function isDocumentFragment(node) {
1419
    return node.parentNode && node.parentNode.nodeType === 11;
1420
  }
1421
1422
  this.$get = ['$animateCss', '$rootScope', '$$AnimateRunner', '$rootElement', '$sniffer', '$$jqLite', '$document',
1423
       function($animateCss,   $rootScope,   $$AnimateRunner,   $rootElement,   $sniffer,   $$jqLite,   $document) {
1424
1425
    // only browsers that support these properties can render animations
1426
    if (!$sniffer.animations && !$sniffer.transitions) return noop;
1427
1428
    var bodyNode = $document[0].body;
1429
    var rootNode = getDomNode($rootElement);
1430
1431
    var rootBodyElement = jqLite(
1432
      // this is to avoid using something that exists outside of the body
1433
      // we also special case the doc fragement case because our unit test code
1434
      // appends the $rootElement to the body after the app has been bootstrapped
1435
      isDocumentFragment(rootNode) || bodyNode.contains(rootNode) ? rootNode : bodyNode
1436
    );
1437
1438
    var applyAnimationClasses = applyAnimationClassesFactory($$jqLite);
1439
1440
    return function initDriverFn(animationDetails) {
1441
      return animationDetails.from && animationDetails.to
1442
          ? prepareFromToAnchorAnimation(animationDetails.from,
1443
                                         animationDetails.to,
1444
                                         animationDetails.classes,
1445
                                         animationDetails.anchors)
1446
          : prepareRegularAnimation(animationDetails);
1447
    };
1448
1449
    function filterCssClasses(classes) {
1450
      //remove all the `ng-` stuff
1451
      return classes.replace(/\bng-\S+\b/g, '');
1452
    }
1453
1454
    function getUniqueValues(a, b) {
1455
      if (isString(a)) a = a.split(' ');
1456
      if (isString(b)) b = b.split(' ');
1457
      return a.filter(function(val) {
1458
        return b.indexOf(val) === -1;
1459
      }).join(' ');
1460
    }
1461
1462
    function prepareAnchoredAnimation(classes, outAnchor, inAnchor) {
1463
      var clone = jqLite(getDomNode(outAnchor).cloneNode(true));
1464
      var startingClasses = filterCssClasses(getClassVal(clone));
1465
1466
      outAnchor.addClass(NG_ANIMATE_SHIM_CLASS_NAME);
1467
      inAnchor.addClass(NG_ANIMATE_SHIM_CLASS_NAME);
1468
1469
      clone.addClass(NG_ANIMATE_ANCHOR_CLASS_NAME);
1470
1471
      rootBodyElement.append(clone);
1472
1473
      var animatorIn, animatorOut = prepareOutAnimation();
1474
1475
      // the user may not end up using the `out` animation and
1476
      // only making use of the `in` animation or vice-versa.
1477
      // In either case we should allow this and not assume the
1478
      // animation is over unless both animations are not used.
1479
      if (!animatorOut) {
1480
        animatorIn = prepareInAnimation();
1481
        if (!animatorIn) {
1482
          return end();
1483
        }
1484
      }
1485
1486
      var startingAnimator = animatorOut || animatorIn;
1487
1488
      return {
1489
        start: function() {
1490
          var runner;
1491
1492
          var currentAnimation = startingAnimator.start();
1493
          currentAnimation.done(function() {
1494
            currentAnimation = null;
1495
            if (!animatorIn) {
1496
              animatorIn = prepareInAnimation();
1497
              if (animatorIn) {
1498
                currentAnimation = animatorIn.start();
1499
                currentAnimation.done(function() {
1500
                  currentAnimation = null;
1501
                  end();
1502
                  runner.complete();
1503
                });
1504
                return currentAnimation;
1505
              }
1506
            }
1507
            // in the event that there is no `in` animation
1508
            end();
1509
            runner.complete();
1510
          });
1511
1512
          runner = new $$AnimateRunner({
1513
            end: endFn,
1514
            cancel: endFn
1515
          });
1516
1517
          return runner;
1518
1519
          function endFn() {
1520
            if (currentAnimation) {
1521
              currentAnimation.end();
1522
            }
1523
          }
1524
        }
1525
      };
1526
1527
      function calculateAnchorStyles(anchor) {
1528
        var styles = {};
1529
1530
        var coords = getDomNode(anchor).getBoundingClientRect();
1531
1532
        // we iterate directly since safari messes up and doesn't return
1533
        // all the keys for the coods object when iterated
1534
        forEach(['width','height','top','left'], function(key) {
1535
          var value = coords[key];
1536
          switch (key) {
1537
            case 'top':
1538
              value += bodyNode.scrollTop;
1539
              break;
1540
            case 'left':
1541
              value += bodyNode.scrollLeft;
1542
              break;
1543
          }
1544
          styles[key] = Math.floor(value) + 'px';
1545
        });
1546
        return styles;
1547
      }
1548
1549
      function prepareOutAnimation() {
1550
        var animator = $animateCss(clone, {
1551
          addClass: NG_OUT_ANCHOR_CLASS_NAME,
1552
          delay: true,
1553
          from: calculateAnchorStyles(outAnchor)
1554
        });
1555
1556
        // read the comment within `prepareRegularAnimation` to understand
1557
        // why this check is necessary
1558
        return animator.$$willAnimate ? animator : null;
1559
      }
1560
1561
      function getClassVal(element) {
1562
        return element.attr('class') || '';
1563
      }
1564
1565
      function prepareInAnimation() {
1566
        var endingClasses = filterCssClasses(getClassVal(inAnchor));
1567
        var toAdd = getUniqueValues(endingClasses, startingClasses);
1568
        var toRemove = getUniqueValues(startingClasses, endingClasses);
1569
1570
        var animator = $animateCss(clone, {
1571
          to: calculateAnchorStyles(inAnchor),
1572
          addClass: NG_IN_ANCHOR_CLASS_NAME + ' ' + toAdd,
1573
          removeClass: NG_OUT_ANCHOR_CLASS_NAME + ' ' + toRemove,
1574
          delay: true
1575
        });
1576
1577
        // read the comment within `prepareRegularAnimation` to understand
1578
        // why this check is necessary
1579
        return animator.$$willAnimate ? animator : null;
1580
      }
1581
1582
      function end() {
1583
        clone.remove();
1584
        outAnchor.removeClass(NG_ANIMATE_SHIM_CLASS_NAME);
1585
        inAnchor.removeClass(NG_ANIMATE_SHIM_CLASS_NAME);
1586
      }
1587
    }
1588
1589
    function prepareFromToAnchorAnimation(from, to, classes, anchors) {
1590
      var fromAnimation = prepareRegularAnimation(from, noop);
1591
      var toAnimation = prepareRegularAnimation(to, noop);
1592
1593
      var anchorAnimations = [];
1594
      forEach(anchors, function(anchor) {
1595
        var outElement = anchor['out'];
1596
        var inElement = anchor['in'];
1597
        var animator = prepareAnchoredAnimation(classes, outElement, inElement);
1598
        if (animator) {
1599
          anchorAnimations.push(animator);
1600
        }
1601
      });
1602
1603
      // no point in doing anything when there are no elements to animate
1604
      if (!fromAnimation && !toAnimation && anchorAnimations.length === 0) return;
1605
1606
      return {
1607
        start: function() {
1608
          var animationRunners = [];
1609
1610
          if (fromAnimation) {
1611
            animationRunners.push(fromAnimation.start());
1612
          }
1613
1614
          if (toAnimation) {
1615
            animationRunners.push(toAnimation.start());
1616
          }
1617
1618
          forEach(anchorAnimations, function(animation) {
1619
            animationRunners.push(animation.start());
1620
          });
1621
1622
          var runner = new $$AnimateRunner({
1623
            end: endFn,
1624
            cancel: endFn // CSS-driven animations cannot be cancelled, only ended
1625
          });
1626
1627
          $$AnimateRunner.all(animationRunners, function(status) {
1628
            runner.complete(status);
1629
          });
1630
1631
          return runner;
1632
1633
          function endFn() {
1634
            forEach(animationRunners, function(runner) {
1635
              runner.end();
1636
            });
1637
          }
1638
        }
1639
      };
1640
    }
1641
1642
    function prepareRegularAnimation(animationDetails) {
1643
      var element = animationDetails.element;
1644
      var options = animationDetails.options || {};
1645
1646
      if (animationDetails.structural) {
1647
        options.event = animationDetails.event;
1648
        options.structural = true;
1649
        options.applyClassesEarly = true;
1650
1651
        // we special case the leave animation since we want to ensure that
1652
        // the element is removed as soon as the animation is over. Otherwise
1653
        // a flicker might appear or the element may not be removed at all
1654
        if (animationDetails.event === 'leave') {
1655
          options.onDone = options.domOperation;
1656
        }
1657
      }
1658
1659
      // We assign the preparationClasses as the actual animation event since
1660
      // the internals of $animateCss will just suffix the event token values
1661
      // with `-active` to trigger the animation.
1662
      if (options.preparationClasses) {
1663
        options.event = concatWithSpace(options.event, options.preparationClasses);
1664
      }
1665
1666
      var animator = $animateCss(element, options);
1667
1668
      // the driver lookup code inside of $$animation attempts to spawn a
1669
      // driver one by one until a driver returns a.$$willAnimate animator object.
1670
      // $animateCss will always return an object, however, it will pass in
1671
      // a flag as a hint as to whether an animation was detected or not
1672
      return animator.$$willAnimate ? animator : null;
1673
    }
1674
  }];
1675
}];
1676
1677
// TODO(matsko): use caching here to speed things up for detection
1678
// TODO(matsko): add documentation
1679
//  by the time...
1680
1681
var $$AnimateJsProvider = ['$animateProvider', function($animateProvider) {
1682
  this.$get = ['$injector', '$$AnimateRunner', '$$jqLite',
1683
       function($injector,   $$AnimateRunner,   $$jqLite) {
1684
1685
    var applyAnimationClasses = applyAnimationClassesFactory($$jqLite);
1686
         // $animateJs(element, 'enter');
1687
    return function(element, event, classes, options) {
1688
      // the `classes` argument is optional and if it is not used
1689
      // then the classes will be resolved from the element's className
1690
      // property as well as options.addClass/options.removeClass.
1691
      if (arguments.length === 3 && isObject(classes)) {
1692
        options = classes;
1693
        classes = null;
1694
      }
1695
1696
      options = prepareAnimationOptions(options);
1697
      if (!classes) {
1698
        classes = element.attr('class') || '';
1699
        if (options.addClass) {
1700
          classes += ' ' + options.addClass;
1701
        }
1702
        if (options.removeClass) {
1703
          classes += ' ' + options.removeClass;
1704
        }
1705
      }
1706
1707
      var classesToAdd = options.addClass;
1708
      var classesToRemove = options.removeClass;
1709
1710
      // the lookupAnimations function returns a series of animation objects that are
1711
      // matched up with one or more of the CSS classes. These animation objects are
1712
      // defined via the module.animation factory function. If nothing is detected then
1713
      // we don't return anything which then makes $animation query the next driver.
1714
      var animations = lookupAnimations(classes);
1715
      var before, after;
1716
      if (animations.length) {
1717
        var afterFn, beforeFn;
1718
        if (event == 'leave') {
1719
          beforeFn = 'leave';
1720
          afterFn = 'afterLeave'; // TODO(matsko): get rid of this
1721
        } else {
1722
          beforeFn = 'before' + event.charAt(0).toUpperCase() + event.substr(1);
1723
          afterFn = event;
1724
        }
1725
1726
        if (event !== 'enter' && event !== 'move') {
1727
          before = packageAnimations(element, event, options, animations, beforeFn);
1728
        }
1729
        after  = packageAnimations(element, event, options, animations, afterFn);
1730
      }
1731
1732
      // no matching animations
1733
      if (!before && !after) return;
1734
1735
      function applyOptions() {
1736
        options.domOperation();
1737
        applyAnimationClasses(element, options);
1738
      }
1739
1740
      return {
1741
        start: function() {
1742
          var closeActiveAnimations;
1743
          var chain = [];
1744
1745
          if (before) {
1746
            chain.push(function(fn) {
1747
              closeActiveAnimations = before(fn);
1748
            });
1749
          }
1750
1751
          if (chain.length) {
1752
            chain.push(function(fn) {
1753
              applyOptions();
1754
              fn(true);
1755
            });
1756
          } else {
1757
            applyOptions();
1758
          }
1759
1760
          if (after) {
1761
            chain.push(function(fn) {
1762
              closeActiveAnimations = after(fn);
1763
            });
1764
          }
1765
1766
          var animationClosed = false;
1767
          var runner = new $$AnimateRunner({
1768
            end: function() {
1769
              endAnimations();
1770
            },
1771
            cancel: function() {
1772
              endAnimations(true);
1773
            }
1774
          });
1775
1776
          $$AnimateRunner.chain(chain, onComplete);
1777
          return runner;
1778
1779
          function onComplete(success) {
1780
            animationClosed = true;
1781
            applyOptions();
1782
            applyAnimationStyles(element, options);
1783
            runner.complete(success);
1784
          }
1785
1786
          function endAnimations(cancelled) {
1787
            if (!animationClosed) {
1788
              (closeActiveAnimations || noop)(cancelled);
1789
              onComplete(cancelled);
1790
            }
1791
          }
1792
        }
1793
      };
1794
1795
      function executeAnimationFn(fn, element, event, options, onDone) {
1796
        var args;
1797
        switch (event) {
1798
          case 'animate':
1799
            args = [element, options.from, options.to, onDone];
1800
            break;
1801
1802
          case 'setClass':
1803
            args = [element, classesToAdd, classesToRemove, onDone];
1804
            break;
1805
1806
          case 'addClass':
1807
            args = [element, classesToAdd, onDone];
1808
            break;
1809
1810
          case 'removeClass':
1811
            args = [element, classesToRemove, onDone];
1812
            break;
1813
1814
          default:
1815
            args = [element, onDone];
1816
            break;
1817
        }
1818
1819
        args.push(options);
1820
1821
        var value = fn.apply(fn, args);
1822
        if (value) {
1823
          if (isFunction(value.start)) {
1824
            value = value.start();
1825
          }
1826
1827
          if (value instanceof $$AnimateRunner) {
1828
            value.done(onDone);
1829
          } else if (isFunction(value)) {
1830
            // optional onEnd / onCancel callback
1831
            return value;
1832
          }
1833
        }
1834
1835
        return noop;
1836
      }
1837
1838
      function groupEventedAnimations(element, event, options, animations, fnName) {
1839
        var operations = [];
1840
        forEach(animations, function(ani) {
1841
          var animation = ani[fnName];
1842
          if (!animation) return;
1843
1844
          // note that all of these animations will run in parallel
1845
          operations.push(function() {
1846
            var runner;
1847
            var endProgressCb;
1848
1849
            var resolved = false;
1850
            var onAnimationComplete = function(rejected) {
1851
              if (!resolved) {
1852
                resolved = true;
1853
                (endProgressCb || noop)(rejected);
1854
                runner.complete(!rejected);
1855
              }
1856
            };
1857
1858
            runner = new $$AnimateRunner({
1859
              end: function() {
1860
                onAnimationComplete();
1861
              },
1862
              cancel: function() {
1863
                onAnimationComplete(true);
1864
              }
1865
            });
1866
1867
            endProgressCb = executeAnimationFn(animation, element, event, options, function(result) {
1868
              var cancelled = result === false;
1869
              onAnimationComplete(cancelled);
1870
            });
1871
1872
            return runner;
1873
          });
1874
        });
1875
1876
        return operations;
1877
      }
1878
1879
      function packageAnimations(element, event, options, animations, fnName) {
1880
        var operations = groupEventedAnimations(element, event, options, animations, fnName);
1881
        if (operations.length === 0) {
1882
          var a,b;
1883
          if (fnName === 'beforeSetClass') {
1884
            a = groupEventedAnimations(element, 'removeClass', options, animations, 'beforeRemoveClass');
1885
            b = groupEventedAnimations(element, 'addClass', options, animations, 'beforeAddClass');
1886
          } else if (fnName === 'setClass') {
1887
            a = groupEventedAnimations(element, 'removeClass', options, animations, 'removeClass');
1888
            b = groupEventedAnimations(element, 'addClass', options, animations, 'addClass');
1889
          }
1890
1891
          if (a) {
1892
            operations = operations.concat(a);
1893
          }
1894
          if (b) {
1895
            operations = operations.concat(b);
1896
          }
1897
        }
1898
1899
        if (operations.length === 0) return;
1900
1901
        // TODO(matsko): add documentation
1902
        return function startAnimation(callback) {
1903
          var runners = [];
1904
          if (operations.length) {
1905
            forEach(operations, function(animateFn) {
1906
              runners.push(animateFn());
1907
            });
1908
          }
1909
1910
          runners.length ? $$AnimateRunner.all(runners, callback) : callback();
1911
1912
          return function endFn(reject) {
1913
            forEach(runners, function(runner) {
1914
              reject ? runner.cancel() : runner.end();
1915
            });
1916
          };
1917
        };
1918
      }
1919
    };
1920
1921
    function lookupAnimations(classes) {
1922
      classes = isArray(classes) ? classes : classes.split(' ');
1923
      var matches = [], flagMap = {};
1924
      for (var i=0; i < classes.length; i++) {
1925
        var klass = classes[i],
1926
            animationFactory = $animateProvider.$$registeredAnimations[klass];
1927
        if (animationFactory && !flagMap[klass]) {
1928
          matches.push($injector.get(animationFactory));
1929
          flagMap[klass] = true;
1930
        }
1931
      }
1932
      return matches;
1933
    }
1934
  }];
1935
}];
1936
1937
var $$AnimateJsDriverProvider = ['$$animationProvider', function($$animationProvider) {
1938
  $$animationProvider.drivers.push('$$animateJsDriver');
1939
  this.$get = ['$$animateJs', '$$AnimateRunner', function($$animateJs, $$AnimateRunner) {
1940
    return function initDriverFn(animationDetails) {
1941
      if (animationDetails.from && animationDetails.to) {
1942
        var fromAnimation = prepareAnimation(animationDetails.from);
1943
        var toAnimation = prepareAnimation(animationDetails.to);
1944
        if (!fromAnimation && !toAnimation) return;
1945
1946
        return {
1947
          start: function() {
1948
            var animationRunners = [];
1949
1950
            if (fromAnimation) {
1951
              animationRunners.push(fromAnimation.start());
1952
            }
1953
1954
            if (toAnimation) {
1955
              animationRunners.push(toAnimation.start());
1956
            }
1957
1958
            $$AnimateRunner.all(animationRunners, done);
1959
1960
            var runner = new $$AnimateRunner({
1961
              end: endFnFactory(),
1962
              cancel: endFnFactory()
1963
            });
1964
1965
            return runner;
1966
1967
            function endFnFactory() {
1968
              return function() {
1969
                forEach(animationRunners, function(runner) {
1970
                  // at this point we cannot cancel animations for groups just yet. 1.5+
1971
                  runner.end();
1972
                });
1973
              };
1974
            }
1975
1976
            function done(status) {
1977
              runner.complete(status);
1978
            }
1979
          }
1980
        };
1981
      } else {
1982
        return prepareAnimation(animationDetails);
1983
      }
1984
    };
1985
1986
    function prepareAnimation(animationDetails) {
1987
      // TODO(matsko): make sure to check for grouped animations and delegate down to normal animations
1988
      var element = animationDetails.element;
1989
      var event = animationDetails.event;
1990
      var options = animationDetails.options;
1991
      var classes = animationDetails.classes;
1992
      return $$animateJs(element, event, classes, options);
1993
    }
1994
  }];
1995
}];
1996
1997
var NG_ANIMATE_ATTR_NAME = 'data-ng-animate';
1998
var NG_ANIMATE_PIN_DATA = '$ngAnimatePin';
1999
var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
2000
  var PRE_DIGEST_STATE = 1;
2001
  var RUNNING_STATE = 2;
2002
2003
  var rules = this.rules = {
2004
    skip: [],
2005
    cancel: [],
2006
    join: []
2007
  };
2008
2009
  function isAllowed(ruleType, element, currentAnimation, previousAnimation) {
2010
    return rules[ruleType].some(function(fn) {
2011
      return fn(element, currentAnimation, previousAnimation);
2012
    });
2013
  }
2014
2015
  function hasAnimationClasses(options, and) {
2016
    options = options || {};
2017
    var a = (options.addClass || '').length > 0;
2018
    var b = (options.removeClass || '').length > 0;
2019
    return and ? a && b : a || b;
2020
  }
2021
2022
  rules.join.push(function(element, newAnimation, currentAnimation) {
2023
    // if the new animation is class-based then we can just tack that on
2024
    return !newAnimation.structural && hasAnimationClasses(newAnimation.options);
2025
  });
2026
2027
  rules.skip.push(function(element, newAnimation, currentAnimation) {
2028
    // there is no need to animate anything if no classes are being added and
2029
    // there is no structural animation that will be triggered
2030
    return !newAnimation.structural && !hasAnimationClasses(newAnimation.options);
2031
  });
2032
2033
  rules.skip.push(function(element, newAnimation, currentAnimation) {
2034
    // why should we trigger a new structural animation if the element will
2035
    // be removed from the DOM anyway?
2036
    return currentAnimation.event == 'leave' && newAnimation.structural;
2037
  });
2038
2039
  rules.skip.push(function(element, newAnimation, currentAnimation) {
2040
    // if there is an ongoing current animation then don't even bother running the class-based animation
2041
    return currentAnimation.structural && currentAnimation.state === RUNNING_STATE && !newAnimation.structural;
2042
  });
2043
2044
  rules.cancel.push(function(element, newAnimation, currentAnimation) {
2045
    // there can never be two structural animations running at the same time
2046
    return currentAnimation.structural && newAnimation.structural;
2047
  });
2048
2049
  rules.cancel.push(function(element, newAnimation, currentAnimation) {
2050
    // if the previous animation is already running, but the new animation will
2051
    // be triggered, but the new animation is structural
2052
    return currentAnimation.state === RUNNING_STATE && newAnimation.structural;
2053
  });
2054
2055
  rules.cancel.push(function(element, newAnimation, currentAnimation) {
2056
    var nO = newAnimation.options;
2057
    var cO = currentAnimation.options;
2058
2059
    // if the exact same CSS class is added/removed then it's safe to cancel it
2060
    return (nO.addClass && nO.addClass === cO.removeClass) || (nO.removeClass && nO.removeClass === cO.addClass);
2061
  });
2062
2063
  this.$get = ['$$rAF', '$rootScope', '$rootElement', '$document', '$$HashMap',
2064
               '$$animation', '$$AnimateRunner', '$templateRequest', '$$jqLite', '$$forceReflow',
2065
       function($$rAF,   $rootScope,   $rootElement,   $document,   $$HashMap,
2066
                $$animation,   $$AnimateRunner,   $templateRequest,   $$jqLite,   $$forceReflow) {
2067
2068
    var activeAnimationsLookup = new $$HashMap();
2069
    var disabledElementsLookup = new $$HashMap();
2070
    var animationsEnabled = null;
2071
2072
    function postDigestTaskFactory() {
2073
      var postDigestCalled = false;
2074
      return function(fn) {
2075
        // we only issue a call to postDigest before
2076
        // it has first passed. This prevents any callbacks
2077
        // from not firing once the animation has completed
2078
        // since it will be out of the digest cycle.
2079
        if (postDigestCalled) {
2080
          fn();
2081
        } else {
2082
          $rootScope.$$postDigest(function() {
2083
            postDigestCalled = true;
2084
            fn();
2085
          });
2086
        }
2087
      };
2088
    }
2089
2090
    // Wait until all directive and route-related templates are downloaded and
2091
    // compiled. The $templateRequest.totalPendingRequests variable keeps track of
2092
    // all of the remote templates being currently downloaded. If there are no
2093
    // templates currently downloading then the watcher will still fire anyway.
2094
    var deregisterWatch = $rootScope.$watch(
2095
      function() { return $templateRequest.totalPendingRequests === 0; },
2096
      function(isEmpty) {
2097
        if (!isEmpty) return;
2098
        deregisterWatch();
2099
2100
        // Now that all templates have been downloaded, $animate will wait until
2101
        // the post digest queue is empty before enabling animations. By having two
2102
        // calls to $postDigest calls we can ensure that the flag is enabled at the
2103
        // very end of the post digest queue. Since all of the animations in $animate
2104
        // use $postDigest, it's important that the code below executes at the end.
2105
        // This basically means that the page is fully downloaded and compiled before
2106
        // any animations are triggered.
2107
        $rootScope.$$postDigest(function() {
2108
          $rootScope.$$postDigest(function() {
2109
            // we check for null directly in the event that the application already called
2110
            // .enabled() with whatever arguments that it provided it with
2111
            if (animationsEnabled === null) {
2112
              animationsEnabled = true;
2113
            }
2114
          });
2115
        });
2116
      }
2117
    );
2118
2119
    var callbackRegistry = {};
2120
2121
    // remember that the classNameFilter is set during the provider/config
2122
    // stage therefore we can optimize here and setup a helper function
2123
    var classNameFilter = $animateProvider.classNameFilter();
2124
    var isAnimatableClassName = !classNameFilter
2125
              ? function() { return true; }
2126
              : function(className) {
2127
                return classNameFilter.test(className);
2128
              };
2129
2130
    var applyAnimationClasses = applyAnimationClassesFactory($$jqLite);
2131
2132
    function normalizeAnimationOptions(element, options) {
2133
      return mergeAnimationOptions(element, options, {});
2134
    }
2135
2136
    function findCallbacks(element, event) {
2137
      var targetNode = getDomNode(element);
2138
2139
      var matches = [];
2140
      var entries = callbackRegistry[event];
2141
      if (entries) {
2142
        forEach(entries, function(entry) {
2143
          if (entry.node.contains(targetNode)) {
2144
            matches.push(entry.callback);
2145
          }
2146
        });
2147
      }
2148
2149
      return matches;
2150
    }
2151
2152
    return {
2153
      on: function(event, container, callback) {
2154
        var node = extractElementNode(container);
2155
        callbackRegistry[event] = callbackRegistry[event] || [];
2156
        callbackRegistry[event].push({
2157
          node: node,
2158
          callback: callback
2159
        });
2160
      },
2161
2162
      off: function(event, container, callback) {
2163
        var entries = callbackRegistry[event];
2164
        if (!entries) return;
2165
2166
        callbackRegistry[event] = arguments.length === 1
2167
            ? null
2168
            : filterFromRegistry(entries, container, callback);
2169
2170
        function filterFromRegistry(list, matchContainer, matchCallback) {
2171
          var containerNode = extractElementNode(matchContainer);
2172
          return list.filter(function(entry) {
2173
            var isMatch = entry.node === containerNode &&
2174
                            (!matchCallback || entry.callback === matchCallback);
2175
            return !isMatch;
2176
          });
2177
        }
2178
      },
2179
2180
      pin: function(element, parentElement) {
2181
        assertArg(isElement(element), 'element', 'not an element');
2182
        assertArg(isElement(parentElement), 'parentElement', 'not an element');
2183
        element.data(NG_ANIMATE_PIN_DATA, parentElement);
2184
      },
2185
2186
      push: function(element, event, options, domOperation) {
2187
        options = options || {};
2188
        options.domOperation = domOperation;
2189
        return queueAnimation(element, event, options);
2190
      },
2191
2192
      // this method has four signatures:
2193
      //  () - global getter
2194
      //  (bool) - global setter
2195
      //  (element) - element getter
2196
      //  (element, bool) - element setter<F37>
2197
      enabled: function(element, bool) {
2198
        var argCount = arguments.length;
2199
2200
        if (argCount === 0) {
2201
          // () - Global getter
2202
          bool = !!animationsEnabled;
2203
        } else {
2204
          var hasElement = isElement(element);
2205
2206
          if (!hasElement) {
2207
            // (bool) - Global setter
2208
            bool = animationsEnabled = !!element;
2209
          } else {
2210
            var node = getDomNode(element);
2211
            var recordExists = disabledElementsLookup.get(node);
2212
2213
            if (argCount === 1) {
2214
              // (element) - Element getter
2215
              bool = !recordExists;
2216
            } else {
2217
              // (element, bool) - Element setter
2218
              bool = !!bool;
2219
              if (!bool) {
2220
                disabledElementsLookup.put(node, true);
2221
              } else if (recordExists) {
2222
                disabledElementsLookup.remove(node);
2223
              }
2224
            }
2225
          }
2226
        }
2227
2228
        return bool;
2229
      }
2230
    };
2231
2232
    function queueAnimation(element, event, options) {
2233
      var node, parent;
2234
      element = stripCommentsFromElement(element);
2235
      if (element) {
2236
        node = getDomNode(element);
2237
        parent = element.parent();
2238
      }
2239
2240
      options = prepareAnimationOptions(options);
2241
2242
      // we create a fake runner with a working promise.
2243
      // These methods will become available after the digest has passed
2244
      var runner = new $$AnimateRunner();
2245
2246
      // this is used to trigger callbacks in postDigest mode
2247
      var runInNextPostDigestOrNow = postDigestTaskFactory();
2248
2249
      if (isArray(options.addClass)) {
2250
        options.addClass = options.addClass.join(' ');
2251
      }
2252
2253
      if (options.addClass && !isString(options.addClass)) {
2254
        options.addClass = null;
2255
      }
2256
2257
      if (isArray(options.removeClass)) {
2258
        options.removeClass = options.removeClass.join(' ');
2259
      }
2260
2261
      if (options.removeClass && !isString(options.removeClass)) {
2262
        options.removeClass = null;
2263
      }
2264
2265
      if (options.from && !isObject(options.from)) {
2266
        options.from = null;
2267
      }
2268
2269
      if (options.to && !isObject(options.to)) {
2270
        options.to = null;
2271
      }
2272
2273
      // there are situations where a directive issues an animation for
2274
      // a jqLite wrapper that contains only comment nodes... If this
2275
      // happens then there is no way we can perform an animation
2276
      if (!node) {
2277
        close();
2278
        return runner;
2279
      }
2280
2281
      var className = [node.className, options.addClass, options.removeClass].join(' ');
2282
      if (!isAnimatableClassName(className)) {
2283
        close();
2284
        return runner;
2285
      }
2286
2287
      var isStructural = ['enter', 'move', 'leave'].indexOf(event) >= 0;
2288
2289
      // this is a hard disable of all animations for the application or on
2290
      // the element itself, therefore  there is no need to continue further
2291
      // past this point if not enabled
2292
      var skipAnimations = !animationsEnabled || disabledElementsLookup.get(node);
2293
      var existingAnimation = (!skipAnimations && activeAnimationsLookup.get(node)) || {};
2294
      var hasExistingAnimation = !!existingAnimation.state;
2295
2296
      // there is no point in traversing the same collection of parent ancestors if a followup
2297
      // animation will be run on the same element that already did all that checking work
2298
      if (!skipAnimations && (!hasExistingAnimation || existingAnimation.state != PRE_DIGEST_STATE)) {
2299
        skipAnimations = !areAnimationsAllowed(element, parent, event);
2300
      }
2301
2302
      if (skipAnimations) {
2303
        close();
2304
        return runner;
2305
      }
2306
2307
      if (isStructural) {
2308
        closeChildAnimations(element);
2309
      }
2310
2311
      var newAnimation = {
2312
        structural: isStructural,
2313
        element: element,
2314
        event: event,
2315
        close: close,
2316
        options: options,
2317
        runner: runner
2318
      };
2319
2320
      if (hasExistingAnimation) {
2321
        var skipAnimationFlag = isAllowed('skip', element, newAnimation, existingAnimation);
2322
        if (skipAnimationFlag) {
2323
          if (existingAnimation.state === RUNNING_STATE) {
2324
            close();
2325
            return runner;
2326
          } else {
2327
            mergeAnimationOptions(element, existingAnimation.options, options);
2328
            return existingAnimation.runner;
2329
          }
2330
        }
2331
2332
        var cancelAnimationFlag = isAllowed('cancel', element, newAnimation, existingAnimation);
2333
        if (cancelAnimationFlag) {
2334
          if (existingAnimation.state === RUNNING_STATE) {
2335
            // this will end the animation right away and it is safe
2336
            // to do so since the animation is already running and the
2337
            // runner callback code will run in async
2338
            existingAnimation.runner.end();
2339
          } else if (existingAnimation.structural) {
2340
            // this means that the animation is queued into a digest, but
2341
            // hasn't started yet. Therefore it is safe to run the close
2342
            // method which will call the runner methods in async.
2343
            existingAnimation.close();
2344
          } else {
2345
            // this will merge the new animation options into existing animation options
2346
            mergeAnimationOptions(element, existingAnimation.options, newAnimation.options);
2347
            return existingAnimation.runner;
2348
          }
2349
        } else {
2350
          // a joined animation means that this animation will take over the existing one
2351
          // so an example would involve a leave animation taking over an enter. Then when
2352
          // the postDigest kicks in the enter will be ignored.
2353
          var joinAnimationFlag = isAllowed('join', element, newAnimation, existingAnimation);
2354
          if (joinAnimationFlag) {
2355
            if (existingAnimation.state === RUNNING_STATE) {
2356
              normalizeAnimationOptions(element, options);
2357
            } else {
2358
              applyGeneratedPreparationClasses(element, isStructural ? event : null, options);
2359
2360
              event = newAnimation.event = existingAnimation.event;
2361
              options = mergeAnimationOptions(element, existingAnimation.options, newAnimation.options);
2362
2363
              //we return the same runner since only the option values of this animation will
2364
              //be fed into the `existingAnimation`.
2365
              return existingAnimation.runner;
2366
            }
2367
          }
2368
        }
2369
      } else {
2370
        // normalization in this case means that it removes redundant CSS classes that
2371
        // already exist (addClass) or do not exist (removeClass) on the element
2372
        normalizeAnimationOptions(element, options);
2373
      }
2374
2375
      // when the options are merged and cleaned up we may end up not having to do
2376
      // an animation at all, therefore we should check this before issuing a post
2377
      // digest callback. Structural animations will always run no matter what.
2378
      var isValidAnimation = newAnimation.structural;
2379
      if (!isValidAnimation) {
2380
        // animate (from/to) can be quickly checked first, otherwise we check if any classes are present
2381
        isValidAnimation = (newAnimation.event === 'animate' && Object.keys(newAnimation.options.to || {}).length > 0)
2382
                            || hasAnimationClasses(newAnimation.options);
2383
      }
2384
2385
      if (!isValidAnimation) {
2386
        close();
2387
        clearElementAnimationState(element);
2388
        return runner;
2389
      }
2390
2391
      // the counter keeps track of cancelled animations
2392
      var counter = (existingAnimation.counter || 0) + 1;
2393
      newAnimation.counter = counter;
2394
2395
      markElementAnimationState(element, PRE_DIGEST_STATE, newAnimation);
2396
2397
      $rootScope.$$postDigest(function() {
2398
        var animationDetails = activeAnimationsLookup.get(node);
2399
        var animationCancelled = !animationDetails;
2400
        animationDetails = animationDetails || {};
2401
2402
        // if addClass/removeClass is called before something like enter then the
2403
        // registered parent element may not be present. The code below will ensure
2404
        // that a final value for parent element is obtained
2405
        var parentElement = element.parent() || [];
2406
2407
        // animate/structural/class-based animations all have requirements. Otherwise there
2408
        // is no point in performing an animation. The parent node must also be set.
2409
        var isValidAnimation = parentElement.length > 0
2410
                                && (animationDetails.event === 'animate'
2411
                                    || animationDetails.structural
2412
                                    || hasAnimationClasses(animationDetails.options));
2413
2414
        // this means that the previous animation was cancelled
2415
        // even if the follow-up animation is the same event
2416
        if (animationCancelled || animationDetails.counter !== counter || !isValidAnimation) {
2417
          // if another animation did not take over then we need
2418
          // to make sure that the domOperation and options are
2419
          // handled accordingly
2420
          if (animationCancelled) {
2421
            applyAnimationClasses(element, options);
2422
            applyAnimationStyles(element, options);
2423
          }
2424
2425
          // if the event changed from something like enter to leave then we do
2426
          // it, otherwise if it's the same then the end result will be the same too
2427
          if (animationCancelled || (isStructural && animationDetails.event !== event)) {
2428
            options.domOperation();
2429
            runner.end();
2430
          }
2431
2432
          // in the event that the element animation was not cancelled or a follow-up animation
2433
          // isn't allowed to animate from here then we need to clear the state of the element
2434
          // so that any future animations won't read the expired animation data.
2435
          if (!isValidAnimation) {
2436
            clearElementAnimationState(element);
2437
          }
2438
2439
          return;
2440
        }
2441
2442
        // this combined multiple class to addClass / removeClass into a setClass event
2443
        // so long as a structural event did not take over the animation
2444
        event = !animationDetails.structural && hasAnimationClasses(animationDetails.options, true)
2445
            ? 'setClass'
2446
            : animationDetails.event;
2447
2448
        markElementAnimationState(element, RUNNING_STATE);
2449
        var realRunner = $$animation(element, event, animationDetails.options);
2450
2451
        realRunner.done(function(status) {
2452
          close(!status);
2453
          var animationDetails = activeAnimationsLookup.get(node);
2454
          if (animationDetails && animationDetails.counter === counter) {
2455
            clearElementAnimationState(getDomNode(element));
2456
          }
2457
          notifyProgress(runner, event, 'close', {});
2458
        });
2459
2460
        // this will update the runner's flow-control events based on
2461
        // the `realRunner` object.
2462
        runner.setHost(realRunner);
2463
        notifyProgress(runner, event, 'start', {});
2464
      });
2465
2466
      return runner;
2467
2468
      function notifyProgress(runner, event, phase, data) {
2469
        runInNextPostDigestOrNow(function() {
2470
          var callbacks = findCallbacks(element, event);
2471
          if (callbacks.length) {
2472
            // do not optimize this call here to RAF because
2473
            // we don't know how heavy the callback code here will
2474
            // be and if this code is buffered then this can
2475
            // lead to a performance regression.
2476
            $$rAF(function() {
2477
              forEach(callbacks, function(callback) {
2478
                callback(element, phase, data);
2479
              });
2480
            });
2481
          }
2482
        });
2483
        runner.progress(event, phase, data);
2484
      }
2485
2486
      function close(reject) { // jshint ignore:line
2487
        clearGeneratedClasses(element, options);
2488
        applyAnimationClasses(element, options);
2489
        applyAnimationStyles(element, options);
2490
        options.domOperation();
2491
        runner.complete(!reject);
2492
      }
2493
    }
2494
2495
    function closeChildAnimations(element) {
2496
      var node = getDomNode(element);
2497
      var children = node.querySelectorAll('[' + NG_ANIMATE_ATTR_NAME + ']');
2498
      forEach(children, function(child) {
2499
        var state = parseInt(child.getAttribute(NG_ANIMATE_ATTR_NAME));
2500
        var animationDetails = activeAnimationsLookup.get(child);
2501
        switch (state) {
2502
          case RUNNING_STATE:
2503
            animationDetails.runner.end();
2504
            /* falls through */
2505
          case PRE_DIGEST_STATE:
2506
            if (animationDetails) {
2507
              activeAnimationsLookup.remove(child);
2508
            }
2509
            break;
2510
        }
2511
      });
2512
    }
2513
2514
    function clearElementAnimationState(element) {
2515
      var node = getDomNode(element);
2516
      node.removeAttribute(NG_ANIMATE_ATTR_NAME);
2517
      activeAnimationsLookup.remove(node);
2518
    }
2519
2520
    function isMatchingElement(nodeOrElmA, nodeOrElmB) {
2521
      return getDomNode(nodeOrElmA) === getDomNode(nodeOrElmB);
2522
    }
2523
2524
    function areAnimationsAllowed(element, parentElement, event) {
2525
      var bodyElement = jqLite($document[0].body);
2526
      var bodyElementDetected = isMatchingElement(element, bodyElement) || element[0].nodeName === 'HTML';
2527
      var rootElementDetected = isMatchingElement(element, $rootElement);
2528
      var parentAnimationDetected = false;
2529
      var animateChildren;
2530
2531
      var parentHost = element.data(NG_ANIMATE_PIN_DATA);
2532
      if (parentHost) {
2533
        parentElement = parentHost;
2534
      }
2535
2536
      while (parentElement && parentElement.length) {
2537
        if (!rootElementDetected) {
2538
          // angular doesn't want to attempt to animate elements outside of the application
2539
          // therefore we need to ensure that the rootElement is an ancestor of the current element
2540
          rootElementDetected = isMatchingElement(parentElement, $rootElement);
2541
        }
2542
2543
        var parentNode = parentElement[0];
2544
        if (parentNode.nodeType !== ELEMENT_NODE) {
2545
          // no point in inspecting the #document element
2546
          break;
2547
        }
2548
2549
        var details = activeAnimationsLookup.get(parentNode) || {};
2550
        // either an enter, leave or move animation will commence
2551
        // therefore we can't allow any animations to take place
2552
        // but if a parent animation is class-based then that's ok
2553
        if (!parentAnimationDetected) {
2554
          parentAnimationDetected = details.structural || disabledElementsLookup.get(parentNode);
2555
        }
2556
2557
        if (isUndefined(animateChildren) || animateChildren === true) {
2558
          var value = parentElement.data(NG_ANIMATE_CHILDREN_DATA);
2559
          if (isDefined(value)) {
2560
            animateChildren = value;
2561
          }
2562
        }
2563
2564
        // there is no need to continue traversing at this point
2565
        if (parentAnimationDetected && animateChildren === false) break;
2566
2567
        if (!rootElementDetected) {
2568
          // angular doesn't want to attempt to animate elements outside of the application
2569
          // therefore we need to ensure that the rootElement is an ancestor of the current element
2570
          rootElementDetected = isMatchingElement(parentElement, $rootElement);
2571
          if (!rootElementDetected) {
2572
            parentHost = parentElement.data(NG_ANIMATE_PIN_DATA);
2573
            if (parentHost) {
2574
              parentElement = parentHost;
2575
            }
2576
          }
2577
        }
2578
2579
        if (!bodyElementDetected) {
2580
          // we also need to ensure that the element is or will be apart of the body element
2581
          // otherwise it is pointless to even issue an animation to be rendered
2582
          bodyElementDetected = isMatchingElement(parentElement, bodyElement);
2583
        }
2584
2585
        parentElement = parentElement.parent();
2586
      }
2587
2588
      var allowAnimation = !parentAnimationDetected || animateChildren;
2589
      return allowAnimation && rootElementDetected && bodyElementDetected;
2590
    }
2591
2592
    function markElementAnimationState(element, state, details) {
2593
      details = details || {};
2594
      details.state = state;
2595
2596
      var node = getDomNode(element);
2597
      node.setAttribute(NG_ANIMATE_ATTR_NAME, state);
2598
2599
      var oldValue = activeAnimationsLookup.get(node);
2600
      var newValue = oldValue
2601
          ? extend(oldValue, details)
2602
          : details;
2603
      activeAnimationsLookup.put(node, newValue);
2604
    }
2605
  }];
2606
}];
2607
2608
var $$AnimateAsyncRunFactory = ['$$rAF', function($$rAF) {
2609
  var waitQueue = [];
2610
2611
  function waitForTick(fn) {
2612
    waitQueue.push(fn);
2613
    if (waitQueue.length > 1) return;
2614
    $$rAF(function() {
2615
      for (var i = 0; i < waitQueue.length; i++) {
2616
        waitQueue[i]();
2617
      }
2618
      waitQueue = [];
2619
    });
2620
  }
2621
2622
  return function() {
2623
    var passed = false;
2624
    waitForTick(function() {
2625
      passed = true;
2626
    });
2627
    return function(callback) {
2628
      passed ? callback() : waitForTick(callback);
2629
    };
2630
  };
2631
}];
2632
2633
var $$AnimateRunnerFactory = ['$q', '$sniffer', '$$animateAsyncRun',
2634
                      function($q,   $sniffer,   $$animateAsyncRun) {
2635
2636
  var INITIAL_STATE = 0;
2637
  var DONE_PENDING_STATE = 1;
2638
  var DONE_COMPLETE_STATE = 2;
2639
2640
  AnimateRunner.chain = function(chain, callback) {
2641
    var index = 0;
2642
2643
    next();
2644
    function next() {
2645
      if (index === chain.length) {
2646
        callback(true);
2647
        return;
2648
      }
2649
2650
      chain[index](function(response) {
2651
        if (response === false) {
2652
          callback(false);
2653
          return;
2654
        }
2655
        index++;
2656
        next();
2657
      });
2658
    }
2659
  };
2660
2661
  AnimateRunner.all = function(runners, callback) {
2662
    var count = 0;
2663
    var status = true;
2664
    forEach(runners, function(runner) {
2665
      runner.done(onProgress);
2666
    });
2667
2668
    function onProgress(response) {
2669
      status = status && response;
2670
      if (++count === runners.length) {
2671
        callback(status);
2672
      }
2673
    }
2674
  };
2675
2676
  function AnimateRunner(host) {
2677
    this.setHost(host);
2678
2679
    this._doneCallbacks = [];
2680
    this._runInAnimationFrame = $$animateAsyncRun();
2681
    this._state = 0;
2682
  }
2683
2684
  AnimateRunner.prototype = {
2685
    setHost: function(host) {
2686
      this.host = host || {};
2687
    },
2688
2689
    done: function(fn) {
2690
      if (this._state === DONE_COMPLETE_STATE) {
2691
        fn();
2692
      } else {
2693
        this._doneCallbacks.push(fn);
2694
      }
2695
    },
2696
2697
    progress: noop,
2698
2699
    getPromise: function() {
2700
      if (!this.promise) {
2701
        var self = this;
2702
        this.promise = $q(function(resolve, reject) {
2703
          self.done(function(status) {
2704
            status === false ? reject() : resolve();
2705
          });
2706
        });
2707
      }
2708
      return this.promise;
2709
    },
2710
2711
    then: function(resolveHandler, rejectHandler) {
2712
      return this.getPromise().then(resolveHandler, rejectHandler);
2713
    },
2714
2715
    'catch': function(handler) {
2716
      return this.getPromise()['catch'](handler);
2717
    },
2718
2719
    'finally': function(handler) {
2720
      return this.getPromise()['finally'](handler);
2721
    },
2722
2723
    pause: function() {
2724
      if (this.host.pause) {
2725
        this.host.pause();
2726
      }
2727
    },
2728
2729
    resume: function() {
2730
      if (this.host.resume) {
2731
        this.host.resume();
2732
      }
2733
    },
2734
2735
    end: function() {
2736
      if (this.host.end) {
2737
        this.host.end();
2738
      }
2739
      this._resolve(true);
2740
    },
2741
2742
    cancel: function() {
2743
      if (this.host.cancel) {
2744
        this.host.cancel();
2745
      }
2746
      this._resolve(false);
2747
    },
2748
2749
    complete: function(response) {
2750
      var self = this;
2751
      if (self._state === INITIAL_STATE) {
2752
        self._state = DONE_PENDING_STATE;
2753
        self._runInAnimationFrame(function() {
2754
          self._resolve(response);
2755
        });
2756
      }
2757
    },
2758
2759
    _resolve: function(response) {
2760
      if (this._state !== DONE_COMPLETE_STATE) {
2761
        forEach(this._doneCallbacks, function(fn) {
2762
          fn(response);
2763
        });
2764
        this._doneCallbacks.length = 0;
2765
        this._state = DONE_COMPLETE_STATE;
2766
      }
2767
    }
2768
  };
2769
2770
  return AnimateRunner;
2771
}];
2772
2773
var $$AnimationProvider = ['$animateProvider', function($animateProvider) {
2774
  var NG_ANIMATE_REF_ATTR = 'ng-animate-ref';
2775
2776
  var drivers = this.drivers = [];
2777
2778
  var RUNNER_STORAGE_KEY = '$$animationRunner';
2779
2780
  function setRunner(element, runner) {
2781
    element.data(RUNNER_STORAGE_KEY, runner);
2782
  }
2783
2784
  function removeRunner(element) {
2785
    element.removeData(RUNNER_STORAGE_KEY);
2786
  }
2787
2788
  function getRunner(element) {
2789
    return element.data(RUNNER_STORAGE_KEY);
2790
  }
2791
2792
  this.$get = ['$$jqLite', '$rootScope', '$injector', '$$AnimateRunner', '$$HashMap', '$$rAFScheduler',
2793
       function($$jqLite,   $rootScope,   $injector,   $$AnimateRunner,   $$HashMap,   $$rAFScheduler) {
2794
2795
    var animationQueue = [];
2796
    var applyAnimationClasses = applyAnimationClassesFactory($$jqLite);
2797
2798
    function sortAnimations(animations) {
2799
      var tree = { children: [] };
2800
      var i, lookup = new $$HashMap();
2801
2802
      // this is done first beforehand so that the hashmap
2803
      // is filled with a list of the elements that will be animated
2804
      for (i = 0; i < animations.length; i++) {
2805
        var animation = animations[i];
2806
        lookup.put(animation.domNode, animations[i] = {
2807
          domNode: animation.domNode,
2808
          fn: animation.fn,
2809
          children: []
2810
        });
2811
      }
2812
2813
      for (i = 0; i < animations.length; i++) {
2814
        processNode(animations[i]);
2815
      }
2816
2817
      return flatten(tree);
2818
2819
      function processNode(entry) {
2820
        if (entry.processed) return entry;
2821
        entry.processed = true;
2822
2823
        var elementNode = entry.domNode;
2824
        var parentNode = elementNode.parentNode;
2825
        lookup.put(elementNode, entry);
2826
2827
        var parentEntry;
2828
        while (parentNode) {
2829
          parentEntry = lookup.get(parentNode);
2830
          if (parentEntry) {
2831
            if (!parentEntry.processed) {
2832
              parentEntry = processNode(parentEntry);
2833
            }
2834
            break;
2835
          }
2836
          parentNode = parentNode.parentNode;
2837
        }
2838
2839
        (parentEntry || tree).children.push(entry);
2840
        return entry;
2841
      }
2842
2843
      function flatten(tree) {
2844
        var result = [];
2845
        var queue = [];
2846
        var i;
2847
2848
        for (i = 0; i < tree.children.length; i++) {
2849
          queue.push(tree.children[i]);
2850
        }
2851
2852
        var remainingLevelEntries = queue.length;
2853
        var nextLevelEntries = 0;
2854
        var row = [];
2855
2856
        for (i = 0; i < queue.length; i++) {
2857
          var entry = queue[i];
2858
          if (remainingLevelEntries <= 0) {
2859
            remainingLevelEntries = nextLevelEntries;
2860
            nextLevelEntries = 0;
2861
            result.push(row);
2862
            row = [];
2863
          }
2864
          row.push(entry.fn);
2865
          entry.children.forEach(function(childEntry) {
2866
            nextLevelEntries++;
2867
            queue.push(childEntry);
2868
          });
2869
          remainingLevelEntries--;
2870
        }
2871
2872
        if (row.length) {
2873
          result.push(row);
2874
        }
2875
2876
        return result;
2877
      }
2878
    }
2879
2880
    // TODO(matsko): document the signature in a better way
2881
    return function(element, event, options) {
2882
      options = prepareAnimationOptions(options);
2883
      var isStructural = ['enter', 'move', 'leave'].indexOf(event) >= 0;
2884
2885
      // there is no animation at the current moment, however
2886
      // these runner methods will get later updated with the
2887
      // methods leading into the driver's end/cancel methods
2888
      // for now they just stop the animation from starting
2889
      var runner = new $$AnimateRunner({
2890
        end: function() { close(); },
2891
        cancel: function() { close(true); }
2892
      });
2893
2894
      if (!drivers.length) {
2895
        close();
2896
        return runner;
2897
      }
2898
2899
      setRunner(element, runner);
2900
2901
      var classes = mergeClasses(element.attr('class'), mergeClasses(options.addClass, options.removeClass));
2902
      var tempClasses = options.tempClasses;
2903
      if (tempClasses) {
2904
        classes += ' ' + tempClasses;
2905
        options.tempClasses = null;
2906
      }
2907
2908
      animationQueue.push({
2909
        // this data is used by the postDigest code and passed into
2910
        // the driver step function
2911
        element: element,
2912
        classes: classes,
2913
        event: event,
2914
        structural: isStructural,
2915
        options: options,
2916
        beforeStart: beforeStart,
2917
        close: close
2918
      });
2919
2920
      element.on('$destroy', handleDestroyedElement);
2921
2922
      // we only want there to be one function called within the post digest
2923
      // block. This way we can group animations for all the animations that
2924
      // were apart of the same postDigest flush call.
2925
      if (animationQueue.length > 1) return runner;
2926
2927
      $rootScope.$$postDigest(function() {
2928
        var animations = [];
2929
        forEach(animationQueue, function(entry) {
2930
          // the element was destroyed early on which removed the runner
2931
          // form its storage. This means we can't animate this element
2932
          // at all and it already has been closed due to destruction.
2933
          if (getRunner(entry.element)) {
2934
            animations.push(entry);
2935
          } else {
2936
            entry.close();
2937
          }
2938
        });
2939
2940
        // now any future animations will be in another postDigest
2941
        animationQueue.length = 0;
2942
2943
        var groupedAnimations = groupAnimations(animations);
2944
        var toBeSortedAnimations = [];
2945
2946
        forEach(groupedAnimations, function(animationEntry) {
2947
          toBeSortedAnimations.push({
2948
            domNode: getDomNode(animationEntry.from ? animationEntry.from.element : animationEntry.element),
2949
            fn: function triggerAnimationStart() {
2950
              // it's important that we apply the `ng-animate` CSS class and the
2951
              // temporary classes before we do any driver invoking since these
2952
              // CSS classes may be required for proper CSS detection.
2953
              animationEntry.beforeStart();
2954
2955
              var startAnimationFn, closeFn = animationEntry.close;
2956
2957
              // in the event that the element was removed before the digest runs or
2958
              // during the RAF sequencing then we should not trigger the animation.
2959
              var targetElement = animationEntry.anchors
2960
                  ? (animationEntry.from.element || animationEntry.to.element)
2961
                  : animationEntry.element;
2962
2963
              if (getRunner(targetElement)) {
2964
                var operation = invokeFirstDriver(animationEntry);
2965
                if (operation) {
2966
                  startAnimationFn = operation.start;
2967
                }
2968
              }
2969
2970
              if (!startAnimationFn) {
2971
                closeFn();
2972
              } else {
2973
                var animationRunner = startAnimationFn();
2974
                animationRunner.done(function(status) {
2975
                  closeFn(!status);
2976
                });
2977
                updateAnimationRunners(animationEntry, animationRunner);
2978
              }
2979
            }
2980
          });
2981
        });
2982
2983
        // we need to sort each of the animations in order of parent to child
2984
        // relationships. This ensures that the child classes are applied at the
2985
        // right time.
2986
        $$rAFScheduler(sortAnimations(toBeSortedAnimations));
2987
      });
2988
2989
      return runner;
2990
2991
      // TODO(matsko): change to reference nodes
2992
      function getAnchorNodes(node) {
2993
        var SELECTOR = '[' + NG_ANIMATE_REF_ATTR + ']';
2994
        var items = node.hasAttribute(NG_ANIMATE_REF_ATTR)
2995
              ? [node]
2996
              : node.querySelectorAll(SELECTOR);
2997
        var anchors = [];
2998
        forEach(items, function(node) {
2999
          var attr = node.getAttribute(NG_ANIMATE_REF_ATTR);
3000
          if (attr && attr.length) {
3001
            anchors.push(node);
3002
          }
3003
        });
3004
        return anchors;
3005
      }
3006
3007
      function groupAnimations(animations) {
3008
        var preparedAnimations = [];
3009
        var refLookup = {};
3010
        forEach(animations, function(animation, index) {
3011
          var element = animation.element;
3012
          var node = getDomNode(element);
3013
          var event = animation.event;
3014
          var enterOrMove = ['enter', 'move'].indexOf(event) >= 0;
3015
          var anchorNodes = animation.structural ? getAnchorNodes(node) : [];
3016
3017
          if (anchorNodes.length) {
3018
            var direction = enterOrMove ? 'to' : 'from';
3019
3020
            forEach(anchorNodes, function(anchor) {
3021
              var key = anchor.getAttribute(NG_ANIMATE_REF_ATTR);
3022
              refLookup[key] = refLookup[key] || {};
3023
              refLookup[key][direction] = {
3024
                animationID: index,
3025
                element: jqLite(anchor)
3026
              };
3027
            });
3028
          } else {
3029
            preparedAnimations.push(animation);
3030
          }
3031
        });
3032
3033
        var usedIndicesLookup = {};
3034
        var anchorGroups = {};
3035
        forEach(refLookup, function(operations, key) {
3036
          var from = operations.from;
3037
          var to = operations.to;
3038
3039
          if (!from || !to) {
3040
            // only one of these is set therefore we can't have an
3041
            // anchor animation since all three pieces are required
3042
            var index = from ? from.animationID : to.animationID;
3043
            var indexKey = index.toString();
3044
            if (!usedIndicesLookup[indexKey]) {
3045
              usedIndicesLookup[indexKey] = true;
3046
              preparedAnimations.push(animations[index]);
3047
            }
3048
            return;
3049
          }
3050
3051
          var fromAnimation = animations[from.animationID];
3052
          var toAnimation = animations[to.animationID];
3053
          var lookupKey = from.animationID.toString();
3054
          if (!anchorGroups[lookupKey]) {
3055
            var group = anchorGroups[lookupKey] = {
3056
              structural: true,
3057
              beforeStart: function() {
3058
                fromAnimation.beforeStart();
3059
                toAnimation.beforeStart();
3060
              },
3061
              close: function() {
3062
                fromAnimation.close();
3063
                toAnimation.close();
3064
              },
3065
              classes: cssClassesIntersection(fromAnimation.classes, toAnimation.classes),
3066
              from: fromAnimation,
3067
              to: toAnimation,
3068
              anchors: [] // TODO(matsko): change to reference nodes
3069
            };
3070
3071
            // the anchor animations require that the from and to elements both have at least
3072
            // one shared CSS class which effictively marries the two elements together to use
3073
            // the same animation driver and to properly sequence the anchor animation.
3074
            if (group.classes.length) {
3075
              preparedAnimations.push(group);
3076
            } else {
3077
              preparedAnimations.push(fromAnimation);
3078
              preparedAnimations.push(toAnimation);
3079
            }
3080
          }
3081
3082
          anchorGroups[lookupKey].anchors.push({
3083
            'out': from.element, 'in': to.element
3084
          });
3085
        });
3086
3087
        return preparedAnimations;
3088
      }
3089
3090
      function cssClassesIntersection(a,b) {
3091
        a = a.split(' ');
3092
        b = b.split(' ');
3093
        var matches = [];
3094
3095
        for (var i = 0; i < a.length; i++) {
3096
          var aa = a[i];
3097
          if (aa.substring(0,3) === 'ng-') continue;
3098
3099
          for (var j = 0; j < b.length; j++) {
3100
            if (aa === b[j]) {
3101
              matches.push(aa);
3102
              break;
3103
            }
3104
          }
3105
        }
3106
3107
        return matches.join(' ');
3108
      }
3109
3110
      function invokeFirstDriver(animationDetails) {
3111
        // we loop in reverse order since the more general drivers (like CSS and JS)
3112
        // may attempt more elements, but custom drivers are more particular
3113
        for (var i = drivers.length - 1; i >= 0; i--) {
3114
          var driverName = drivers[i];
3115
          if (!$injector.has(driverName)) continue; // TODO(matsko): remove this check
3116
3117
          var factory = $injector.get(driverName);
3118
          var driver = factory(animationDetails);
3119
          if (driver) {
3120
            return driver;
3121
          }
3122
        }
3123
      }
3124
3125
      function beforeStart() {
3126
        element.addClass(NG_ANIMATE_CLASSNAME);
3127
        if (tempClasses) {
3128
          $$jqLite.addClass(element, tempClasses);
3129
        }
3130
      }
3131
3132
      function updateAnimationRunners(animation, newRunner) {
3133
        if (animation.from && animation.to) {
3134
          update(animation.from.element);
3135
          update(animation.to.element);
3136
        } else {
3137
          update(animation.element);
3138
        }
3139
3140
        function update(element) {
3141
          getRunner(element).setHost(newRunner);
3142
        }
3143
      }
3144
3145
      function handleDestroyedElement() {
3146
        var runner = getRunner(element);
3147
        if (runner && (event !== 'leave' || !options.$$domOperationFired)) {
3148
          runner.end();
3149
        }
3150
      }
3151
3152
      function close(rejected) { // jshint ignore:line
3153
        element.off('$destroy', handleDestroyedElement);
3154
        removeRunner(element);
3155
3156
        applyAnimationClasses(element, options);
3157
        applyAnimationStyles(element, options);
3158
        options.domOperation();
3159
3160
        if (tempClasses) {
3161
          $$jqLite.removeClass(element, tempClasses);
3162
        }
3163
3164
        element.removeClass(NG_ANIMATE_CLASSNAME);
3165
        runner.complete(!rejected);
3166
      }
3167
    };
3168
  }];
3169
}];
3170
3171
/* global angularAnimateModule: true,
3172
3173
   $$AnimateAsyncRunFactory,
3174
   $$rAFSchedulerFactory,
3175
   $$AnimateChildrenDirective,
3176
   $$AnimateRunnerFactory,
3177
   $$AnimateQueueProvider,
3178
   $$AnimationProvider,
3179
   $AnimateCssProvider,
3180
   $$AnimateCssDriverProvider,
3181
   $$AnimateJsProvider,
3182
   $$AnimateJsDriverProvider,
3183
*/
3184
3185
/**
3186
 * @ngdoc module
3187
 * @name ngAnimate
3188
 * @description
3189
 *
3190
 * The `ngAnimate` module provides support for CSS-based animations (keyframes and transitions) as well as JavaScript-based animations via
3191
 * callback hooks. Animations are not enabled by default, however, by including `ngAnimate` the animation hooks are enabled for an Angular app.
3192
 *
3193
 * <div doc-module-components="ngAnimate"></div>
3194
 *
3195
 * # Usage
3196
 * Simply put, there are two ways to make use of animations when ngAnimate is used: by using **CSS** and **JavaScript**. The former works purely based
3197
 * using CSS (by using matching CSS selectors/styles) and the latter triggers animations that are registered via `module.animation()`. For
3198
 * both CSS and JS animations the sole requirement is to have a matching `CSS class` that exists both in the registered animation and within
3199
 * the HTML element that the animation will be triggered on.
3200
 *
3201
 * ## Directive Support
3202
 * The following directives are "animation aware":
3203
 *
3204
 * | Directive                                                                                                | Supported Animations                                                     |
3205
 * |----------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------|
3206
 * | {@link ng.directive:ngRepeat#animations ngRepeat}                                                        | enter, leave and move                                                    |
3207
 * | {@link ngRoute.directive:ngView#animations ngView}                                                       | enter and leave                                                          |
3208
 * | {@link ng.directive:ngInclude#animations ngInclude}                                                      | enter and leave                                                          |
3209
 * | {@link ng.directive:ngSwitch#animations ngSwitch}                                                        | enter and leave                                                          |
3210
 * | {@link ng.directive:ngIf#animations ngIf}                                                                | enter and leave                                                          |
3211
 * | {@link ng.directive:ngClass#animations ngClass}                                                          | add and remove (the CSS class(es) present)                               |
3212
 * | {@link ng.directive:ngShow#animations ngShow} & {@link ng.directive:ngHide#animations ngHide}            | add and remove (the ng-hide class value)                                 |
3213
 * | {@link ng.directive:form#animation-hooks form} & {@link ng.directive:ngModel#animation-hooks ngModel}    | add and remove (dirty, pristine, valid, invalid & all other validations) |
3214
 * | {@link module:ngMessages#animations ngMessages}                                                          | add and remove (ng-active & ng-inactive)                                 |
3215
 * | {@link module:ngMessages#animations ngMessage}                                                           | enter and leave                                                          |
3216
 *
3217
 * (More information can be found by visiting each the documentation associated with each directive.)
3218
 *
3219
 * ## CSS-based Animations
3220
 *
3221
 * 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
3222
 * and CSS code we can create an animation that will be picked up by Angular when an the underlying directive performs an operation.
3223
 *
3224
 * The example below shows how an `enter` animation can be made possible on an element using `ng-if`:
3225
 *
3226
 * ```html
3227
 * <div ng-if="bool" class="fade">
3228
 *    Fade me in out
3229
 * </div>
3230
 * <button ng-click="bool=true">Fade In!</button>
3231
 * <button ng-click="bool=false">Fade Out!</button>
3232
 * ```
3233
 *
3234
 * Notice the CSS class **fade**? We can now create the CSS transition code that references this class:
3235
 *
3236
 * ```css
3237
 * /&#42; The starting CSS styles for the enter animation &#42;/
3238
 * .fade.ng-enter {
3239
 *   transition:0.5s linear all;
3240
 *   opacity:0;
3241
 * }
3242
 *
3243
 * /&#42; The finishing CSS styles for the enter animation &#42;/
3244
 * .fade.ng-enter.ng-enter-active {
3245
 *   opacity:1;
3246
 * }
3247
 * ```
3248
 *
3249
 * The key thing to remember here is that, depending on the animation event (which each of the directives above trigger depending on what's going on) two
3250
 * generated CSS classes will be applied to the element; in the example above we have `.ng-enter` and `.ng-enter-active`. For CSS transitions, the transition
3251
 * code **must** be defined within the starting CSS class (in this case `.ng-enter`). The destination class is what the transition will animate towards.
3252
 *
3253
 * If for example we wanted to create animations for `leave` and `move` (ngRepeat triggers move) then we can do so using the same CSS naming conventions:
3254
 *
3255
 * ```css
3256
 * /&#42; now the element will fade out before it is removed from the DOM &#42;/
3257
 * .fade.ng-leave {
3258
 *   transition:0.5s linear all;
3259
 *   opacity:1;
3260
 * }
3261
 * .fade.ng-leave.ng-leave-active {
3262
 *   opacity:0;
3263
 * }
3264
 * ```
3265
 *
3266
 * We can also make use of **CSS Keyframes** by referencing the keyframe animation within the starting CSS class:
3267
 *
3268
 * ```css
3269
 * /&#42; there is no need to define anything inside of the destination
3270
 * CSS class since the keyframe will take charge of the animation &#42;/
3271
 * .fade.ng-leave {
3272
 *   animation: my_fade_animation 0.5s linear;
3273
 *   -webkit-animation: my_fade_animation 0.5s linear;
3274
 * }
3275
 *
3276
 * @keyframes my_fade_animation {
3277
 *   from { opacity:1; }
3278
 *   to { opacity:0; }
3279
 * }
3280
 *
3281
 * @-webkit-keyframes my_fade_animation {
3282
 *   from { opacity:1; }
3283
 *   to { opacity:0; }
3284
 * }
3285
 * ```
3286
 *
3287
 * Feel free also mix transitions and keyframes together as well as any other CSS classes on the same element.
3288
 *
3289
 * ### CSS Class-based Animations
3290
 *
3291
 * Class-based animations (animations that are triggered via `ngClass`, `ngShow`, `ngHide` and some other directives) have a slightly different
3292
 * naming convention. Class-based animations are basic enough that a standard transition or keyframe can be referenced on the class being added
3293
 * and removed.
3294
 *
3295
 * For example if we wanted to do a CSS animation for `ngHide` then we place an animation on the `.ng-hide` CSS class:
3296
 *
3297
 * ```html
3298
 * <div ng-show="bool" class="fade">
3299
 *   Show and hide me
3300
 * </div>
3301
 * <button ng-click="bool=true">Toggle</button>
3302
 *
3303
 * <style>
3304
 * .fade.ng-hide {
3305
 *   transition:0.5s linear all;
3306
 *   opacity:0;
3307
 * }
3308
 * </style>
3309
 * ```
3310
 *
3311
 * All that is going on here with ngShow/ngHide behind the scenes is the `.ng-hide` class is added/removed (when the hidden state is valid). Since
3312
 * ngShow and ngHide are animation aware then we can match up a transition and ngAnimate handles the rest.
3313
 *
3314
 * In addition the addition and removal of the CSS class, ngAnimate also provides two helper methods that we can use to further decorate the animation
3315
 * with CSS styles.
3316
 *
3317
 * ```html
3318
 * <div ng-class="{on:onOff}" class="highlight">
3319
 *   Highlight this box
3320
 * </div>
3321
 * <button ng-click="onOff=!onOff">Toggle</button>
3322
 *
3323
 * <style>
3324
 * .highlight {
3325
 *   transition:0.5s linear all;
3326
 * }
3327
 * .highlight.on-add {
3328
 *   background:white;
3329
 * }
3330
 * .highlight.on {
3331
 *   background:yellow;
3332
 * }
3333
 * .highlight.on-remove {
3334
 *   background:black;
3335
 * }
3336
 * </style>
3337
 * ```
3338
 *
3339
 * We can also make use of CSS keyframes by placing them within the CSS classes.
3340
 *
3341
 *
3342
 * ### CSS Staggering Animations
3343
 * A Staggering animation is a collection of animations that are issued with a slight delay in between each successive operation resulting in a
3344
 * curtain-like effect. The ngAnimate module (versions >=1.2) supports staggering animations and the stagger effect can be
3345
 * performed by creating a **ng-EVENT-stagger** CSS class and attaching that class to the base CSS class used for
3346
 * the animation. The style property expected within the stagger class can either be a **transition-delay** or an
3347
 * **animation-delay** property (or both if your animation contains both transitions and keyframe animations).
3348
 *
3349
 * ```css
3350
 * .my-animation.ng-enter {
3351
 *   /&#42; standard transition code &#42;/
3352
 *   transition: 1s linear all;
3353
 *   opacity:0;
3354
 * }
3355
 * .my-animation.ng-enter-stagger {
3356
 *   /&#42; this will have a 100ms delay between each successive leave animation &#42;/
3357
 *   transition-delay: 0.1s;
3358
 *
3359
 *   /&#42; As of 1.4.4, this must always be set: it signals ngAnimate
3360
 *     to not accidentally inherit a delay property from another CSS class &#42;/
3361
 *   transition-duration: 0s;
3362
 * }
3363
 * .my-animation.ng-enter.ng-enter-active {
3364
 *   /&#42; standard transition styles &#42;/
3365
 *   opacity:1;
3366
 * }
3367
 * ```
3368
 *
3369
 * Staggering animations work by default in ngRepeat (so long as the CSS class is defined). Outside of ngRepeat, to use staggering animations
3370
 * on your own, they can be triggered by firing multiple calls to the same event on $animate. However, the restrictions surrounding this
3371
 * are that each of the elements must have the same CSS className value as well as the same parent element. A stagger operation
3372
 * will also be reset if one or more animation frames have passed since the multiple calls to `$animate` were fired.
3373
 *
3374
 * The following code will issue the **ng-leave-stagger** event on the element provided:
3375
 *
3376
 * ```js
3377
 * var kids = parent.children();
3378
 *
3379
 * $animate.leave(kids[0]); //stagger index=0
3380
 * $animate.leave(kids[1]); //stagger index=1
3381
 * $animate.leave(kids[2]); //stagger index=2
3382
 * $animate.leave(kids[3]); //stagger index=3
3383
 * $animate.leave(kids[4]); //stagger index=4
3384
 *
3385
 * window.requestAnimationFrame(function() {
3386
 *   //stagger has reset itself
3387
 *   $animate.leave(kids[5]); //stagger index=0
3388
 *   $animate.leave(kids[6]); //stagger index=1
3389
 *
3390
 *   $scope.$digest();
3391
 * });
3392
 * ```
3393
 *
3394
 * Stagger animations are currently only supported within CSS-defined animations.
3395
 *
3396
 * ### The `ng-animate` CSS class
3397
 *
3398
 * When ngAnimate is animating an element it will apply the `ng-animate` CSS class to the element for the duration of the animation.
3399
 * This is a temporary CSS class and it will be removed once the animation is over (for both JavaScript and CSS-based animations).
3400
 *
3401
 * Therefore, animations can be applied to an element using this temporary class directly via CSS.
3402
 *
3403
 * ```css
3404
 * .zipper.ng-animate {
3405
 *   transition:0.5s linear all;
3406
 * }
3407
 * .zipper.ng-enter {
3408
 *   opacity:0;
3409
 * }
3410
 * .zipper.ng-enter.ng-enter-active {
3411
 *   opacity:1;
3412
 * }
3413
 * .zipper.ng-leave {
3414
 *   opacity:1;
3415
 * }
3416
 * .zipper.ng-leave.ng-leave-active {
3417
 *   opacity:0;
3418
 * }
3419
 * ```
3420
 *
3421
 * (Note that the `ng-animate` CSS class is reserved and it cannot be applied on an element directly since ngAnimate will always remove
3422
 * the CSS class once an animation has completed.)
3423
 *
3424
 *
3425
 * ## JavaScript-based Animations
3426
 *
3427
 * ngAnimate also allows for animations to be consumed by JavaScript code. The approach is similar to CSS-based animations (where there is a shared
3428
 * 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
3429
 * `module.animation()` module function we can register the ainmation.
3430
 *
3431
 * Let's see an example of a enter/leave animation using `ngRepeat`:
3432
 *
3433
 * ```html
3434
 * <div ng-repeat="item in items" class="slide">
3435
 *   {{ item }}
3436
 * </div>
3437
 * ```
3438
 *
3439
 * See the **slide** CSS class? Let's use that class to define an animation that we'll structure in our module code by using `module.animation`:
3440
 *
3441
 * ```js
3442
 * myModule.animation('.slide', [function() {
3443
 *   return {
3444
 *     // make note that other events (like addClass/removeClass)
3445
 *     // have different function input parameters
3446
 *     enter: function(element, doneFn) {
3447
 *       jQuery(element).fadeIn(1000, doneFn);
3448
 *
3449
 *       // remember to call doneFn so that angular
3450
 *       // knows that the animation has concluded
3451
 *     },
3452
 *
3453
 *     move: function(element, doneFn) {
3454
 *       jQuery(element).fadeIn(1000, doneFn);
3455
 *     },
3456
 *
3457
 *     leave: function(element, doneFn) {
3458
 *       jQuery(element).fadeOut(1000, doneFn);
3459
 *     }
3460
 *   }
3461
 * }]
3462
 * ```
3463
 *
3464
 * The nice thing about JS-based animations is that we can inject other services and make use of advanced animation libraries such as
3465
 * greensock.js and velocity.js.
3466
 *
3467
 * If our animation code class-based (meaning that something like `ngClass`, `ngHide` and `ngShow` triggers it) then we can still define
3468
 * our animations inside of the same registered animation, however, the function input arguments are a bit different:
3469
 *
3470
 * ```html
3471
 * <div ng-class="color" class="colorful">
3472
 *   this box is moody
3473
 * </div>
3474
 * <button ng-click="color='red'">Change to red</button>
3475
 * <button ng-click="color='blue'">Change to blue</button>
3476
 * <button ng-click="color='green'">Change to green</button>
3477
 * ```
3478
 *
3479
 * ```js
3480
 * myModule.animation('.colorful', [function() {
3481
 *   return {
3482
 *     addClass: function(element, className, doneFn) {
3483
 *       // do some cool animation and call the doneFn
3484
 *     },
3485
 *     removeClass: function(element, className, doneFn) {
3486
 *       // do some cool animation and call the doneFn
3487
 *     },
3488
 *     setClass: function(element, addedClass, removedClass, doneFn) {
3489
 *       // do some cool animation and call the doneFn
3490
 *     }
3491
 *   }
3492
 * }]
3493
 * ```
3494
 *
3495
 * ## CSS + JS Animations Together
3496
 *
3497
 * 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,
3498
 * 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
3499
 * charge of the animation**:
3500
 *
3501
 * ```html
3502
 * <div ng-if="bool" class="slide">
3503
 *   Slide in and out
3504
 * </div>
3505
 * ```
3506
 *
3507
 * ```js
3508
 * myModule.animation('.slide', [function() {
3509
 *   return {
3510
 *     enter: function(element, doneFn) {
3511
 *       jQuery(element).slideIn(1000, doneFn);
3512
 *     }
3513
 *   }
3514
 * }]
3515
 * ```
3516
 *
3517
 * ```css
3518
 * .slide.ng-enter {
3519
 *   transition:0.5s linear all;
3520
 *   transform:translateY(-100px);
3521
 * }
3522
 * .slide.ng-enter.ng-enter-active {
3523
 *   transform:translateY(0);
3524
 * }
3525
 * ```
3526
 *
3527
 * 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
3528
 * lack of CSS animations by using the `$animateCss` service to trigger our own tweaked-out, CSS-based animations directly from
3529
 * our own JS-based animation code:
3530
 *
3531
 * ```js
3532
 * myModule.animation('.slide', ['$animateCss', function($animateCss) {
3533
 *   return {
3534
 *     enter: function(element, doneFn) {
3535
*        // this will trigger `.slide.ng-enter` and `.slide.ng-enter-active`.
3536
 *       var runner = $animateCss(element, {
3537
 *         event: 'enter',
3538
 *         structural: true
3539
 *       }).start();
3540
*        runner.done(doneFn);
3541
 *     }
3542
 *   }
3543
 * }]
3544
 * ```
3545
 *
3546
 * 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.
3547
 *
3548
 * The `$animateCss` service is very powerful since we can feed in all kinds of extra properties that will be evaluated and fed into a CSS transition or
3549
 * keyframe animation. For example if we wanted to animate the height of an element while adding and removing classes then we can do so by providing that
3550
 * data into `$animateCss` directly:
3551
 *
3552
 * ```js
3553
 * myModule.animation('.slide', ['$animateCss', function($animateCss) {
3554
 *   return {
3555
 *     enter: function(element, doneFn) {
3556
 *       var runner = $animateCss(element, {
3557
 *         event: 'enter',
3558
 *         structural: true,
3559
 *         addClass: 'maroon-setting',
3560
 *         from: { height:0 },
3561
 *         to: { height: 200 }
3562
 *       }).start();
3563
 *
3564
 *       runner.done(doneFn);
3565
 *     }
3566
 *   }
3567
 * }]
3568
 * ```
3569
 *
3570
 * Now we can fill in the rest via our transition CSS code:
3571
 *
3572
 * ```css
3573
 * /&#42; the transition tells ngAnimate to make the animation happen &#42;/
3574
 * .slide.ng-enter { transition:0.5s linear all; }
3575
 *
3576
 * /&#42; this extra CSS class will be absorbed into the transition
3577
 * since the $animateCss code is adding the class &#42;/
3578
 * .maroon-setting { background:red; }
3579
 * ```
3580
 *
3581
 * And `$animateCss` will figure out the rest. Just make sure to have the `done()` callback fire the `doneFn` function to signal when the animation is over.
3582
 *
3583
 * To learn more about what's possible be sure to visit the {@link ngAnimate.$animateCss $animateCss service}.
3584
 *
3585
 * ## Animation Anchoring (via `ng-animate-ref`)
3586
 *
3587
 * ngAnimate in AngularJS 1.4 comes packed with the ability to cross-animate elements between
3588
 * structural areas of an application (like views) by pairing up elements using an attribute
3589
 * called `ng-animate-ref`.
3590
 *
3591
 * Let's say for example we have two views that are managed by `ng-view` and we want to show
3592
 * that there is a relationship between two components situated in within these views. By using the
3593
 * `ng-animate-ref` attribute we can identify that the two components are paired together and we
3594
 * can then attach an animation, which is triggered when the view changes.
3595
 *
3596
 * Say for example we have the following template code:
3597
 *
3598
 * ```html
3599
 * <!-- index.html -->
3600
 * <div ng-view class="view-animation">
3601
 * </div>
3602
 *
3603
 * <!-- home.html -->
3604
 * <a href="#/banner-page">
3605
 *   <img src="./banner.jpg" class="banner" ng-animate-ref="banner">
3606
 * </a>
3607
 *
3608
 * <!-- banner-page.html -->
3609
 * <img src="./banner.jpg" class="banner" ng-animate-ref="banner">
3610
 * ```
3611
 *
3612
 * Now, when the view changes (once the link is clicked), ngAnimate will examine the
3613
 * HTML contents to see if there is a match reference between any components in the view
3614
 * that is leaving and the view that is entering. It will scan both the view which is being
3615
 * removed (leave) and inserted (enter) to see if there are any paired DOM elements that
3616
 * contain a matching ref value.
3617
 *
3618
 * The two images match since they share the same ref value. ngAnimate will now create a
3619
 * transport element (which is a clone of the first image element) and it will then attempt
3620
 * to animate to the position of the second image element in the next view. For the animation to
3621
 * work a special CSS class called `ng-anchor` will be added to the transported element.
3622
 *
3623
 * We can now attach a transition onto the `.banner.ng-anchor` CSS class and then
3624
 * ngAnimate will handle the entire transition for us as well as the addition and removal of
3625
 * any changes of CSS classes between the elements:
3626
 *
3627
 * ```css
3628
 * .banner.ng-anchor {
3629
 *   /&#42; this animation will last for 1 second since there are
3630
 *          two phases to the animation (an `in` and an `out` phase) &#42;/
3631
 *   transition:0.5s linear all;
3632
 * }
3633
 * ```
3634
 *
3635
 * We also **must** include animations for the views that are being entered and removed
3636
 * (otherwise anchoring wouldn't be possible since the new view would be inserted right away).
3637
 *
3638
 * ```css
3639
 * .view-animation.ng-enter, .view-animation.ng-leave {
3640
 *   transition:0.5s linear all;
3641
 *   position:fixed;
3642
 *   left:0;
3643
 *   top:0;
3644
 *   width:100%;
3645
 * }
3646
 * .view-animation.ng-enter {
3647
 *   transform:translateX(100%);
3648
 * }
3649
 * .view-animation.ng-leave,
3650
 * .view-animation.ng-enter.ng-enter-active {
3651
 *   transform:translateX(0%);
3652
 * }
3653
 * .view-animation.ng-leave.ng-leave-active {
3654
 *   transform:translateX(-100%);
3655
 * }
3656
 * ```
3657
 *
3658
 * Now we can jump back to the anchor animation. When the animation happens, there are two stages that occur:
3659
 * an `out` and an `in` stage. The `out` stage happens first and that is when the element is animated away
3660
 * from its origin. Once that animation is over then the `in` stage occurs which animates the
3661
 * element to its destination. The reason why there are two animations is to give enough time
3662
 * for the enter animation on the new element to be ready.
3663
 *
3664
 * The example above sets up a transition for both the in and out phases, but we can also target the out or
3665
 * in phases directly via `ng-anchor-out` and `ng-anchor-in`.
3666
 *
3667
 * ```css
3668
 * .banner.ng-anchor-out {
3669
 *   transition: 0.5s linear all;
3670
 *
3671
 *   /&#42; the scale will be applied during the out animation,
3672
 *          but will be animated away when the in animation runs &#42;/
3673
 *   transform: scale(1.2);
3674
 * }
3675
 *
3676
 * .banner.ng-anchor-in {
3677
 *   transition: 1s linear all;
3678
 * }
3679
 * ```
3680
 *
3681
 *
3682
 *
3683
 *
3684
 * ### Anchoring Demo
3685
 *
3686
  <example module="anchoringExample"
3687
           name="anchoringExample"
3688
           id="anchoringExample"
3689
           deps="angular-animate.js;angular-route.js"
3690
           animations="true">
3691
    <file name="index.html">
3692
      <a href="#/">Home</a>
3693
      <hr />
3694
      <div class="view-container">
3695
        <div ng-view class="view"></div>
3696
      </div>
3697
    </file>
3698
    <file name="script.js">
3699
      angular.module('anchoringExample', ['ngAnimate', 'ngRoute'])
3700
        .config(['$routeProvider', function($routeProvider) {
3701
          $routeProvider.when('/', {
3702
            templateUrl: 'home.html',
3703
            controller: 'HomeController as home'
3704
          });
3705
          $routeProvider.when('/profile/:id', {
3706
            templateUrl: 'profile.html',
3707
            controller: 'ProfileController as profile'
3708
          });
3709
        }])
3710
        .run(['$rootScope', function($rootScope) {
3711
          $rootScope.records = [
3712
            { id:1, title: "Miss Beulah Roob" },
3713
            { id:2, title: "Trent Morissette" },
3714
            { id:3, title: "Miss Ava Pouros" },
3715
            { id:4, title: "Rod Pouros" },
3716
            { id:5, title: "Abdul Rice" },
3717
            { id:6, title: "Laurie Rutherford Sr." },
3718
            { id:7, title: "Nakia McLaughlin" },
3719
            { id:8, title: "Jordon Blanda DVM" },
3720
            { id:9, title: "Rhoda Hand" },
3721
            { id:10, title: "Alexandrea Sauer" }
3722
          ];
3723
        }])
3724
        .controller('HomeController', [function() {
3725
          //empty
3726
        }])
3727
        .controller('ProfileController', ['$rootScope', '$routeParams', function($rootScope, $routeParams) {
3728
          var index = parseInt($routeParams.id, 10);
3729
          var record = $rootScope.records[index - 1];
3730
3731
          this.title = record.title;
3732
          this.id = record.id;
3733
        }]);
3734
    </file>
3735
    <file name="home.html">
3736
      <h2>Welcome to the home page</h1>
3737
      <p>Please click on an element</p>
3738
      <a class="record"
3739
         ng-href="#/profile/{{ record.id }}"
3740
         ng-animate-ref="{{ record.id }}"
3741
         ng-repeat="record in records">
3742
        {{ record.title }}
3743
      </a>
3744
    </file>
3745
    <file name="profile.html">
3746
      <div class="profile record" ng-animate-ref="{{ profile.id }}">
3747
        {{ profile.title }}
3748
      </div>
3749
    </file>
3750
    <file name="animations.css">
3751
      .record {
3752
        display:block;
3753
        font-size:20px;
3754
      }
3755
      .profile {
3756
        background:black;
3757
        color:white;
3758
        font-size:100px;
3759
      }
3760
      .view-container {
3761
        position:relative;
3762
      }
3763
      .view-container > .view.ng-animate {
3764
        position:absolute;
3765
        top:0;
3766
        left:0;
3767
        width:100%;
3768
        min-height:500px;
3769
      }
3770
      .view.ng-enter, .view.ng-leave,
3771
      .record.ng-anchor {
3772
        transition:0.5s linear all;
3773
      }
3774
      .view.ng-enter {
3775
        transform:translateX(100%);
3776
      }
3777
      .view.ng-enter.ng-enter-active, .view.ng-leave {
3778
        transform:translateX(0%);
3779
      }
3780
      .view.ng-leave.ng-leave-active {
3781
        transform:translateX(-100%);
3782
      }
3783
      .record.ng-anchor-out {
3784
        background:red;
3785
      }
3786
    </file>
3787
  </example>
3788
 *
3789
 * ### How is the element transported?
3790
 *
3791
 * When an anchor animation occurs, ngAnimate will clone the starting element and position it exactly where the starting
3792
 * element is located on screen via absolute positioning. The cloned element will be placed inside of the root element
3793
 * of the application (where ng-app was defined) and all of the CSS classes of the starting element will be applied. The
3794
 * element will then animate into the `out` and `in` animations and will eventually reach the coordinates and match
3795
 * the dimensions of the destination element. During the entire animation a CSS class of `.ng-animate-shim` will be applied
3796
 * to both the starting and destination elements in order to hide them from being visible (the CSS styling for the class
3797
 * is: `visibility:hidden`). Once the anchor reaches its destination then it will be removed and the destination element
3798
 * will become visible since the shim class will be removed.
3799
 *
3800
 * ### How is the morphing handled?
3801
 *
3802
 * CSS Anchoring relies on transitions and keyframes and the internal code is intelligent enough to figure out
3803
 * what CSS classes differ between the starting element and the destination element. These different CSS classes
3804
 * will be added/removed on the anchor element and a transition will be applied (the transition that is provided
3805
 * in the anchor class). Long story short, ngAnimate will figure out what classes to add and remove which will
3806
 * make the transition of the element as smooth and automatic as possible. Be sure to use simple CSS classes that
3807
 * do not rely on DOM nesting structure so that the anchor element appears the same as the starting element (since
3808
 * the cloned element is placed inside of root element which is likely close to the body element).
3809
 *
3810
 * Note that if the root element is on the `<html>` element then the cloned node will be placed inside of body.
3811
 *
3812
 *
3813
 * ## Using $animate in your directive code
3814
 *
3815
 * So far we've explored how to feed in animations into an Angular application, but how do we trigger animations within our own directives in our application?
3816
 * By injecting the `$animate` service into our directive code, we can trigger structural and class-based hooks which can then be consumed by animations. Let's
3817
 * imagine we have a greeting box that shows and hides itself when the data changes
3818
 *
3819
 * ```html
3820
 * <greeting-box active="onOrOff">Hi there</greeting-box>
3821
 * ```
3822
 *
3823
 * ```js
3824
 * ngModule.directive('greetingBox', ['$animate', function($animate) {
3825
 *   return function(scope, element, attrs) {
3826
 *     attrs.$observe('active', function(value) {
3827
 *       value ? $animate.addClass(element, 'on') : $animate.removeClass(element, 'on');
3828
 *     });
3829
 *   });
3830
 * }]);
3831
 * ```
3832
 *
3833
 * Now the `on` CSS class is added and removed on the greeting box component. Now if we add a CSS class on top of the greeting box element
3834
 * in our HTML code then we can trigger a CSS or JS animation to happen.
3835
 *
3836
 * ```css
3837
 * /&#42; normally we would create a CSS class to reference on the element &#42;/
3838
 * greeting-box.on { transition:0.5s linear all; background:green; color:white; }
3839
 * ```
3840
 *
3841
 * The `$animate` service contains a variety of other methods like `enter`, `leave`, `animate` and `setClass`. To learn more about what's
3842
 * possible be sure to visit the {@link ng.$animate $animate service API page}.
3843
 *
3844
 *
3845
 * ### Preventing Collisions With Third Party Libraries
3846
 *
3847
 * Some third-party frameworks place animation duration defaults across many element or className
3848
 * selectors in order to make their code small and reuseable. This can lead to issues with ngAnimate, which
3849
 * is expecting actual animations on these elements and has to wait for their completion.
3850
 *
3851
 * You can prevent this unwanted behavior by using a prefix on all your animation classes:
3852
 *
3853
 * ```css
3854
 * /&#42; prefixed with animate- &#42;/
3855
 * .animate-fade-add.animate-fade-add-active {
3856
 *   transition:1s linear all;
3857
 *   opacity:0;
3858
 * }
3859
 * ```
3860
 *
3861
 * You then configure `$animate` to enforce this prefix:
3862
 *
3863
 * ```js
3864
 * $animateProvider.classNameFilter(/animate-/);
3865
 * ```
3866
 *
3867
 * This also may provide your application with a speed boost since only specific elements containing CSS class prefix
3868
 * will be evaluated for animation when any DOM changes occur in the application.
3869
 *
3870
 * ## Callbacks and Promises
3871
 *
3872
 * 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
3873
 * an animation (within our directive code) then we can continue performing directive and scope related activities after the animation has
3874
 * ended by chaining onto the returned promise that animation method returns.
3875
 *
3876
 * ```js
3877
 * // somewhere within the depths of the directive
3878
 * $animate.enter(element, parent).then(function() {
3879
 *   //the animation has completed
3880
 * });
3881
 * ```
3882
 *
3883
 * (Note that earlier versions of Angular prior to v1.4 required the promise code to be wrapped using `$scope.$apply(...)`. This is not the case
3884
 * anymore.)
3885
 *
3886
 * In addition to the animation promise, we can also make use of animation-related callbacks within our directives and controller code by registering
3887
 * an event listener using the `$animate` service. Let's say for example that an animation was triggered on our view
3888
 * routing controller to hook into that:
3889
 *
3890
 * ```js
3891
 * ngModule.controller('HomePageController', ['$animate', function($animate) {
3892
 *   $animate.on('enter', ngViewElement, function(element) {
3893
 *     // the animation for this route has completed
3894
 *   }]);
3895
 * }])
3896
 * ```
3897
 *
3898
 * (Note that you will need to trigger a digest within the callback to get angular to notice any scope-related changes.)
3899
 */
3900
3901
/**
3902
 * @ngdoc service
3903
 * @name $animate
3904
 * @kind object
3905
 *
3906
 * @description
3907
 * The ngAnimate `$animate` service documentation is the same for the core `$animate` service.
3908
 *
3909
 * Click here {@link ng.$animate to learn more about animations with `$animate`}.
3910
 */
3911
angular.module('ngAnimate', [])
3912
  .directive('ngAnimateChildren', $$AnimateChildrenDirective)
3913
  .factory('$$rAFScheduler', $$rAFSchedulerFactory)
3914
3915
  .factory('$$AnimateRunner', $$AnimateRunnerFactory)
3916
  .factory('$$animateAsyncRun', $$AnimateAsyncRunFactory)
3917
3918
  .provider('$$animateQueue', $$AnimateQueueProvider)
3919
  .provider('$$animation', $$AnimationProvider)
3920
3921
  .provider('$animateCss', $AnimateCssProvider)
3922
  .provider('$$animateCssDriver', $$AnimateCssDriverProvider)
3923
3924
  .provider('$$animateJs', $$AnimateJsProvider)
3925
  .provider('$$animateJsDriver', $$AnimateJsDriverProvider);
3926
3927
3928
})(window, window.angular);