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