source: trip-planner-front/node_modules/angular-animate/angular-animate.js@ 6a3a178

Last change on this file since 6a3a178 was 6a3a178, checked in by Ema <ema_spirova@…>, 3 years ago

initial commit

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