vendor/assets/javascripts/angular-animate.js in angularjs-rails-1.2.0.rc3 vs vendor/assets/javascripts/angular-animate.js in angularjs-rails-1.2.0
- old
+ new
@@ -1,53 +1,54 @@
/**
- * @license AngularJS v1.2.0-rc.3
+ * @license AngularJS v1.2.0
* (c) 2010-2012 Google, Inc. http://angularjs.org
* License: MIT
*/
(function(window, angular, undefined) {'use strict';
+/* jshint maxlen: false */
+
/**
* @ngdoc overview
* @name ngAnimate
* @description
*
* # ngAnimate
*
- * `ngAnimate` is an optional module that provides CSS and JavaScript animation hooks.
+ * The `ngAnimate` module provides support for JavaScript, CSS3 transition and CSS3 keyframe animation hooks within existing core and custom directives.
*
* {@installModule animate}
*
+ * <div doc-module-components="ngAnimate"></div>
+ *
* # Usage
*
* To see animations in action, all that is required is to define the appropriate CSS classes
- * or to register a JavaScript animation via the $animation service. The directives that support animation automatically are:
- * `ngRepeat`, `ngInclude`, `ngSwitch`, `ngShow`, `ngHide` and `ngView`. Custom directives can take advantage of animation
+ * or to register a JavaScript animation via the myModule.animation() function. The directives that support animation automatically are:
+ * `ngRepeat`, `ngInclude`, `ngIf`, `ngSwitch`, `ngShow`, `ngHide`, `ngView` and `ngClass`. Custom directives can take advantage of animation
* by using the `$animate` service.
*
* Below is a more detailed breakdown of the supported animation events provided by pre-existing ng directives:
*
* | Directive | Supported Animations |
* |---------------------------------------------------------- |----------------------------------------------------|
- * | {@link ng.directive:ngRepeat#animations ngRepeat} | enter, leave and move |
- * | {@link ngRoute.directive:ngView#animations ngView} | enter and leave |
- * | {@link ng.directive:ngInclude#animations ngInclude} | enter and leave |
- * | {@link ng.directive:ngSwitch#animations ngSwitch} | enter and leave |
- * | {@link ng.directive:ngIf#animations ngIf} | enter and leave |
- * | {@link ng.directive:ngClass#animations ngClass} | add and remove |
- * | {@link ng.directive:ngShow#animations ngShow & ngHide} | add and remove (the ng-hide class value) |
+ * | {@link ng.directive:ngRepeat#usage_animations ngRepeat} | enter, leave and move |
+ * | {@link ngRoute.directive:ngView#usage_animations ngView} | enter and leave |
+ * | {@link ng.directive:ngInclude#usage_animations ngInclude} | enter and leave |
+ * | {@link ng.directive:ngSwitch#usage_animations ngSwitch} | enter and leave |
+ * | {@link ng.directive:ngIf#usage_animations ngIf} | enter and leave |
+ * | {@link ng.directive:ngClass#usage_animations ngClass} | add and remove |
+ * | {@link ng.directive:ngShow#usage_animations ngShow & ngHide} | add and remove (the ng-hide class value) |
*
* You can find out more information about animations upon visiting each directive page.
*
* Below is an example of how to apply animations to a directive that supports animation hooks:
*
* <pre>
* <style type="text/css">
- * .slide.ng-enter > div,
- * .slide.ng-leave > div {
+ * .slide.ng-enter, .slide.ng-leave {
* -webkit-transition:0.5s linear all;
- * -moz-transition:0.5s linear all;
- * -o-transition:0.5s linear all;
* transition:0.5s linear all;
* }
*
* .slide.ng-enter { } /* starting animations for enter */
* .slide.ng-enter-active { } /* terminal animations for enter */
@@ -78,13 +79,11 @@
* The animate class is apart of the element and the ng-enter class
* is attached to the element once the enter animation event is triggered
* */
* .reveal-animation.ng-enter {
* -webkit-transition: 1s linear all; /* Safari/Chrome */
- * -moz-transition: 1s linear all; /* Firefox */
- * -o-transition: 1s linear all; /* Opera */
- * transition: 1s linear all; /* IE10+ and Future Browsers */
+ * transition: 1s linear all; /* All other modern browsers and IE10+ */
*
* /* The animation preparation code */
* opacity: 0;
* }
*
@@ -108,26 +107,16 @@
*
* <pre>
* <style type="text/css">
* .reveal-animation.ng-enter {
* -webkit-animation: enter_sequence 1s linear; /* Safari/Chrome */
- * -moz-animation: enter_sequence 1s linear; /* Firefox */
- * -o-animation: enter_sequence 1s linear; /* Opera */
* animation: enter_sequence 1s linear; /* IE10+ and Future Browsers */
* }
* @-webkit-keyframes enter_sequence {
* from { opacity:0; }
* to { opacity:1; }
* }
- * @-moz-keyframes enter_sequence {
- * from { opacity:0; }
- * to { opacity:1; }
- * }
- * @-o-keyframes enter_sequence {
- * from { opacity:0; }
- * to { opacity:1; }
- * }
* @keyframes enter_sequence {
* from { opacity:0; }
* to { opacity:1; }
* }
* </style>
@@ -144,41 +133,105 @@
* detect the CSS code to determine when the animation ends. Once the animation is over then both CSS classes will be
* removed from the DOM. If a browser does not support CSS transitions or CSS animations then the animation will start and end
* immediately resulting in a DOM element that is at its final state. This final state is when the DOM element
* has no CSS transition/animation classes applied to it.
*
+ * <h3>CSS Staggering Animations</h3>
+ * A Staggering animation is a collection of animations that are issued with a slight delay in between each successive operation resulting in a
+ * curtain-like effect. The ngAnimate module, as of 1.2.0, supports staggering animations and the stagger effect can be
+ * performed by creating a **ng-EVENT-stagger** CSS class and attaching that class to the base CSS class used for
+ * the animation. The style property expected within the stagger class can either be a **transition-delay** or an
+ * **animation-delay** property (or both if your animation contains both transitions and keyframe animations).
+ *
+ * <pre>
+ * .my-animation.ng-enter {
+ * /* standard transition code */
+ * -webkit-transition: 1s linear all;
+ * transition: 1s linear all;
+ * opacity:0;
+ * }
+ * .my-animation.ng-enter-stagger {
+ * /* this will have a 100ms delay between each successive leave animation */
+ * -webkit-transition-delay: 0.1s;
+ * transition-delay: 0.1s;
+ *
+ * /* in case the stagger doesn't work then these two values
+ * must be set to 0 to avoid an accidental CSS inheritance */
+ * -webkit-transition-duration: 0s;
+ * transition-duration: 0s;
+ * }
+ * .my-animation.ng-enter.ng-enter-active {
+ * /* standard transition styles */
+ * opacity:1;
+ * }
+ * </pre>
+ *
+ * Staggering animations work by default in ngRepeat (so long as the CSS class is defiend). Outside of ngRepeat, to use staggering animations
+ * on your own, they can be triggered by firing multiple calls to the same event on $animate. However, the restrictions surrounding this
+ * are that each of the elements must have the same CSS className value as well as the same parent element. A stagger operation
+ * will also be reset if more than 10ms has passed after the last animation has been fired.
+ *
+ * The following code will issue the **ng-leave-stagger** event on the element provided:
+ *
+ * <pre>
+ * var kids = parent.children();
+ *
+ * $animate.leave(kids[0]); //stagger index=0
+ * $animate.leave(kids[1]); //stagger index=1
+ * $animate.leave(kids[2]); //stagger index=2
+ * $animate.leave(kids[3]); //stagger index=3
+ * $animate.leave(kids[4]); //stagger index=4
+ *
+ * $timeout(function() {
+ * //stagger has reset itself
+ * $animate.leave(kids[5]); //stagger index=0
+ * $animate.leave(kids[6]); //stagger index=1
+ * }, 100, false);
+ * </pre>
+ *
+ * Stagger animations are currently only supported within CSS-defined animations.
+ *
* <h2>JavaScript-defined Animations</h2>
* In the event that you do not want to use CSS3 transitions or CSS3 animations or if you wish to offer animations on browsers that do not
* yet support CSS transitions/animations, then you can make use of JavaScript animations defined inside of your AngularJS module.
*
* <pre>
* //!annotate="YourApp" Your AngularJS Module|Replace this or ngModule with the module that you used to define your application.
* var ngModule = angular.module('YourApp', []);
* ngModule.animation('.my-crazy-animation', function() {
* return {
* enter: function(element, done) {
- * //run the animation
- * //!annotate Cancel Animation|This function (if provided) will perform the cancellation of the animation when another is triggered
- * return function(element, done) {
- * //cancel the animation
+ * //run the animation here and call done when the animation is complete
+ * return function(cancelled) {
+ * //this (optional) function will be called when the animation
+ * //completes or when the animation is cancelled (the cancelled
+ * //flag will be set to true if cancelled).
* }
* }
* leave: function(element, done) { },
* move: function(element, done) { },
- * show: function(element, done) { },
- * hide: function(element, done) { },
+ *
+ * //animation that can be triggered before the class is added
+ * beforeAddClass: function(element, className, done) { },
+ *
+ * //animation that can be triggered after the class is added
* addClass: function(element, className, done) { },
- * removeClass: function(element, className, done) { },
+ *
+ * //animation that can be triggered before the class is removed
+ * beforeRemoveClass: function(element, className, done) { },
+ *
+ * //animation that can be triggered after the class is removed
+ * removeClass: function(element, className, done) { }
* }
* });
* </pre>
*
* JavaScript-defined animations are created with a CSS-like class selector and a collection of events which are set to run
* a javascript callback function. When an animation is triggered, $animate will look for a matching animation which fits
* the element's CSS class attribute value and then run the matching animation event function (if found).
* In other words, if the CSS classes present on the animated element match any of the JavaScript animations then the callback function
- * be executed. It should be also noted that only simple class selectors are allowed.
+ * be executed. It should be also noted that only simple, single class selectors are allowed (compound class selectors are not supported).
*
* Within a JavaScript animation, an object containing various event callback animation functions is expected to be returned.
* As explained above, these callbacks are triggered based on the animation event. Therefore if an enter animation is run,
* and the JavaScript animation is found, then the enter callback will handle that animation (in addition to the CSS keyframe animation
* or transition code that is defined via a stylesheet).
@@ -190,13 +243,13 @@
/**
* @ngdoc object
* @name ngAnimate.$animateProvider
* @description
*
- * The `$AnimationProvider` allows developers to register and access custom JavaScript animations directly inside
- * of a module. When an animation is triggered, the $animate service will query the $animation function to find any
- * animations that match the provided name value.
+ * The `$animateProvider` allows developers to register JavaScript animation event handlers directly inside of a module.
+ * When an animation is triggered, the $animate service will query the $animate service to find any animations that match
+ * the provided name value.
*
* Requires the {@link ngAnimate `ngAnimate`} module to be installed.
*
* Please visit the {@link ngAnimate `ngAnimate`} module overview page learn more about how to use animations in your application.
*
@@ -204,18 +257,25 @@
.config(['$provide', '$animateProvider', function($provide, $animateProvider) {
var noop = angular.noop;
var forEach = angular.forEach;
var selectors = $animateProvider.$$selectors;
+ var ELEMENT_NODE = 1;
var NG_ANIMATE_STATE = '$$ngAnimateState';
var NG_ANIMATE_CLASS_NAME = 'ng-animate';
- var rootAnimateState = {running:true};
- $provide.decorator('$animate', ['$delegate', '$injector', '$sniffer', '$rootElement', '$timeout', '$rootScope',
- function($delegate, $injector, $sniffer, $rootElement, $timeout, $rootScope) {
-
+ var rootAnimateState = {running: true};
+
+ $provide.decorator('$animate', ['$delegate', '$injector', '$sniffer', '$rootElement', '$timeout', '$rootScope', '$document',
+ function($delegate, $injector, $sniffer, $rootElement, $timeout, $rootScope, $document) {
+
$rootElement.data(NG_ANIMATE_STATE, rootAnimateState);
+ // disable animations during bootstrap, but once we bootstrapped, enable animations
+ $rootScope.$$postDigest(function() {
+ rootAnimateState.running = false;
+ });
+
function lookup(name) {
if (name) {
var matches = [],
flagMap = {},
classes = name.substr(1).split('.');
@@ -242,16 +302,15 @@
}
/**
* @ngdoc object
* @name ngAnimate.$animate
- * @requires $timeout, $sniffer, $rootElement
* @function
*
* @description
- * The `$animate` service provides animation detection support while performing DOM operations (enter, leave and move)
- * as well as during addClass and removeClass operations. When any of these operations are run, the $animate service
+ * The `$animate` service provides animation detection support while performing DOM operations (enter, leave and move) as well as during addClass and removeClass operations.
+ * When any of these operations are run, the $animate service
* will examine any JavaScript-defined animations (which are defined by using the $animateProvider provider object)
* as well as any CSS-defined animations against the CSS classes present on the element once the DOM operation is run.
*
* The `$animate` service is used behind the scenes with pre-existing directives and animation with these directives
* will work out of the box without any extra configuration.
@@ -267,39 +326,38 @@
* @name ngAnimate.$animate#enter
* @methodOf ngAnimate.$animate
* @function
*
* @description
- * Appends the element to the parent element that resides in the document and then runs the enter animation. Once
+ * Appends the element to the parentElement element that resides in the document and then runs the enter animation. Once
* the animation is started, the following CSS classes will be present on the element for the duration of the animation:
*
* Below is a breakdown of each step that occurs during enter animation:
*
- * | Animation Step | What the element class attribute looks like |
- * |----------------------------------------------------------------------------------------------|-----------------------------------------------|
- * | 1. $animate.enter(...) is called | class="my-animation" |
- * | 2. element is inserted into the parent element or beside the after element | class="my-animation" |
- * | 3. $animate runs any JavaScript-defined animations on the element | class="my-animation" |
- * | 4. the .ng-enter class is added to the element | class="my-animation ng-enter" |
- * | 5. $animate scans the element styles to get the CSS transition/animation duration and delay | class="my-animation ng-enter" |
- * | 6. the .ng-enter-active class is added (this triggers the CSS transition/animation) | class="my-animation ng-enter ng-enter-active" |
- * | 7. $animate waits for X milliseconds for the animation to complete | class="my-animation ng-enter ng-enter-active" |
- * | 8. The animation ends and both CSS classes are removed from the element | class="my-animation" |
- * | 9. The done() callback is fired (if provided) | class="my-animation" |
+ * | Animation Step | What the element class attribute looks like |
+ * |----------------------------------------------------------------------------------------------|---------------------------------------------|
+ * | 1. $animate.enter(...) is called | class="my-animation" |
+ * | 2. element is inserted into the parentElement element or beside the afterElement element | class="my-animation" |
+ * | 3. $animate runs any JavaScript-defined animations on the element | class="my-animation ng-animate" |
+ * | 4. the .ng-enter class is added to the element | class="my-animation ng-animate ng-enter" |
+ * | 5. $animate scans the element styles to get the CSS transition/animation duration and delay | class="my-animation ng-animate ng-enter" |
+ * | 6. $animate waits for 10ms (this performs a reflow) | class="my-animation ng-animate ng-enter" |
+ * | 7. the .ng-enter-active and .ng-animate-active classes are added (this triggers the CSS transition/animation) | class="my-animation ng-animate ng-animate-active ng-enter ng-enter-active" |
+ * | 8. $animate waits for X milliseconds for the animation to complete | class="my-animation ng-animate ng-animate-active ng-enter ng-enter-active" |
+ * | 9. The animation ends and all generated CSS classes are removed from the element | class="my-animation" |
+ * | 10. The doneCallback() callback is fired (if provided) | class="my-animation" |
*
* @param {jQuery/jqLite element} element the element that will be the focus of the enter animation
- * @param {jQuery/jqLite element} parent the parent element of the element that will be the focus of the enter animation
- * @param {jQuery/jqLite element} after the sibling element (which is the previous element) of the element that will be the focus of the enter animation
- * @param {function()=} done callback function that will be called once the animation is complete
+ * @param {jQuery/jqLite element} parentElement the parent element of the element that will be the focus of the enter animation
+ * @param {jQuery/jqLite element} afterElement the sibling element (which is the previous element) of the element that will be the focus of the enter animation
+ * @param {function()=} doneCallback the callback function that will be called once the animation is complete
*/
- enter : function(element, parent, after, done) {
+ enter : function(element, parentElement, afterElement, doneCallback) {
this.enabled(false, element);
- $delegate.enter(element, parent, after);
+ $delegate.enter(element, parentElement, afterElement);
$rootScope.$$postDigest(function() {
- performAnimation('enter', 'ng-enter', element, parent, after, function() {
- done && $timeout(done, 0, false);
- });
+ performAnimation('enter', 'ng-enter', element, parentElement, afterElement, noop, doneCallback);
});
},
/**
* @ngdoc function
@@ -311,73 +369,73 @@
* Runs the leave animation operation and, upon completion, removes the element from the DOM. Once
* the animation is started, the following CSS classes will be added for the duration of the animation:
*
* Below is a breakdown of each step that occurs during enter animation:
*
- * | Animation Step | What the element class attribute looks like |
- * |----------------------------------------------------------------------------------------------|----------------------------------------------|
- * | 1. $animate.leave(...) is called | class="my-animation" |
- * | 2. $animate runs any JavaScript-defined animations on the element | class="my-animation" |
- * | 3. the .ng-leave class is added to the element | class="my-animation ng-leave" |
- * | 4. $animate scans the element styles to get the CSS transition/animation duration and delay | class="my-animation ng-leave" |
- * | 5. the .ng-leave-active class is added (this triggers the CSS transition/animation) | class="my-animation ng-leave ng-leave-active |
- * | 6. $animate waits for X milliseconds for the animation to complete | class="my-animation ng-leave ng-leave-active |
- * | 7. The animation ends and both CSS classes are removed from the element | class="my-animation" |
- * | 8. The element is removed from the DOM | ... |
- * | 9. The done() callback is fired (if provided) | ... |
+ * | Animation Step | What the element class attribute looks like |
+ * |----------------------------------------------------------------------------------------------|---------------------------------------------|
+ * | 1. $animate.leave(...) is called | class="my-animation" |
+ * | 2. $animate runs any JavaScript-defined animations on the element | class="my-animation ng-animate" |
+ * | 3. the .ng-leave class is added to the element | class="my-animation ng-animate ng-leave" |
+ * | 4. $animate scans the element styles to get the CSS transition/animation duration and delay | class="my-animation ng-animate ng-leave" |
+ * | 5. $animate waits for 10ms (this performs a reflow) | class="my-animation ng-animate ng-leave" |
+ * | 6. the .ng-leave-active and .ng-animate-active classes is added (this triggers the CSS transition/animation) | class="my-animation ng-animate ng-animate-active ng-leave ng-leave-active" |
+ * | 7. $animate waits for X milliseconds for the animation to complete | class="my-animation ng-animate ng-animate-active ng-leave ng-leave-active" |
+ * | 8. The animation ends and all generated CSS classes are removed from the element | class="my-animation" |
+ * | 9. The element is removed from the DOM | ... |
+ * | 10. The doneCallback() callback is fired (if provided) | ... |
*
* @param {jQuery/jqLite element} element the element that will be the focus of the leave animation
- * @param {function()=} done callback function that will be called once the animation is complete
+ * @param {function()=} doneCallback the callback function that will be called once the animation is complete
*/
- leave : function(element, done) {
+ leave : function(element, doneCallback) {
cancelChildAnimations(element);
this.enabled(false, element);
$rootScope.$$postDigest(function() {
performAnimation('leave', 'ng-leave', element, null, null, function() {
- $delegate.leave(element, done);
- });
+ $delegate.leave(element);
+ }, doneCallback);
});
},
/**
* @ngdoc function
* @name ngAnimate.$animate#move
* @methodOf ngAnimate.$animate
* @function
*
* @description
- * Fires the move DOM operation. Just before the animation starts, the animate service will either append it into the parent container or
- * add the element directly after the after element if present. Then the move animation will be run. Once
+ * Fires the move DOM operation. Just before the animation starts, the animate service will either append it into the parentElement container or
+ * add the element directly after the afterElement element if present. Then the move animation will be run. Once
* the animation is started, the following CSS classes will be added for the duration of the animation:
*
* Below is a breakdown of each step that occurs during move animation:
*
* | Animation Step | What the element class attribute looks like |
* |----------------------------------------------------------------------------------------------|---------------------------------------------|
* | 1. $animate.move(...) is called | class="my-animation" |
- * | 2. element is moved into the parent element or beside the after element | class="my-animation" |
- * | 3. $animate runs any JavaScript-defined animations on the element | class="my-animation" |
- * | 4. the .ng-move class is added to the element | class="my-animation ng-move" |
- * | 5. $animate scans the element styles to get the CSS transition/animation duration and delay | class="my-animation ng-move" |
- * | 6. the .ng-move-active class is added (this triggers the CSS transition/animation) | class="my-animation ng-move ng-move-active" |
- * | 7. $animate waits for X milliseconds for the animation to complete | class="my-animation ng-move ng-move-active" |
- * | 8. The animation ends and both CSS classes are removed from the element | class="my-animation" |
- * | 9. The done() callback is fired (if provided) | class="my-animation" |
+ * | 2. element is moved into the parentElement element or beside the afterElement element | class="my-animation" |
+ * | 3. $animate runs any JavaScript-defined animations on the element | class="my-animation ng-animate" |
+ * | 4. the .ng-move class is added to the element | class="my-animation ng-animate ng-move" |
+ * | 5. $animate scans the element styles to get the CSS transition/animation duration and delay | class="my-animation ng-animate ng-move" |
+ * | 6. $animate waits for 10ms (this performs a reflow) | class="my-animation ng-animate ng-move" |
+ * | 7. the .ng-move-active and .ng-animate-active classes is added (this triggers the CSS transition/animation) | class="my-animation ng-animate ng-animate-active ng-move ng-move-active" |
+ * | 8. $animate waits for X milliseconds for the animation to complete | class="my-animation ng-animate ng-animate-active ng-move ng-move-active" |
+ * | 9. The animation ends and all generated CSS classes are removed from the element | class="my-animation" |
+ * | 10. The doneCallback() callback is fired (if provided) | class="my-animation" |
*
* @param {jQuery/jqLite element} element the element that will be the focus of the move animation
- * @param {jQuery/jqLite element} parent the parent element of the element that will be the focus of the move animation
- * @param {jQuery/jqLite element} after the sibling element (which is the previous element) of the element that will be the focus of the move animation
- * @param {function()=} done callback function that will be called once the animation is complete
+ * @param {jQuery/jqLite element} parentElement the parentElement element of the element that will be the focus of the move animation
+ * @param {jQuery/jqLite element} afterElement the sibling element (which is the previous element) of the element that will be the focus of the move animation
+ * @param {function()=} doneCallback the callback function that will be called once the animation is complete
*/
- move : function(element, parent, after, done) {
+ move : function(element, parentElement, afterElement, doneCallback) {
cancelChildAnimations(element);
this.enabled(false, element);
- $delegate.move(element, parent, after);
+ $delegate.move(element, parentElement, afterElement);
$rootScope.$$postDigest(function() {
- performAnimation('move', 'ng-move', element, null, null, function() {
- done && $timeout(done, 0, false);
- });
+ performAnimation('move', 'ng-move', element, parentElement, afterElement, noop, doneCallback);
});
},
/**
* @ngdoc function
@@ -386,34 +444,35 @@
*
* @description
* Triggers a custom animation event based off the className variable and then attaches the className value to the element as a CSS class.
* Unlike the other animation methods, the animate service will suffix the className value with {@type -add} in order to provide
* the animate service the setup and active CSS classes in order to trigger the animation (this will be skipped if no CSS transitions
- * or keyframes are defined on the -add CSS class).
+ * or keyframes are defined on the -add or base CSS class).
*
* Below is a breakdown of each step that occurs during addClass animation:
*
* | Animation Step | What the element class attribute looks like |
* |------------------------------------------------------------------------------------------------|---------------------------------------------|
- * | 1. $animate.addClass(element, 'super') is called | class="" |
- * | 2. $animate runs any JavaScript-defined animations on the element | class="" |
- * | 3. the .super-add class is added to the element | class="super-add" |
- * | 4. $animate scans the element styles to get the CSS transition/animation duration and delay | class="super-add" |
- * | 5. the .super-add-active class is added (this triggers the CSS transition/animation) | class="super-add super-add-active" |
- * | 6. $animate waits for X milliseconds for the animation to complete | class="super-add super-add-active" |
- * | 7. The animation ends and both CSS classes are removed from the element | class="" |
- * | 8. The super class is added to the element | class="super" |
- * | 9. The done() callback is fired (if provided) | class="super" |
+ * | 1. $animate.addClass(element, 'super') is called | class="my-animation" |
+ * | 2. $animate runs any JavaScript-defined animations on the element | class="my-animation ng-animate" |
+ * | 3. the .super-add class are added to the element | class="my-animation ng-animate super-add" |
+ * | 4. $animate scans the element styles to get the CSS transition/animation duration and delay | class="my-animation ng-animate super-add" |
+ * | 5. $animate waits for 10ms (this performs a reflow) | class="my-animation ng-animate super-add" |
+ * | 6. the .super, .super-add-active and .ng-animate-active classes are added (this triggers the CSS transition/animation) | class="my-animation ng-animate ng-animate-active super super-add super-add-active" |
+ * | 7. $animate waits for X milliseconds for the animation to complete | class="my-animation super-add super-add-active" |
+ * | 8. The animation ends and all generated CSS classes are removed from the element | class="my-animation super" |
+ * | 9. The super class is kept on the element | class="my-animation super" |
+ * | 10. The doneCallback() callback is fired (if provided) | class="my-animation super" |
*
* @param {jQuery/jqLite element} element the element that will be animated
- * @param {string} className the CSS class that will be animated and then attached to the element
- * @param {function()=} done callback function that will be called once the animation is complete
+ * @param {string} className the CSS class that will be added to the element and then animated
+ * @param {function()=} doneCallback the callback function that will be called once the animation is complete
*/
- addClass : function(element, className, done) {
+ addClass : function(element, className, doneCallback) {
performAnimation('addClass', className, element, null, null, function() {
- $delegate.addClass(element, className, done);
- });
+ $delegate.addClass(element, className);
+ }, doneCallback);
},
/**
* @ngdoc function
* @name ngAnimate.$animate#removeClass
@@ -421,33 +480,35 @@
*
* @description
* Triggers a custom animation event based off the className variable and then removes the CSS class provided by the className value
* from the element. Unlike the other animation methods, the animate service will suffix the className value with {@type -remove} in
* order to provide the animate service the setup and active CSS classes in order to trigger the animation (this will be skipped if
- * no CSS transitions or keyframes are defined on the -remove CSS class).
+ * no CSS transitions or keyframes are defined on the -remove or base CSS classes).
*
* Below is a breakdown of each step that occurs during removeClass animation:
*
* | Animation Step | What the element class attribute looks like |
- * |-----------------------------------------------------------------------------------------------|-------------------------------------------------|
- * | 1. $animate.removeClass(element, 'super') is called | class="super" |
- * | 2. $animate runs any JavaScript-defined animations on the element | class="super" |
- * | 3. the .super-remove class is added to the element | class="super super-remove" |
- * | 4. $animate scans the element styles to get the CSS transition/animation duration and delay | class="super super-remove" |
- * | 5. the .super-remove-active class is added (this triggers the CSS transition/animation) | class="super super-remove super-remove-active" |
- * | 6. $animate waits for X milliseconds for the animation to complete | class="super super-remove super-remove-active" |
- * | 7. The animation ends and both CSS all three classes are removed from the element | class="" |
- * | 8. The done() callback is fired (if provided) | class="" |
+ * |-----------------------------------------------------------------------------------------------|---------------------------------------------|
+ * | 1. $animate.removeClass(element, 'super') is called | class="my-animation super" |
+ * | 2. $animate runs any JavaScript-defined animations on the element | class="my-animation super ng-animate" |
+ * | 3. the .super-remove class are added to the element | class="my-animation super ng-animate super-remove"|
+ * | 4. $animate scans the element styles to get the CSS transition/animation duration and delay | class="my-animation super ng-animate super-remove" |
+ * | 5. $animate waits for 10ms (this performs a reflow) | class="my-animation super ng-animate super-remove" |
+ * | 6. the .super-remove-active and .ng-animate-active classes are added and .super is removed (this triggers the CSS transition/animation) | class="my-animation ng-animate ng-animate-active super-remove super-remove-active" |
+ * | 7. $animate waits for X milliseconds for the animation to complete | class="my-animation ng-animate ng-animate-active super-remove super-remove-active" |
+ * | 8. The animation ends and all generated CSS classes are removed from the element | class="my-animation" |
+ * | 9. The doneCallback() callback is fired (if provided) | class="my-animation" |
*
+ *
* @param {jQuery/jqLite element} element the element that will be animated
* @param {string} className the CSS class that will be animated and then removed from the element
- * @param {function()=} done callback function that will be called once the animation is complete
+ * @param {function()=} doneCallback the callback function that will be called once the animation is complete
*/
- removeClass : function(element, className, done) {
+ removeClass : function(element, className, doneCallback) {
performAnimation('removeClass', className, element, null, null, function() {
- $delegate.removeClass(element, className, done);
- });
+ $delegate.removeClass(element, className);
+ }, doneCallback);
},
/**
* @ngdoc function
* @name ngAnimate.$animate#enabled
@@ -464,135 +525,214 @@
enabled : function(value, element) {
switch(arguments.length) {
case 2:
if(value) {
cleanup(element);
- }
- else {
+ } else {
var data = element.data(NG_ANIMATE_STATE) || {};
- data.structural = true;
- data.running = true;
+ data.disabled = true;
element.data(NG_ANIMATE_STATE, data);
}
break;
case 1:
- rootAnimateState.running = !value;
+ rootAnimateState.disabled = !value;
break;
default:
- value = !rootAnimateState.running
+ value = !rootAnimateState.disabled;
break;
}
return !!value;
}
};
/*
all animations call this shared animation triggering function internally.
- The event variable refers to the JavaScript animation event that will be triggered
+ The animationEvent variable refers to the JavaScript animation event that will be triggered
and the className value is the name of the animation that will be applied within the
- CSS code. Element, parent and after are provided DOM elements for the animation
+ CSS code. Element, parentElement and afterElement are provided DOM elements for the animation
and the onComplete callback will be fired once the animation is fully complete.
*/
- function performAnimation(event, className, element, parent, after, onComplete) {
+ function performAnimation(animationEvent, className, element, parentElement, afterElement, domOperation, doneCallback) {
var classes = (element.attr('class') || '') + ' ' + className;
- var animationLookup = (' ' + classes).replace(/\s+/g,'.'),
- animations = [];
- forEach(lookup(animationLookup), function(animation, index) {
- animations.push({
- start : animation[event]
- });
- });
-
- if (!parent) {
- parent = after ? after.parent() : element.parent();
+ var animationLookup = (' ' + classes).replace(/\s+/g,'.');
+ if (!parentElement) {
+ parentElement = afterElement ? afterElement.parent() : element.parent();
}
- var disabledAnimation = { running : true };
- //skip the animation if animations are disabled, a parent is already being animated
- //or the element is not currently attached to the document body.
- if ((parent.inheritedData(NG_ANIMATE_STATE) || disabledAnimation).running || animations.length == 0) {
- done();
+ var matches = lookup(animationLookup);
+ var isClassBased = animationEvent == 'addClass' || animationEvent == 'removeClass';
+ var ngAnimateState = element.data(NG_ANIMATE_STATE) || {};
+
+ //skip the animation if animations are disabled, a parent is already being animated,
+ //the element is not currently attached to the document body or then completely close
+ //the animation if any matching animations are not found at all.
+ //NOTE: IE8 + IE9 should close properly (run closeAnimation()) in case a NO animation is not found.
+ if (animationsDisabled(element, parentElement) || matches.length === 0) {
+ domOperation();
+ closeAnimation();
return;
}
- var ngAnimateState = element.data(NG_ANIMATE_STATE) || {};
+ var animations = [];
+ //only add animations if the currently running animation is not structural
+ //or if there is no animation running at all
+ if(!ngAnimateState.running || !(isClassBased && ngAnimateState.structural)) {
+ forEach(matches, function(animation) {
+ //add the animation to the queue to if it is allowed to be cancelled
+ if(!animation.allowCancel || animation.allowCancel(element, animationEvent, className)) {
+ var beforeFn, afterFn = animation[animationEvent];
- var isClassBased = event == 'addClass' || event == 'removeClass';
- if(ngAnimateState.running) {
- if(isClassBased && ngAnimateState.structural) {
- onComplete && onComplete();
- return;
- }
+ //Special case for a leave animation since there is no point in performing an
+ //animation on a element node that has already been removed from the DOM
+ if(animationEvent == 'leave') {
+ beforeFn = afterFn;
+ afterFn = null; //this must be falsy so that the animation is skipped for leave
+ } else {
+ beforeFn = animation['before' + animationEvent.charAt(0).toUpperCase() + animationEvent.substr(1)];
+ }
+ animations.push({
+ before : beforeFn,
+ after : afterFn
+ });
+ }
+ });
+ }
+ //this would mean that an animation was not allowed so let the existing
+ //animation do it's thing and close this one early
+ if(animations.length === 0) {
+ domOperation();
+ fireDoneCallbackAsync();
+ return;
+ }
+
+ if(ngAnimateState.running) {
//if an animation is currently running on the element then lets take the steps
//to cancel that animation and fire any required callbacks
- $timeout.cancel(ngAnimateState.flagTimer);
+ $timeout.cancel(ngAnimateState.closeAnimationTimeout);
+ cleanup(element);
cancelAnimations(ngAnimateState.animations);
- (ngAnimateState.done || noop)();
+ (ngAnimateState.done || noop)(true);
}
+ //There is no point in perform a class-based animation if the element already contains
+ //(on addClass) or doesn't contain (on removeClass) the className being animated.
+ //The reason why this is being called after the previous animations are cancelled
+ //is so that the CSS classes present on the element can be properly examined.
+ if((animationEvent == 'addClass' && element.hasClass(className)) ||
+ (animationEvent == 'removeClass' && !element.hasClass(className))) {
+ domOperation();
+ fireDoneCallbackAsync();
+ return;
+ }
+
+ //the ng-animate class does nothing, but it's here to allow for
+ //parent animations to find and cancel child animations when needed
+ element.addClass(NG_ANIMATE_CLASS_NAME);
+
element.data(NG_ANIMATE_STATE, {
running:true,
structural:!isClassBased,
animations:animations,
- done:done
+ done:onBeforeAnimationsComplete
});
- //the ng-animate class does nothing, but it's here to allow for
- //parent animations to find and cancel child animations when needed
- element.addClass(NG_ANIMATE_CLASS_NAME);
+ //first we run the before animations and when all of those are complete
+ //then we perform the DOM operation and run the next set of animations
+ invokeRegisteredAnimationFns(animations, 'before', onBeforeAnimationsComplete);
- forEach(animations, function(animation, index) {
- var fn = function() {
- progress(index);
- };
+ function onBeforeAnimationsComplete(cancelled) {
+ domOperation();
+ if(cancelled === true) {
+ closeAnimation();
+ return;
+ }
- if(animation.start) {
- animation.endFn = isClassBased ?
- animation.start(element, className, fn) :
- animation.start(element, fn);
- } else {
- fn();
+ //set the done function to the final done function
+ //so that the DOM event won't be executed twice by accident
+ //if the after animation is cancelled as well
+ var data = element.data(NG_ANIMATE_STATE);
+ if(data) {
+ data.done = closeAnimation;
+ element.data(NG_ANIMATE_STATE, data);
}
- });
+ invokeRegisteredAnimationFns(animations, 'after', closeAnimation);
+ }
- function progress(index) {
- animations[index].done = true;
- (animations[index].endFn || noop)();
- for(var i=0;i<animations.length;i++) {
- if(!animations[i].done) return;
+ function invokeRegisteredAnimationFns(animations, phase, allAnimationFnsComplete) {
+ var endFnName = phase + 'End';
+ forEach(animations, function(animation, index) {
+ var animationPhaseCompleted = function() {
+ progress(index, phase);
+ };
+
+ //there are no before functions for enter + move since the DOM
+ //operations happen before the performAnimation method fires
+ if(phase == 'before' && (animationEvent == 'enter' || animationEvent == 'move')) {
+ animationPhaseCompleted();
+ return;
+ }
+
+ if(animation[phase]) {
+ animation[endFnName] = isClassBased ?
+ animation[phase](element, className, animationPhaseCompleted) :
+ animation[phase](element, animationPhaseCompleted);
+ } else {
+ animationPhaseCompleted();
+ }
+ });
+
+ function progress(index, phase) {
+ var phaseCompletionFlag = phase + 'Complete';
+ var currentAnimation = animations[index];
+ currentAnimation[phaseCompletionFlag] = true;
+ (currentAnimation[endFnName] || noop)();
+
+ for(var i=0;i<animations.length;i++) {
+ if(!animations[i][phaseCompletionFlag]) return;
+ }
+
+ allAnimationFnsComplete();
}
- done();
}
- function done() {
- if(!done.hasBeenRun) {
- done.hasBeenRun = true;
+ function fireDoneCallbackAsync() {
+ doneCallback && $timeout(doneCallback, 0, false);
+ }
+
+ function closeAnimation() {
+ if(!closeAnimation.hasBeenRun) {
+ closeAnimation.hasBeenRun = true;
var data = element.data(NG_ANIMATE_STATE);
if(data) {
/* only structural animations wait for reflow before removing an
animation, but class-based animations don't. An example of this
failing would be when a parent HTML tag has a ng-class attribute
causing ALL directives below to skip animations during the digest */
if(isClassBased) {
cleanup(element);
} else {
- data.flagTimer = $timeout(function() {
+ data.closeAnimationTimeout = $timeout(function() {
cleanup(element);
}, 0, false);
element.data(NG_ANIMATE_STATE, data);
}
}
- (onComplete || noop)();
+ fireDoneCallbackAsync();
}
}
}
function cancelChildAnimations(element) {
- angular.forEach(element[0].querySelectorAll('.' + NG_ANIMATE_CLASS_NAME), function(element) {
+ var node = element[0];
+ if(node.nodeType != ELEMENT_NODE) {
+ return;
+ }
+
+ forEach(node.querySelectorAll('.' + NG_ANIMATE_CLASS_NAME), function(element) {
element = angular.element(element);
var data = element.data(NG_ANIMATE_STATE);
if(data) {
cancelAnimations(data.animations);
cleanup(element);
@@ -601,198 +741,329 @@
}
function cancelAnimations(animations) {
var isCancelledFlag = true;
forEach(animations, function(animation) {
- (animation.endFn || noop)(isCancelledFlag);
+ if(!animations['beforeComplete']) {
+ (animation.beforeEnd || noop)(isCancelledFlag);
+ }
+ if(!animations['afterComplete']) {
+ (animation.afterEnd || noop)(isCancelledFlag);
+ }
});
}
function cleanup(element) {
- element.removeClass(NG_ANIMATE_CLASS_NAME);
- element.removeData(NG_ANIMATE_STATE);
+ if(element[0] == $rootElement[0]) {
+ if(!rootAnimateState.disabled) {
+ rootAnimateState.running = false;
+ rootAnimateState.structural = false;
+ }
+ } else {
+ element.removeClass(NG_ANIMATE_CLASS_NAME);
+ element.removeData(NG_ANIMATE_STATE);
+ }
}
+
+ function animationsDisabled(element, parentElement) {
+ if (rootAnimateState.disabled) return true;
+
+ if(element[0] == $rootElement[0]) {
+ return rootAnimateState.disabled || rootAnimateState.running;
+ }
+
+ do {
+ //the element did not reach the root element which means that it
+ //is not apart of the DOM. Therefore there is no reason to do
+ //any animations on it
+ if(parentElement.length === 0) break;
+
+ var isRoot = parentElement[0] == $rootElement[0];
+ var state = isRoot ? rootAnimateState : parentElement.data(NG_ANIMATE_STATE);
+ var result = state && (!!state.disabled || !!state.running);
+ if(isRoot || result) {
+ return result;
+ }
+
+ if(isRoot) return true;
+ }
+ while(parentElement = parentElement.parent());
+
+ return true;
+ }
}]);
$animateProvider.register('', ['$window', '$sniffer', '$timeout', function($window, $sniffer, $timeout) {
- var forEach = angular.forEach;
-
// Detect proper transitionend/animationend event names.
- var transitionProp, transitionendEvent, animationProp, animationendEvent;
+ var CSS_PREFIX = '', TRANSITION_PROP, TRANSITIONEND_EVENT, ANIMATION_PROP, ANIMATIONEND_EVENT;
// If unprefixed events are not supported but webkit-prefixed are, use the latter.
// Otherwise, just use W3C names, browsers not supporting them at all will just ignore them.
// Note: Chrome implements `window.onwebkitanimationend` and doesn't implement `window.onanimationend`
// but at the same time dispatches the `animationend` event and not `webkitAnimationEnd`.
// Register both events in case `window.onanimationend` is not supported because of that,
// do the same for `transitionend` as Safari is likely to exhibit similar behavior.
// Also, the only modern browser that uses vendor prefixes for transitions/keyframes is webkit
// therefore there is no reason to test anymore for other vendor prefixes: http://caniuse.com/#search=transition
if (window.ontransitionend === undefined && window.onwebkittransitionend !== undefined) {
- transitionProp = 'WebkitTransition';
- transitionendEvent = 'webkitTransitionEnd transitionend';
+ CSS_PREFIX = '-webkit-';
+ TRANSITION_PROP = 'WebkitTransition';
+ TRANSITIONEND_EVENT = 'webkitTransitionEnd transitionend';
} else {
- transitionProp = 'transition';
- transitionendEvent = 'transitionend';
+ TRANSITION_PROP = 'transition';
+ TRANSITIONEND_EVENT = 'transitionend';
}
if (window.onanimationend === undefined && window.onwebkitanimationend !== undefined) {
- animationProp = 'WebkitAnimation';
- animationendEvent = 'webkitAnimationEnd animationend';
+ CSS_PREFIX = '-webkit-';
+ ANIMATION_PROP = 'WebkitAnimation';
+ ANIMATIONEND_EVENT = 'webkitAnimationEnd animationend';
} else {
- animationProp = 'animation';
- animationendEvent = 'animationend';
+ ANIMATION_PROP = 'animation';
+ ANIMATIONEND_EVENT = 'animationend';
}
- var durationKey = 'Duration',
- propertyKey = 'Property',
- delayKey = 'Delay',
- animationIterationCountKey = 'IterationCount',
- ELEMENT_NODE = 1;
+ var DURATION_KEY = 'Duration';
+ var PROPERTY_KEY = 'Property';
+ var DELAY_KEY = 'Delay';
+ var ANIMATION_ITERATION_COUNT_KEY = 'IterationCount';
+ var NG_ANIMATE_PARENT_KEY = '$$ngAnimateKey';
+ var NG_ANIMATE_CSS_DATA_KEY = '$$ngAnimateCSS3Data';
+ var NG_ANIMATE_FALLBACK_CLASS_NAME = 'ng-animate-start';
+ var NG_ANIMATE_FALLBACK_ACTIVE_CLASS_NAME = 'ng-animate-active';
- var NG_ANIMATE_PARENT_KEY = '$ngAnimateKey';
var lookupCache = {};
var parentCounter = 0;
var animationReflowQueue = [], animationTimer, timeOut = false;
function afterReflow(callback) {
animationReflowQueue.push(callback);
$timeout.cancel(animationTimer);
animationTimer = $timeout(function() {
- angular.forEach(animationReflowQueue, function(fn) {
+ forEach(animationReflowQueue, function(fn) {
fn();
});
animationReflowQueue = [];
animationTimer = null;
lookupCache = {};
- }, 10, false);
+ }, 10, false);
}
- function getElementAnimationDetails(element, cacheKey, onlyCheckTransition) {
- var data = lookupCache[cacheKey];
+ function applyStyle(node, style) {
+ var oldStyle = node.getAttribute('style') || '';
+ var newStyle = (oldStyle.length > 0 ? '; ' : '') + style;
+ node.setAttribute('style', newStyle);
+ return oldStyle;
+ }
+
+ function getElementAnimationDetails(element, cacheKey) {
+ var data = cacheKey ? lookupCache[cacheKey] : null;
if(!data) {
- var transitionDuration = 0, transitionDelay = 0,
- animationDuration = 0, animationDelay = 0;
+ var transitionDuration = 0;
+ var transitionDelay = 0;
+ var animationDuration = 0;
+ var animationDelay = 0;
+ var transitionDelayStyle;
+ var animationDelayStyle;
+ var transitionDurationStyle;
+ var transitionPropertyStyle;
//we want all the styles defined before and after
forEach(element, function(element) {
if (element.nodeType == ELEMENT_NODE) {
var elementStyles = $window.getComputedStyle(element) || {};
- transitionDuration = Math.max(parseMaxTime(elementStyles[transitionProp + durationKey]), transitionDuration);
+ transitionDurationStyle = elementStyles[TRANSITION_PROP + DURATION_KEY];
- if(!onlyCheckTransition) {
- transitionDelay = Math.max(parseMaxTime(elementStyles[transitionProp + delayKey]), transitionDelay);
+ transitionDuration = Math.max(parseMaxTime(transitionDurationStyle), transitionDuration);
- animationDelay = Math.max(parseMaxTime(elementStyles[animationProp + delayKey]), animationDelay);
+ transitionPropertyStyle = elementStyles[TRANSITION_PROP + PROPERTY_KEY];
- var aDuration = parseMaxTime(elementStyles[animationProp + durationKey]);
+ transitionDelayStyle = elementStyles[TRANSITION_PROP + DELAY_KEY];
- if(aDuration > 0) {
- aDuration *= parseInt(elementStyles[animationProp + animationIterationCountKey]) || 1;
- }
+ transitionDelay = Math.max(parseMaxTime(transitionDelayStyle), transitionDelay);
- animationDuration = Math.max(aDuration, animationDuration);
+ animationDelayStyle = elementStyles[ANIMATION_PROP + DELAY_KEY];
+
+ animationDelay = Math.max(parseMaxTime(animationDelayStyle), animationDelay);
+
+ var aDuration = parseMaxTime(elementStyles[ANIMATION_PROP + DURATION_KEY]);
+
+ if(aDuration > 0) {
+ aDuration *= parseInt(elementStyles[ANIMATION_PROP + ANIMATION_ITERATION_COUNT_KEY], 10) || 1;
}
+
+ animationDuration = Math.max(aDuration, animationDuration);
}
});
data = {
- transitionDelay : transitionDelay,
- animationDelay : animationDelay,
- transitionDuration : transitionDuration,
- animationDuration : animationDuration
+ total : 0,
+ transitionPropertyStyle: transitionPropertyStyle,
+ transitionDurationStyle: transitionDurationStyle,
+ transitionDelayStyle: transitionDelayStyle,
+ transitionDelay: transitionDelay,
+ transitionDuration: transitionDuration,
+ animationDelayStyle: animationDelayStyle,
+ animationDelay: animationDelay,
+ animationDuration: animationDuration
};
- lookupCache[cacheKey] = data;
+ if(cacheKey) {
+ lookupCache[cacheKey] = data;
+ }
}
return data;
}
function parseMaxTime(str) {
- var total = 0, values = angular.isString(str) ? str.split(/\s*,\s*/) : [];
+ var maxValue = 0;
+ var values = angular.isString(str) ?
+ str.split(/\s*,\s*/) :
+ [];
forEach(values, function(value) {
- total = Math.max(parseFloat(value) || 0, total);
+ maxValue = Math.max(parseFloat(value) || 0, maxValue);
});
- return total;
+ return maxValue;
}
function getCacheKey(element) {
- var parent = element.parent();
- var parentID = parent.data(NG_ANIMATE_PARENT_KEY);
+ var parentElement = element.parent();
+ var parentID = parentElement.data(NG_ANIMATE_PARENT_KEY);
if(!parentID) {
- parent.data(NG_ANIMATE_PARENT_KEY, ++parentCounter);
+ parentElement.data(NG_ANIMATE_PARENT_KEY, ++parentCounter);
parentID = parentCounter;
}
return parentID + '-' + element[0].className;
}
- function animate(element, className, done) {
-
+ function animateSetup(element, className) {
var cacheKey = getCacheKey(element);
- if(getElementAnimationDetails(element, cacheKey, true).transitionDuration > 0) {
+ var eventCacheKey = cacheKey + ' ' + className;
+ var stagger = {};
+ var ii = lookupCache[eventCacheKey] ? ++lookupCache[eventCacheKey].total : 0;
- done();
- return;
+ if(ii > 0) {
+ var staggerClassName = className + '-stagger';
+ var staggerCacheKey = cacheKey + ' ' + staggerClassName;
+ var applyClasses = !lookupCache[staggerCacheKey];
+
+ applyClasses && element.addClass(staggerClassName);
+
+ stagger = getElementAnimationDetails(element, staggerCacheKey);
+
+ applyClasses && element.removeClass(staggerClassName);
}
element.addClass(className);
- var timings = getElementAnimationDetails(element, cacheKey + ' ' + className);
+ var timings = getElementAnimationDetails(element, eventCacheKey);
/* there is no point in performing a reflow if the animation
timeout is empty (this would cause a flicker bug normally
in the page. There is also no point in performing an animation
that only has a delay and no duration */
var maxDuration = Math.max(timings.transitionDuration, timings.animationDuration);
- if(maxDuration > 0) {
- var maxDelayTime = Math.max(timings.transitionDelay, timings.animationDelay) * 1000,
- startTime = Date.now(),
- node = element[0];
+ if(maxDuration === 0) {
+ element.removeClass(className);
+ return false;
+ }
- //temporarily disable the transition so that the enter styles
- //don't animate twice (this is here to avoid a bug in Chrome/FF).
- if(timings.transitionDuration > 0) {
- node.style[transitionProp + propertyKey] = 'none';
- }
+ var node = element[0];
+ //temporarily disable the transition so that the enter styles
+ //don't animate twice (this is here to avoid a bug in Chrome/FF).
+ var activeClassName = '';
+ if(timings.transitionDuration > 0) {
+ element.addClass(NG_ANIMATE_FALLBACK_CLASS_NAME);
+ activeClassName += NG_ANIMATE_FALLBACK_ACTIVE_CLASS_NAME + ' ';
+ node.style[TRANSITION_PROP + PROPERTY_KEY] = 'none';
+ }
- var activeClassName = '';
- forEach(className.split(' '), function(klass, i) {
- activeClassName += (i > 0 ? ' ' : '') + klass + '-active';
- });
+ forEach(className.split(' '), function(klass, i) {
+ activeClassName += (i > 0 ? ' ' : '') + klass + '-active';
+ });
- // This triggers a reflow which allows for the transition animation to kick in.
- var css3AnimationEvents = animationendEvent + ' ' + transitionendEvent;
+ element.data(NG_ANIMATE_CSS_DATA_KEY, {
+ className : className,
+ activeClassName : activeClassName,
+ maxDuration : maxDuration,
+ classes : className + ' ' + activeClassName,
+ timings : timings,
+ stagger : stagger,
+ ii : ii
+ });
- afterReflow(function() {
- if(timings.transitionDuration > 0) {
- node.style[transitionProp + propertyKey] = '';
- }
- element.addClass(activeClassName);
- });
+ return true;
+ }
- element.on(css3AnimationEvents, onAnimationProgress);
+ function animateRun(element, className, activeAnimationComplete) {
+ var data = element.data(NG_ANIMATE_CSS_DATA_KEY);
+ if(!element.hasClass(className) || !data) {
+ activeAnimationComplete();
+ return;
+ }
- // This will automatically be called by $animate so
- // there is no need to attach this internally to the
- // timeout done method.
- return function onEnd(cancelled) {
- element.off(css3AnimationEvents, onAnimationProgress);
- element.removeClass(className);
- element.removeClass(activeClassName);
+ var node = element[0];
+ var timings = data.timings;
+ var stagger = data.stagger;
+ var maxDuration = data.maxDuration;
+ var activeClassName = data.activeClassName;
+ var maxDelayTime = Math.max(timings.transitionDelay, timings.animationDelay) * 1000;
+ var startTime = Date.now();
+ var css3AnimationEvents = ANIMATIONEND_EVENT + ' ' + TRANSITIONEND_EVENT;
+ var formerStyle;
+ var ii = data.ii;
- // Only when the animation is cancelled is the done()
- // function not called for this animation therefore
- // this must be also called.
- if(cancelled) {
- done();
+ var applyFallbackStyle, style = '';
+ if(timings.transitionDuration > 0) {
+ node.style[TRANSITION_PROP + PROPERTY_KEY] = '';
+
+ var propertyStyle = timings.transitionPropertyStyle;
+ if(propertyStyle.indexOf('all') == -1) {
+ applyFallbackStyle = true;
+ var fallbackProperty = $sniffer.msie ? '-ms-zoom' : 'clip';
+ style += CSS_PREFIX + 'transition-property: ' + propertyStyle + ', ' + fallbackProperty + '; ';
+ style += CSS_PREFIX + 'transition-duration: ' + timings.transitionDurationStyle + ', ' + timings.transitionDuration + 's; ';
+ }
+ }
+
+ if(ii > 0) {
+ if(stagger.transitionDelay > 0 && stagger.transitionDuration === 0) {
+ var delayStyle = timings.transitionDelayStyle;
+ if(applyFallbackStyle) {
+ delayStyle += ', ' + timings.transitionDelay + 's';
}
+
+ style += CSS_PREFIX + 'transition-delay: ' +
+ prepareStaggerDelay(delayStyle, stagger.transitionDelay, ii) + '; ';
}
+
+ if(stagger.animationDelay > 0 && stagger.animationDuration === 0) {
+ style += CSS_PREFIX + 'animation-delay: ' +
+ prepareStaggerDelay(timings.animationDelayStyle, stagger.animationDelay, ii) + '; ';
+ }
}
- else {
- element.removeClass(className);
- done();
+
+ if(style.length > 0) {
+ formerStyle = applyStyle(node, style);
}
+ element.on(css3AnimationEvents, onAnimationProgress);
+ element.addClass(activeClassName);
+
+ // This will automatically be called by $animate so
+ // there is no need to attach this internally to the
+ // timeout done method.
+ return function onEnd(cancelled) {
+ element.off(css3AnimationEvents, onAnimationProgress);
+ element.removeClass(activeClassName);
+ animateClose(element, className);
+ if(formerStyle != null) {
+ formerStyle.length > 0 ?
+ node.setAttribute('style', formerStyle) :
+ node.removeAttribute('style');
+ }
+ };
+
function onAnimationProgress(event) {
event.stopPropagation();
var ev = event.originalEvent || event;
var timeStamp = ev.$manualTimeStamp || ev.timeStamp || Date.now();
/* $manualTimeStamp is a mocked timeStamp value which is set
@@ -801,30 +1072,141 @@
* or, if they don't, then a timeStamp is automatically created for them.
* We're checking to see if the timeStamp surpasses the expected delay,
* but we're using elapsedTime instead of the timeStamp on the 2nd
* pre-condition since animations sometimes close off early */
if(Math.max(timeStamp - startTime, 0) >= maxDelayTime && ev.elapsedTime >= maxDuration) {
- done();
+ activeAnimationComplete();
}
}
+ }
+ function prepareStaggerDelay(delayStyle, staggerDelay, index) {
+ var style = '';
+ forEach(delayStyle.split(','), function(val, i) {
+ style += (i > 0 ? ',' : '') +
+ (index * staggerDelay + parseInt(val, 10)) + 's';
+ });
+ return style;
}
+ function animateBefore(element, className) {
+ if(animateSetup(element, className)) {
+ return function(cancelled) {
+ cancelled && animateClose(element, className);
+ };
+ }
+ }
+
+ function animateAfter(element, className, afterAnimationComplete) {
+ if(element.data(NG_ANIMATE_CSS_DATA_KEY)) {
+ return animateRun(element, className, afterAnimationComplete);
+ } else {
+ animateClose(element, className);
+ afterAnimationComplete();
+ }
+ }
+
+ function animate(element, className, animationComplete) {
+ //If the animateSetup function doesn't bother returning a
+ //cancellation function then it means that there is no animation
+ //to perform at all
+ var preReflowCancellation = animateBefore(element, className);
+ if(!preReflowCancellation) {
+ animationComplete();
+ return;
+ }
+
+ //There are two cancellation functions: one is before the first
+ //reflow animation and the second is during the active state
+ //animation. The first function will take care of removing the
+ //data from the element which will not make the 2nd animation
+ //happen in the first place
+ var cancel = preReflowCancellation;
+ afterReflow(function() {
+ //once the reflow is complete then we point cancel to
+ //the new cancellation function which will remove all of the
+ //animation properties from the active animation
+ cancel = animateAfter(element, className, animationComplete);
+ });
+
+ return function(cancelled) {
+ (cancel || noop)(cancelled);
+ };
+ }
+
+ function animateClose(element, className) {
+ element.removeClass(className);
+ element.removeClass(NG_ANIMATE_FALLBACK_CLASS_NAME);
+ element.removeData(NG_ANIMATE_CSS_DATA_KEY);
+ }
+
return {
- enter : function(element, done) {
- return animate(element, 'ng-enter', done);
+ allowCancel : function(element, animationEvent, className) {
+ //always cancel the current animation if it is a
+ //structural animation
+ var oldClasses = (element.data(NG_ANIMATE_CSS_DATA_KEY) || {}).classes;
+ if(!oldClasses || ['enter','leave','move'].indexOf(animationEvent) >= 0) {
+ return true;
+ }
+
+ var parentElement = element.parent();
+ var clone = angular.element(element[0].cloneNode());
+
+ //make the element super hidden and override any CSS style values
+ clone.attr('style','position:absolute; top:-9999px; left:-9999px');
+ clone.removeAttr('id');
+ clone.html('');
+
+ forEach(oldClasses.split(' '), function(klass) {
+ clone.removeClass(klass);
+ });
+
+ var suffix = animationEvent == 'addClass' ? '-add' : '-remove';
+ clone.addClass(suffixClasses(className, suffix));
+ parentElement.append(clone);
+
+ var timings = getElementAnimationDetails(clone);
+ clone.remove();
+
+ return Math.max(timings.transitionDuration, timings.animationDuration) > 0;
},
- leave : function(element, done) {
- return animate(element, 'ng-leave', done);
+
+ enter : function(element, animationCompleted) {
+ return animate(element, 'ng-enter', animationCompleted);
},
- move : function(element, done) {
- return animate(element, 'ng-move', done);
+
+ leave : function(element, animationCompleted) {
+ return animate(element, 'ng-leave', animationCompleted);
},
- addClass : function(element, className, done) {
- return animate(element, suffixClasses(className, '-add'), done);
+
+ move : function(element, animationCompleted) {
+ return animate(element, 'ng-move', animationCompleted);
},
- removeClass : function(element, className, done) {
- return animate(element, suffixClasses(className, '-remove'), done);
+
+ beforeAddClass : function(element, className, animationCompleted) {
+ var cancellationMethod = animateBefore(element, suffixClasses(className, '-add'));
+ if(cancellationMethod) {
+ afterReflow(animationCompleted);
+ return cancellationMethod;
+ }
+ animationCompleted();
+ },
+
+ addClass : function(element, className, animationCompleted) {
+ return animateAfter(element, suffixClasses(className, '-add'), animationCompleted);
+ },
+
+ beforeRemoveClass : function(element, className, animationCompleted) {
+ var cancellationMethod = animateBefore(element, suffixClasses(className, '-remove'));
+ if(cancellationMethod) {
+ afterReflow(animationCompleted);
+ return cancellationMethod;
+ }
+ animationCompleted();
+ },
+
+ removeClass : function(element, className, animationCompleted) {
+ return animateAfter(element, suffixClasses(className, '-remove'), animationCompleted);
}
};
function suffixClasses(classes, suffix) {
var className = '';