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