vendor/assets/javascripts/angular-animate.js in angular-gem-1.2.13 vs vendor/assets/javascripts/angular-animate.js in angular-gem-1.2.14
- old
+ new
@@ -1,24 +1,23 @@
/**
- * @license AngularJS v1.2.13
+ * @license AngularJS v1.2.14
* (c) 2010-2014 Google, Inc. http://angularjs.org
* License: MIT
*/
(function(window, angular, undefined) {'use strict';
/* jshint maxlen: false */
/**
- * @ngdoc overview
+ * @ngdoc module
* @name ngAnimate
* @description
*
* # ngAnimate
*
* 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
*
@@ -36,16 +35,18 @@
* | {@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) |
+ * | {@link ng.directive:form#usage_animations form} | add and remove (dirty, pristine, valid, invalid & all other validations) |
+ * | {@link ng.directive:ngModel#usage_animations ngModel} | add and remove (dirty, pristine, valid, invalid & all other validations) |
*
* 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>
+ * ```html
* <style type="text/css">
* .slide.ng-enter, .slide.ng-leave {
* -webkit-transition:0.5s linear all;
* transition:0.5s linear all;
* }
@@ -59,11 +60,11 @@
* <!--
* the animate service will automatically add .ng-enter and .ng-leave to the element
* to trigger the CSS transition/animations
* -->
* <ANY class="slide" ng-include="..."></ANY>
- * </pre>
+ * ```
*
* Keep in mind that if an animation is running, any child elements cannot be animated until the parent element's
* animation has completed.
*
* <h2>CSS-defined Animations</h2>
@@ -71,11 +72,11 @@
* are designed to contain the start and end CSS styling. Both CSS transitions and keyframe animations are supported
* and can be used to play along with this naming structure.
*
* The following code below demonstrates how to perform animations using **CSS transitions** with Angular:
*
- * <pre>
+ * ```html
* <style type="text/css">
* /*
* 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
* */
@@ -99,15 +100,15 @@
* </style>
*
* <div class="view-container">
* <div ng-view class="reveal-animation"></div>
* </div>
- * </pre>
+ * ```
*
* The following code below demonstrates how to perform animations using **CSS animations** with Angular:
*
- * <pre>
+ * ```html
* <style type="text/css">
* .reveal-animation.ng-enter {
* -webkit-animation: enter_sequence 1s linear; /* Safari/Chrome */
* animation: enter_sequence 1s linear; /* IE10+ and Future Browsers */
* }
@@ -122,11 +123,11 @@
* </style>
*
* <div class="view-container">
* <div ng-view class="reveal-animation"></div>
* </div>
- * </pre>
+ * ```
*
* Both CSS3 animations and transitions can be used together and the animate service will figure out the correct duration and delay timing.
*
* Upon DOM mutation, the event class is added first (something like `ng-enter`), then the browser prepares itself to add
* the active class (in this case `ng-enter-active`) which then triggers the animation. The animation module will automatically
@@ -140,11 +141,11 @@
* 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>
+ * ```css
* .my-animation.ng-enter {
* /* standard transition code */
* -webkit-transition: 1s linear all;
* transition: 1s linear all;
* opacity:0;
@@ -161,20 +162,20 @@
* }
* .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 defined). 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>
+ * ```js
* 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
@@ -184,19 +185,19 @@
* $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>
+ * ```js
* //!annotate="YourApp" Your AngularJS Module|Replace this or ngModule with the module that you used to define your application.
* var ngModule = angular.module('YourApp', ['ngAnimate']);
* ngModule.animation('.my-crazy-animation', function() {
* return {
* enter: function(element, done) {
@@ -221,11 +222,11 @@
*
* //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 will
@@ -239,12 +240,12 @@
*/
angular.module('ngAnimate', ['ng'])
/**
- * @ngdoc object
- * @name ngAnimate.$animateProvider
+ * @ngdoc provider
+ * @name $animateProvider
* @description
*
* 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.
@@ -252,49 +253,31 @@
* 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.
*
*/
- .factory('$$animateReflow', ['$window', '$timeout', '$document',
- function($window, $timeout, $document) {
- var bod = $document[0].body;
- var requestAnimationFrame = $window.requestAnimationFrame ||
- $window.webkitRequestAnimationFrame ||
- function(fn) {
- return $timeout(fn, 10, false);
- };
- var cancelAnimationFrame = $window.cancelAnimationFrame ||
- $window.webkitCancelAnimationFrame ||
- function(timer) {
- return $timeout.cancel(timer);
- };
+ //this private service is only used within CSS-enabled animations
+ //IE8 + IE9 do not support rAF natively, but that is fine since they
+ //also don't support transitions and keyframes which means that the code
+ //below will never be used by the two browsers.
+ .factory('$$animateReflow', ['$$rAF', '$document', function($$rAF, $document) {
+ var bod = $document[0].body;
return function(fn) {
- var id = requestAnimationFrame(function() {
+ //the returned function acts as the cancellation function
+ return $$rAF(function() {
+ //the line below will force the browser to perform a repaint
+ //so that all the animated elements within the animation frame
+ //will be properly updated and drawn on screen. This is
+ //required to perform multi-class CSS based animations with
+ //Firefox. DO NOT REMOVE THIS LINE.
var a = bod.offsetWidth + 1;
fn();
});
- return function() {
- cancelAnimationFrame(id);
- };
};
}])
- .factory('$$asyncQueueBuffer', ['$timeout', function($timeout) {
- var timer, queue = [];
- return function(fn) {
- $timeout.cancel(timer);
- queue.push(fn);
- timer = $timeout(function() {
- for(var i = 0; i < queue.length; i++) {
- queue[i]();
- }
- queue = [];
- }, 0, false);
- };
- }])
-
.config(['$provide', '$animateProvider', function($provide, $animateProvider) {
var noop = angular.noop;
var forEach = angular.forEach;
var selectors = $animateProvider.$$selectors;
@@ -318,12 +301,12 @@
function isMatchingElement(elm1, elm2) {
return extractElementNode(elm1) == extractElementNode(elm2);
}
- $provide.decorator('$animate', ['$delegate', '$injector', '$sniffer', '$rootElement', '$$asyncQueueBuffer', '$rootScope', '$document',
- function($delegate, $injector, $sniffer, $rootElement, $$asyncQueueBuffer, $rootScope, $document) {
+ $provide.decorator('$animate', ['$delegate', '$injector', '$sniffer', '$rootElement', '$$asyncCallback', '$rootScope', '$document',
+ function($delegate, $injector, $sniffer, $rootElement, $$asyncCallback, $rootScope, $document) {
var globalAnimationCounter = 0;
$rootElement.data(NG_ANIMATE_STATE, rootAnimateState);
// disable animations during bootstrap, but once we bootstrapped, wait again
@@ -370,13 +353,155 @@
}
return matches;
}
}
+ function animationRunner(element, animationEvent, className) {
+ //transcluded directives may sometimes fire an animation using only comment nodes
+ //best to catch this early on to prevent any animation operations from occurring
+ var node = element[0];
+ if(!node) {
+ return;
+ }
+
+ var isSetClassOperation = animationEvent == 'setClass';
+ var isClassBased = isSetClassOperation ||
+ animationEvent == 'addClass' ||
+ animationEvent == 'removeClass';
+
+ var classNameAdd, classNameRemove;
+ if(angular.isArray(className)) {
+ classNameAdd = className[0];
+ classNameRemove = className[1];
+ className = classNameAdd + ' ' + classNameRemove;
+ }
+
+ var currentClassName = element.attr('class');
+ var classes = currentClassName + ' ' + className;
+ if(!isAnimatableClassName(classes)) {
+ return;
+ }
+
+ var beforeComplete = noop,
+ beforeCancel = [],
+ before = [],
+ afterComplete = noop,
+ afterCancel = [],
+ after = [];
+
+ var animationLookup = (' ' + classes).replace(/\s+/g,'.');
+ forEach(lookup(animationLookup), function(animationFactory) {
+ var created = registerAnimation(animationFactory, animationEvent);
+ if(!created && isSetClassOperation) {
+ registerAnimation(animationFactory, 'addClass');
+ registerAnimation(animationFactory, 'removeClass');
+ }
+ });
+
+ function registerAnimation(animationFactory, event) {
+ var afterFn = animationFactory[event];
+ var beforeFn = animationFactory['before' + event.charAt(0).toUpperCase() + event.substr(1)];
+ if(afterFn || beforeFn) {
+ if(event == 'leave') {
+ beforeFn = afterFn;
+ //when set as null then animation knows to skip this phase
+ afterFn = null;
+ }
+ after.push({
+ event : event, fn : afterFn
+ });
+ before.push({
+ event : event, fn : beforeFn
+ });
+ return true;
+ }
+ }
+
+ function run(fns, cancellations, allCompleteFn) {
+ var animations = [];
+ forEach(fns, function(animation) {
+ animation.fn && animations.push(animation);
+ });
+
+ var count = 0;
+ function afterAnimationComplete(index) {
+ if(cancellations) {
+ (cancellations[index] || noop)();
+ if(++count < animations.length) return;
+ cancellations = null;
+ }
+ allCompleteFn();
+ }
+
+ //The code below adds directly to the array in order to work with
+ //both sync and async animations. Sync animations are when the done()
+ //operation is called right away. DO NOT REFACTOR!
+ forEach(animations, function(animation, index) {
+ var progress = function() {
+ afterAnimationComplete(index);
+ };
+ switch(animation.event) {
+ case 'setClass':
+ cancellations.push(animation.fn(element, classNameAdd, classNameRemove, progress));
+ break;
+ case 'addClass':
+ cancellations.push(animation.fn(element, classNameAdd || className, progress));
+ break;
+ case 'removeClass':
+ cancellations.push(animation.fn(element, classNameRemove || className, progress));
+ break;
+ default:
+ cancellations.push(animation.fn(element, progress));
+ break;
+ }
+ });
+
+ if(cancellations && cancellations.length === 0) {
+ allCompleteFn();
+ }
+ }
+
+ return {
+ node : node,
+ event : animationEvent,
+ className : className,
+ isClassBased : isClassBased,
+ isSetClassOperation : isSetClassOperation,
+ before : function(allCompleteFn) {
+ beforeComplete = allCompleteFn;
+ run(before, beforeCancel, function() {
+ beforeComplete = noop;
+ allCompleteFn();
+ });
+ },
+ after : function(allCompleteFn) {
+ afterComplete = allCompleteFn;
+ run(after, afterCancel, function() {
+ afterComplete = noop;
+ allCompleteFn();
+ });
+ },
+ cancel : function() {
+ if(beforeCancel) {
+ forEach(beforeCancel, function(cancelFn) {
+ (cancelFn || noop)(true);
+ });
+ beforeComplete(true);
+ }
+ if(afterCancel) {
+ forEach(afterCancel, function(cancelFn) {
+ (cancelFn || noop)(true);
+ });
+ afterComplete(true);
+ }
+ }
+ };
+ }
+
/**
- * @ngdoc object
- * @name ngAnimate.$animate
+ * @ngdoc service
+ * @name $animate
* @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
@@ -391,13 +516,12 @@
* Please visit the {@link ngAnimate `ngAnimate`} module overview page learn more about how to use animations in your application.
*
*/
return {
/**
- * @ngdoc function
- * @name ngAnimate.$animate#enter
- * @methodOf ngAnimate.$animate
+ * @ngdoc method
+ * @name $animate#enter
* @function
*
* @description
* 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:
@@ -415,13 +539,13 @@
* | 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} 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 {DOMElement} element the element that will be the focus of the enter animation
+ * @param {DOMElement} parentElement the parent element of the element that will be the focus of the enter animation
+ * @param {DOMElement} 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, parentElement, afterElement, doneCallback) {
this.enabled(false, element);
$delegate.enter(element, parentElement, afterElement);
@@ -430,13 +554,12 @@
performAnimation('enter', 'ng-enter', element, parentElement, afterElement, noop, doneCallback);
});
},
/**
- * @ngdoc function
- * @name ngAnimate.$animate#leave
- * @methodOf ngAnimate.$animate
+ * @ngdoc method
+ * @name $animate#leave
* @function
*
* @description
* 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:
@@ -454,28 +577,26 @@
* | 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 {DOMElement} element the element that will be the focus of the leave animation
* @param {function()=} doneCallback the callback function that will be called once the animation is complete
*/
leave : function(element, doneCallback) {
cancelChildAnimations(element);
this.enabled(false, element);
$rootScope.$$postDigest(function() {
- element = stripCommentsFromElement(element);
- performAnimation('leave', 'ng-leave', element, null, null, function() {
+ performAnimation('leave', 'ng-leave', stripCommentsFromElement(element), null, null, function() {
$delegate.leave(element);
}, doneCallback);
});
},
/**
- * @ngdoc function
- * @name ngAnimate.$animate#move
- * @methodOf ngAnimate.$animate
+ * @ngdoc method
+ * @name $animate#move
* @function
*
* @description
* 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
@@ -494,13 +615,13 @@
* | 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} 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 {DOMElement} element the element that will be the focus of the move animation
+ * @param {DOMElement} parentElement the parentElement element of the element that will be the focus of the move animation
+ * @param {DOMElement} 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, parentElement, afterElement, doneCallback) {
cancelChildAnimations(element);
this.enabled(false, element);
@@ -510,13 +631,12 @@
performAnimation('move', 'ng-move', element, parentElement, afterElement, noop, doneCallback);
});
},
/**
- * @ngdoc function
- * @name ngAnimate.$animate#addClass
- * @methodOf ngAnimate.$animate
+ * @ngdoc method
+ * @name $animate#addClass
*
* @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
@@ -530,16 +650,16 @@
* | 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" |
+ * | 7. $animate waits for X milliseconds for the animation to complete | class="my-animation super 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 {DOMElement} element the element that will be animated
* @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, doneCallback) {
element = stripCommentsFromElement(element);
@@ -547,13 +667,12 @@
$delegate.addClass(element, className);
}, doneCallback);
},
/**
- * @ngdoc function
- * @name ngAnimate.$animate#removeClass
- * @methodOf ngAnimate.$animate
+ * @ngdoc method
+ * @name $animate#removeClass
*
* @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
@@ -572,11 +691,11 @@
* | 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 {DOMElement} 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()=} doneCallback the callback function that will be called once the animation is complete
*/
removeClass : function(element, className, doneCallback) {
element = stripCommentsFromElement(element);
@@ -591,32 +710,31 @@
* @name ng.$animate#setClass
* @methodOf ng.$animate
* @function
* @description Adds and/or removes the given CSS classes to and from the element.
* Once complete, the done() callback will be fired (if provided).
- * @param {jQuery/jqLite element} element the element which will it's CSS classes changed
+ * @param {DOMElement} element the element which will it's CSS classes changed
* removed from it
* @param {string} add the CSS classes which will be added to the element
* @param {string} remove the CSS class which will be removed from the element
- * @param {function=} done the callback function (if provided) that will be fired after the
+ * @param {Function=} done the callback function (if provided) that will be fired after the
* CSS classes have been set on the element
*/
setClass : function(element, add, remove, doneCallback) {
element = stripCommentsFromElement(element);
performAnimation('setClass', [add, remove], element, null, null, function() {
$delegate.setClass(element, add, remove);
}, doneCallback);
},
/**
- * @ngdoc function
- * @name ngAnimate.$animate#enabled
- * @methodOf ngAnimate.$animate
+ * @ngdoc method
+ * @name $animate#enabled
* @function
*
* @param {boolean=} value If provided then set the animation on or off.
- * @param {jQuery/jqLite element=} element If provided then the element will be used to represent the enable/disable operation
+ * @param {DOMElement=} element If provided then the element will be used to represent the enable/disable operation
* @return {boolean} Current animation state.
*
* @description
* Globally enables/disables animations.
*
@@ -652,107 +770,54 @@
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(animationEvent, className, element, parentElement, afterElement, domOperation, doneCallback) {
- var classNameAdd, classNameRemove, setClassOperation = animationEvent == 'setClass';
- if(setClassOperation) {
- classNameAdd = className[0];
- classNameRemove = className[1];
- className = classNameAdd + ' ' + classNameRemove;
- }
-
- var currentClassName, classes, node = element[0];
- if(node) {
- currentClassName = node.className;
- classes = currentClassName + ' ' + className;
- }
-
- //transcluded directives may sometimes fire an animation using only comment nodes
- //best to catch this early on to prevent any animation operations from occurring
- if(!node || !isAnimatableClassName(classes)) {
+ var runner = animationRunner(element, animationEvent, className);
+ if(!runner) {
fireDOMOperation();
fireBeforeCallbackAsync();
fireAfterCallbackAsync();
fireDoneCallbackAsync();
return;
}
- var elementEvents = angular.element._data(node);
+ className = runner.className;
+ var elementEvents = angular.element._data(runner.node);
elementEvents = elementEvents && elementEvents.events;
- var animationLookup = (' ' + classes).replace(/\s+/g,'.');
if (!parentElement) {
parentElement = afterElement ? afterElement.parent() : element.parent();
}
- var matches = lookup(animationLookup);
- var isClassBased = animationEvent == 'addClass' ||
- animationEvent == 'removeClass' ||
- setClassOperation;
var ngAnimateState = element.data(NG_ANIMATE_STATE) || {};
-
var runningAnimations = ngAnimateState.active || {};
var totalActiveAnimations = ngAnimateState.totalActive || 0;
var lastAnimation = ngAnimateState.last;
+ //only allow animations if the currently running animation is not structural
+ //or if there is no animation running at all
+ var skipAnimations = runner.isClassBased ?
+ ngAnimateState.disabled || (lastAnimation && !lastAnimation.isClassBased) :
+ false;
+
//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) {
+ //NOTE: IE8 + IE9 should close properly (run closeAnimation()) in case an animation was found.
+ if (skipAnimations || animationsDisabled(element, parentElement)) {
fireDOMOperation();
fireBeforeCallbackAsync();
fireAfterCallbackAsync();
closeAnimation();
return;
}
- var animations = [];
-
- //only add animations if the currently running animation is not structural
- //or if there is no animation running at all
- var allowAnimations = isClassBased ?
- !ngAnimateState.disabled && (!lastAnimation || lastAnimation.classBased) :
- true;
-
- if(allowAnimations) {
- 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];
-
- //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) {
- fireDOMOperation();
- fireBeforeCallbackAsync();
- fireAfterCallbackAsync();
- fireDoneCallbackAsync();
- return;
- }
-
var skipAnimation = false;
if(totalActiveAnimations > 0) {
var animationsToCancel = [];
- if(!isClassBased) {
+ if(!runner.isClassBased) {
if(animationEvent == 'leave' && runningAnimations['ng-leave']) {
skipAnimation = true;
} else {
//cancel all animations when a structural animation takes place
for(var klass in runningAnimations) {
@@ -775,123 +840,81 @@
cleanup(element, className);
}
}
if(animationsToCancel.length > 0) {
- angular.forEach(animationsToCancel, function(operation) {
- (operation.done || noop)(true);
- cancelAnimations(operation.animations);
+ forEach(animationsToCancel, function(operation) {
+ operation.cancel();
});
}
}
- if(isClassBased && !setClassOperation && !skipAnimation) {
+ if(runner.isClassBased && !runner.isSetClassOperation && !skipAnimation) {
skipAnimation = (animationEvent == 'addClass') == element.hasClass(className); //opposite of XOR
}
if(skipAnimation) {
fireBeforeCallbackAsync();
fireAfterCallbackAsync();
fireDoneCallbackAsync();
return;
}
+ if(animationEvent == 'leave') {
+ //there's no need to ever remove the listener since the element
+ //will be removed (destroyed) after the leave animation ends or
+ //is cancelled midway
+ element.one('$destroy', function(e) {
+ var element = angular.element(this);
+ var state = element.data(NG_ANIMATE_STATE);
+ if(state) {
+ var activeLeaveAnimation = state.active['ng-leave'];
+ if(activeLeaveAnimation) {
+ activeLeaveAnimation.cancel();
+ cleanup(element, 'ng-leave');
+ }
+ }
+ });
+ }
+
//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);
var localAnimationCount = globalAnimationCounter++;
- lastAnimation = {
- classBased : isClassBased,
- event : animationEvent,
- animations : animations,
- done:onBeforeAnimationsComplete
- };
-
totalActiveAnimations++;
- runningAnimations[className] = lastAnimation;
+ runningAnimations[className] = runner;
element.data(NG_ANIMATE_STATE, {
- last : lastAnimation,
+ last : runner,
active : runningAnimations,
index : localAnimationCount,
totalActive : totalActiveAnimations
});
//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);
-
- function onBeforeAnimationsComplete(cancelled) {
+ fireBeforeCallbackAsync();
+ runner.before(function(cancelled) {
var data = element.data(NG_ANIMATE_STATE);
cancelled = cancelled ||
- !data || !data.active[className] ||
- (isClassBased && data.active[className].event != animationEvent);
+ !data || !data.active[className] ||
+ (runner.isClassBased && data.active[className].event != animationEvent);
fireDOMOperation();
if(cancelled === true) {
closeAnimation();
- return;
+ } else {
+ fireAfterCallbackAsync();
+ runner.after(closeAnimation);
}
+ });
- //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 currentAnimation = data.active[className];
- currentAnimation.done = closeAnimation;
- invokeRegisteredAnimationFns(animations, 'after', closeAnimation);
- }
-
- function invokeRegisteredAnimationFns(animations, phase, allAnimationFnsComplete) {
- phase == 'after' ?
- fireAfterCallbackAsync() :
- fireBeforeCallbackAsync();
-
- 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]) {
- if(setClassOperation) {
- animation[endFnName] = animation[phase](element, classNameAdd, classNameRemove, animationPhaseCompleted);
- } else {
- 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();
- }
- }
-
function fireDOMCallback(animationPhase) {
var eventName = '$animate:' + animationPhase;
if(elementEvents && elementEvents[eventName] && elementEvents[eventName].length > 0) {
- $$asyncQueueBuffer(function() {
+ $$asyncCallback(function() {
element.triggerHandler(eventName, {
event : animationEvent,
className : className
});
});
@@ -907,17 +930,17 @@
}
function fireDoneCallbackAsync() {
fireDOMCallback('close');
if(doneCallback) {
- $$asyncQueueBuffer(function() {
+ $$asyncCallback(function() {
doneCallback();
});
}
}
- //it is less complicated to use a flag than managing and cancelling
+ //it is less complicated to use a flag than managing and canceling
//timeouts containing multiple callbacks.
function fireDOMOperation() {
if(!fireDOMOperation.hasBeenRun) {
fireDOMOperation.hasBeenRun = true;
domOperation();
@@ -931,14 +954,14 @@
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) {
+ if(runner.isClassBased) {
cleanup(element, className);
} else {
- $$asyncQueueBuffer(function() {
+ $$asyncCallback(function() {
var data = element.data(NG_ANIMATE_STATE) || {};
if(localAnimationCount == data.index) {
cleanup(element, className, animationEvent);
}
});
@@ -950,49 +973,39 @@
}
}
function cancelChildAnimations(element) {
var node = extractElementNode(element);
- forEach(node.querySelectorAll('.' + NG_ANIMATE_CLASS_NAME), function(element) {
- element = angular.element(element);
- var data = element.data(NG_ANIMATE_STATE);
- if(data && data.active) {
- angular.forEach(data.active, function(operation) {
- (operation.done || noop)(true);
- cancelAnimations(operation.animations);
- });
- }
- });
+ if (node) {
+ var nodes = angular.isFunction(node.getElementsByClassName) ?
+ node.getElementsByClassName(NG_ANIMATE_CLASS_NAME) :
+ node.querySelectorAll('.' + NG_ANIMATE_CLASS_NAME);
+ forEach(nodes, function(element) {
+ element = angular.element(element);
+ var data = element.data(NG_ANIMATE_STATE);
+ if(data && data.active) {
+ forEach(data.active, function(runner) {
+ runner.cancel();
+ });
+ }
+ });
+ }
}
- function cancelAnimations(animations) {
- var isCancelledFlag = true;
- forEach(animations, function(animation) {
- if(!animation.beforeComplete) {
- (animation.beforeEnd || noop)(isCancelledFlag);
- }
- if(!animation.afterComplete) {
- (animation.afterEnd || noop)(isCancelledFlag);
- }
- });
- }
-
function cleanup(element, className) {
if(isMatchingElement(element, $rootElement)) {
if(!rootAnimateState.disabled) {
rootAnimateState.running = false;
rootAnimateState.structural = false;
}
} else if(className) {
var data = element.data(NG_ANIMATE_STATE) || {};
var removeAnimations = className === true;
- if(!removeAnimations) {
- if(data.active && data.active[className]) {
- data.totalActive--;
- delete data.active[className];
- }
+ if(!removeAnimations && data.active && data.active[className]) {
+ data.totalActive--;
+ delete data.active[className];
}
if(removeAnimations || !data.totalActive) {
element.removeClass(NG_ANIMATE_CLASS_NAME);
element.removeData(NG_ANIMATE_STATE);
@@ -1092,21 +1105,26 @@
var closingTimer = null;
var closingTimestamp = 0;
var animationElementQueue = [];
function animationCloseHandler(element, totalTime) {
+ var node = extractElementNode(element);
+ element = angular.element(node);
+
+ //this item will be garbage collected by the closing
+ //animation timeout
+ animationElementQueue.push(element);
+
+ //but it may not need to cancel out the existing timeout
+ //if the timestamp is less than the previous one
var futureTimestamp = Date.now() + (totalTime * 1000);
if(futureTimestamp <= closingTimestamp) {
return;
}
$timeout.cancel(closingTimer);
- var node = extractElementNode(element);
- element = angular.element(node);
- animationElementQueue.push(element);
-
closingTimestamp = futureTimestamp;
closingTimer = $timeout(function() {
closeAllAnimations(animationElementQueue);
animationElementQueue = [];
}, totalTime, false);
@@ -1241,19 +1259,26 @@
element.data(NG_ANIMATE_CSS_DATA_KEY, {
running : formerData.running || 0,
itemIndex : itemIndex,
stagger : stagger,
timings : timings,
- closeAnimationFn : angular.noop
+ closeAnimationFn : noop
});
//temporarily disable the transition so that the enter styles
//don't animate twice (this is here to avoid a bug in Chrome/FF).
var isCurrentlyAnimating = formerData.running > 0 || animationEvent == 'setClass';
if(transitionDuration > 0) {
blockTransitions(element, className, isCurrentlyAnimating);
}
- if(animationDuration > 0) {
+
+ //staggering keyframe animations work by adjusting the `animation-delay` CSS property
+ //on the given element, however, the delay value can only calculated after the reflow
+ //since by that time $animate knows how many elements are being animated. Therefore,
+ //until the reflow occurs the element needs to be blocked (where the keyframe animation
+ //is set to `none 0s`). This blocking mechanism should only be set for when a stagger
+ //animation is detected and when the element item index is greater than 0.
+ if(animationDuration > 0 && stagger.animationDelay > 0 && stagger.animationDuration === 0) {
blockKeyframeAnimations(element);
}
return true;
}