GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.

Issues (3063)

angularjs/angular-1.5.0/angular-animate.js (24 issues)

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