Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/matomo-org/matomo.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'libs/angularjs/angular-animate.js')
-rwxr-xr-xlibs/angularjs/angular-animate.js688
1 files changed, 394 insertions, 294 deletions
diff --git a/libs/angularjs/angular-animate.js b/libs/angularjs/angular-animate.js
index d8a7855ae0..61befa2a04 100755
--- a/libs/angularjs/angular-animate.js
+++ b/libs/angularjs/angular-animate.js
@@ -1,5 +1,5 @@
/**
- * @license AngularJS v1.2.13
+ * @license AngularJS v1.2.25
* (c) 2010-2014 Google, Inc. http://angularjs.org
* License: MIT
*/
@@ -8,7 +8,7 @@
/* jshint maxlen: false */
/**
- * @ngdoc overview
+ * @ngdoc module
* @name ngAnimate
* @description
*
@@ -16,7 +16,6 @@
*
* 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>
*
@@ -38,12 +37,14 @@
* | {@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;
@@ -51,9 +52,9 @@
* }
*
* .slide.ng-enter { } /&#42; starting animations for enter &#42;/
- * .slide.ng-enter-active { } /&#42; terminal animations for enter &#42;/
+ * .slide.ng-enter.ng-enter-active { } /&#42; terminal animations for enter &#42;/
* .slide.ng-leave { } /&#42; starting animations for leave &#42;/
- * .slide.ng-leave-active { } /&#42; terminal animations for leave &#42;/
+ * .slide.ng-leave.ng-leave-active { } /&#42; terminal animations for leave &#42;/
* </style>
*
* <!--
@@ -61,10 +62,24 @@
* 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.
+ * Keep in mind that, by default, if an animation is running, any child elements cannot be animated
+ * until the parent element's animation has completed. This blocking feature can be overridden by
+ * placing the `ng-animate-children` attribute on a parent container tag.
+ *
+ * ```html
+ * <div class="slide-animation" ng-if="on" ng-animate-children>
+ * <div class="fade-animation" ng-if="on">
+ * <div class="explode-animation" ng-if="on">
+ * ...
+ * </div>
+ * </div>
+ * </div>
+ * ```
+ *
+ * When the `on` expression value changes and an animation is triggered then each of the elements within
+ * will all animate without the block being applied to child elements.
*
* <h2>CSS-defined Animations</h2>
* The animate service will automatically apply two CSS classes to the animated element and these two CSS classes
@@ -73,7 +88,7 @@
*
* The following code below demonstrates how to perform animations using **CSS transitions** with Angular:
*
- * <pre>
+ * ```html
* <style type="text/css">
* /&#42;
* The animate class is apart of the element and the ng-enter class
@@ -101,21 +116,21 @@
* <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; /&#42; Safari/Chrome &#42;/
* animation: enter_sequence 1s linear; /&#42; IE10+ and Future Browsers &#42;/
* }
- * &#64-webkit-keyframes enter_sequence {
+ * @-webkit-keyframes enter_sequence {
* from { opacity:0; }
* to { opacity:1; }
* }
- * &#64keyframes enter_sequence {
+ * @keyframes enter_sequence {
* from { opacity:0; }
* to { opacity:1; }
* }
@@ -124,7 +139,7 @@
* <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.
*
@@ -142,7 +157,7 @@
* 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 {
* /&#42; standard transition code &#42;/
* -webkit-transition: 1s linear all;
@@ -163,7 +178,7 @@
* /&#42; standard transition styles &#42;/
* 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
@@ -172,7 +187,7 @@
*
* 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
@@ -186,7 +201,7 @@
* $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.
*
@@ -194,7 +209,7 @@
* 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() {
@@ -223,7 +238,7 @@
* 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
@@ -241,8 +256,8 @@
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.
@@ -254,42 +269,37 @@ angular.module('ngAnimate', ['ng'])
* 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) {
+ .directive('ngAnimateChildren', function() {
+ var NG_ANIMATE_CHILDREN = '$$ngAnimateChildren';
+ return function(scope, element, attrs) {
+ var val = attrs.ngAnimateChildren;
+ if(angular.isString(val) && val.length === 0) { //empty attribute
+ element.data(NG_ANIMATE_CHILDREN, true);
+ } else {
+ scope.$watch(val, function(value) {
+ element.data(NG_ANIMATE_CHILDREN, !!value);
+ });
+ }
+ };
+ })
+
+ //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;
- 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);
- };
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);
};
}])
@@ -300,6 +310,7 @@ angular.module('ngAnimate', ['ng'])
var ELEMENT_NODE = 1;
var NG_ANIMATE_STATE = '$$ngAnimateState';
+ var NG_ANIMATE_CHILDREN = '$$ngAnimateChildren';
var NG_ANIMATE_CLASS_NAME = 'ng-animate';
var rootAnimateState = {running: true};
@@ -312,6 +323,10 @@ angular.module('ngAnimate', ['ng'])
}
}
+ function prepareElement(element) {
+ return element && angular.element(element);
+ }
+
function stripCommentsFromElement(element) {
return angular.element(extractElementNode(element));
}
@@ -320,8 +335,8 @@ angular.module('ngAnimate', ['ng'])
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);
@@ -345,6 +360,12 @@ angular.module('ngAnimate', ['ng'])
return classNameFilter.test(className);
};
+ function blockElementAnimations(element) {
+ var data = element.data(NG_ANIMATE_STATE) || {};
+ data.running = true;
+ element.data(NG_ANIMATE_STATE, data);
+ }
+
function lookup(name) {
if (name) {
var matches = [],
@@ -355,9 +376,12 @@ angular.module('ngAnimate', ['ng'])
//operation which performs CSS transition and keyframe
//animations sniffing. This is always included for each
//element animation procedure if the browser supports
- //transitions and/or keyframe animations
+ //transitions and/or keyframe animations. The default
+ //animation is added to the top of the list to prevent
+ //any previous animations from affecting the element styling
+ //prior to the element being animated.
if ($sniffer.transitions || $sniffer.animations) {
- classes.push('');
+ matches.push($injector.get(selectors['']));
}
for(var i=0; i < classes.length; i++) {
@@ -372,10 +396,152 @@ angular.module('ngAnimate', ['ng'])
}
}
+ 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
- * @function
+ * @ngdoc service
+ * @name $animate
+ * @kind 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.
@@ -393,10 +559,9 @@ angular.module('ngAnimate', ['ng'])
*/
return {
/**
- * @ngdoc function
- * @name ngAnimate.$animate#enter
- * @methodOf ngAnimate.$animate
- * @function
+ * @ngdoc method
+ * @name $animate#enter
+ * @kind function
*
* @description
* Appends the element to the parentElement element that resides in the document and then runs the enter animation. Once
@@ -417,13 +582,17 @@ angular.module('ngAnimate', ['ng'])
* | 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);
+ element = angular.element(element);
+ parentElement = prepareElement(parentElement);
+ afterElement = prepareElement(afterElement);
+
+ blockElementAnimations(element);
$delegate.enter(element, parentElement, afterElement);
$rootScope.$$postDigest(function() {
element = stripCommentsFromElement(element);
@@ -432,10 +601,9 @@ angular.module('ngAnimate', ['ng'])
},
/**
- * @ngdoc function
- * @name ngAnimate.$animate#leave
- * @methodOf ngAnimate.$animate
- * @function
+ * @ngdoc method
+ * @name $animate#leave
+ * @kind function
*
* @description
* Runs the leave animation operation and, upon completion, removes the element from the DOM. Once
@@ -456,25 +624,24 @@ angular.module('ngAnimate', ['ng'])
* | 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) {
+ element = angular.element(element);
cancelChildAnimations(element);
- this.enabled(false, element);
+ blockElementAnimations(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
- * @function
+ * @ngdoc method
+ * @name $animate#move
+ * @kind function
*
* @description
* Fires the move DOM operation. Just before the animation starts, the animate service will either append it into the parentElement container or
@@ -496,14 +663,18 @@ angular.module('ngAnimate', ['ng'])
* | 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) {
+ element = angular.element(element);
+ parentElement = prepareElement(parentElement);
+ afterElement = prepareElement(afterElement);
+
cancelChildAnimations(element);
- this.enabled(false, element);
+ blockElementAnimations(element);
$delegate.move(element, parentElement, afterElement);
$rootScope.$$postDigest(function() {
element = stripCommentsFromElement(element);
@@ -512,9 +683,8 @@ angular.module('ngAnimate', ['ng'])
},
/**
- * @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.
@@ -532,16 +702,17 @@ angular.module('ngAnimate', ['ng'])
* | 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 = angular.element(element);
element = stripCommentsFromElement(element);
performAnimation('addClass', className, element, null, null, function() {
$delegate.addClass(element, className);
@@ -549,9 +720,8 @@ angular.module('ngAnimate', ['ng'])
},
/**
- * @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
@@ -574,11 +744,12 @@ angular.module('ngAnimate', ['ng'])
* | 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 = angular.element(element);
element = stripCommentsFromElement(element);
performAnimation('removeClass', className, element, null, null, function() {
$delegate.removeClass(element, className);
@@ -588,19 +759,19 @@ angular.module('ngAnimate', ['ng'])
/**
*
* @ngdoc function
- * @name ng.$animate#setClass
- * @methodOf ng.$animate
+ * @name $animate#setClass
* @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 its 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 = angular.element(element);
element = stripCommentsFromElement(element);
performAnimation('setClass', [add, remove], element, null, null, function() {
$delegate.setClass(element, add, remove);
@@ -608,13 +779,12 @@ angular.module('ngAnimate', ['ng'])
},
/**
- * @ngdoc function
- * @name ngAnimate.$animate#enabled
- * @methodOf ngAnimate.$animate
- * @function
+ * @ngdoc method
+ * @name $animate#enabled
+ * @kind 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
@@ -654,52 +824,42 @@ angular.module('ngAnimate', ['ng'])
*/
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();
+ closeAnimation();
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;
+ if (runner.isClassBased) {
+ skipAnimations = ngAnimateState.running ||
+ ngAnimateState.disabled ||
+ (lastAnimation && !lastAnimation.isClassBased);
+ }
+
//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();
@@ -707,50 +867,10 @@ angular.module('ngAnimate', ['ng'])
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 {
@@ -777,41 +897,51 @@ angular.module('ngAnimate', ['ng'])
}
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) {
+ fireDOMOperation();
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
@@ -819,77 +949,26 @@ angular.module('ngAnimate', ['ng'])
//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;
- }
-
- //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();
+ } else {
+ fireAfterCallbackAsync();
+ runner.after(closeAnimation);
}
- }
+ });
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
@@ -909,13 +988,13 @@ angular.module('ngAnimate', ['ng'])
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) {
@@ -933,10 +1012,10 @@ angular.module('ngAnimate', ['ng'])
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 && 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);
@@ -952,28 +1031,20 @@ angular.module('ngAnimate', ['ng'])
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);
- });
- }
- });
- }
-
- function cancelAnimations(animations) {
- var isCancelledFlag = true;
- forEach(animations, function(animation) {
- if(!animation.beforeComplete) {
- (animation.beforeEnd || noop)(isCancelledFlag);
- }
- if(!animation.afterComplete) {
- (animation.afterEnd || noop)(isCancelledFlag);
- }
- });
+ 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 cleanup(element, className) {
@@ -986,11 +1057,9 @@ angular.module('ngAnimate', ['ng'])
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) {
@@ -1001,30 +1070,49 @@ angular.module('ngAnimate', ['ng'])
}
function animationsDisabled(element, parentElement) {
- if (rootAnimateState.disabled) return true;
+ if (rootAnimateState.disabled) {
+ return true;
+ }
- if(isMatchingElement(element, $rootElement)) {
- return rootAnimateState.disabled || rootAnimateState.running;
+ if (isMatchingElement(element, $rootElement)) {
+ return rootAnimateState.running;
}
+ var allowChildAnimations, parentRunningAnimation, hasParent;
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;
+ if (parentElement.length === 0) break;
var isRoot = isMatchingElement(parentElement, $rootElement);
- var state = isRoot ? rootAnimateState : parentElement.data(NG_ANIMATE_STATE);
- var result = state && (!!state.disabled || state.running || state.totalActive > 0);
- if(isRoot || result) {
- return result;
+ var state = isRoot ? rootAnimateState : (parentElement.data(NG_ANIMATE_STATE) || {});
+ if (state.disabled) {
+ return true;
}
- if(isRoot) return true;
+ //no matter what, for an animation to work it must reach the root element
+ //this implies that the element is attached to the DOM when the animation is run
+ if (isRoot) {
+ hasParent = true;
+ }
+
+ //once a flag is found that is strictly false then everything before
+ //it will be discarded and all child animations will be restricted
+ if (allowChildAnimations !== false) {
+ var animateChildrenFlag = parentElement.data(NG_ANIMATE_CHILDREN);
+ if(angular.isDefined(animateChildrenFlag)) {
+ allowChildAnimations = animateChildrenFlag;
+ }
+ }
+
+ parentRunningAnimation = parentRunningAnimation ||
+ state.running ||
+ (state.last && !state.last.isClassBased);
}
while(parentElement = parentElement.parent());
- return true;
+ return !hasParent || (!allowChildAnimations && parentRunningAnimation);
}
}]);
@@ -1094,17 +1182,22 @@ angular.module('ngAnimate', ['ng'])
var closingTimestamp = 0;
var animationElementQueue = [];
function animationCloseHandler(element, totalTime) {
- var futureTimestamp = Date.now() + (totalTime * 1000);
+ 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;
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);
@@ -1197,7 +1290,7 @@ angular.module('ngAnimate', ['ng'])
parentElement.data(NG_ANIMATE_PARENT_KEY, ++parentCounter);
parentID = parentCounter;
}
- return parentID + '-' + extractElementNode(element).className;
+ return parentID + '-' + extractElementNode(element).getAttribute('class');
}
function animateSetup(animationEvent, element, className, calculationDecorator) {
@@ -1243,7 +1336,7 @@ angular.module('ngAnimate', ['ng'])
itemIndex : itemIndex,
stagger : stagger,
timings : timings,
- closeAnimationFn : angular.noop
+ closeAnimationFn : noop
});
//temporarily disable the transition so that the enter styles
@@ -1252,7 +1345,14 @@ angular.module('ngAnimate', ['ng'])
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);
}
@@ -1295,7 +1395,7 @@ angular.module('ngAnimate', ['ng'])
function animateRun(animationEvent, element, className, activeAnimationComplete) {
var node = extractElementNode(element);
var elementData = element.data(NG_ANIMATE_CSS_DATA_KEY);
- if(node.className.indexOf(className) == -1 || !elementData) {
+ if(node.getAttribute('class').indexOf(className) == -1 || !elementData) {
activeAnimationComplete();
return;
}
@@ -1346,7 +1446,7 @@ angular.module('ngAnimate', ['ng'])
//the jqLite object, so we're safe to use a single variable to house
//the styles since there is always only one element being animated
var oldStyle = node.getAttribute('style') || '';
- node.setAttribute('style', oldStyle + ' ' + style);
+ node.setAttribute('style', oldStyle + '; ' + style);
}
element.on(css3AnimationEvents, onAnimationProgress);