vendor/assets/javascripts/unstable/angular-animate.js in angularjs-rails-1.2.25 vs vendor/assets/javascripts/unstable/angular-animate.js in angularjs-rails-1.2.26

- old
+ new

@@ -1,7 +1,7 @@ /** - * @license AngularJS v1.3.0-rc.3 + * @license AngularJS v1.3.0-rc.5 * (c) 2010-2014 Google, Inc. http://angularjs.org * License: MIT */ (function(window, angular, undefined) {'use strict'; @@ -225,11 +225,11 @@ * Also, try not to mix the two class-based animation flavors together since the CSS code may become * overly complex. * * ### CSS Staggering Animations * 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 + * curtain-like effect. The ngAnimate module (versions >=1.2) 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). * * ```css @@ -381,10 +381,11 @@ .config(['$provide', '$animateProvider', function($provide, $animateProvider) { var noop = angular.noop; var forEach = angular.forEach; var selectors = $animateProvider.$$selectors; var isArray = angular.isArray; + var isString = angular.isString; var ELEMENT_NODE = 1; var NG_ANIMATE_STATE = '$$ngAnimateState'; var NG_ANIMATE_CHILDREN = '$$ngAnimateChildren'; var NG_ANIMATE_CLASS_NAME = 'ng-animate'; @@ -471,54 +472,52 @@ }); }); return defer.promise; } + function parseAnimateOptions(options) { + // some plugin code may still be passing in the callback + // function as the last param for the $animate methods so + // it's best to only allow string or array values for now + if (isArray(options)) return options; + if (isString(options)) return [options]; + } + function resolveElementClasses(element, cache, runningAnimations) { runningAnimations = runningAnimations || {}; - var map = {}; - forEach(cache.add, function(className) { - if (className && className.length) { - map[className] = map[className] || 0; - map[className]++; - } - }); - - forEach(cache.remove, function(className) { - if (className && className.length) { - map[className] = map[className] || 0; - map[className]--; - } - }); - - var lookup = []; + var lookup = {}; forEach(runningAnimations, function(data, selector) { forEach(selector.split(' '), function(s) { lookup[s]=data; }); }); + var hasClasses = Object.create(null); + forEach((element.attr('class') || '').split(/\s+/), function(className) { + hasClasses[className] = true; + }); + var toAdd = [], toRemove = []; - forEach(map, function(status, className) { - var hasClass = angular.$$hasClass(element[0], className); + forEach(cache.classes, function(status, className) { + var hasClass = hasClasses[className]; var matchingAnimation = lookup[className] || {}; // When addClass and removeClass is called then $animate will check to // see if addClass and removeClass cancel each other out. When there are // more calls to removeClass than addClass then the count falls below 0 // and then the removeClass animation will be allowed. Otherwise if the // count is above 0 then that means an addClass animation will commence. // Once an animation is allowed then the code will also check to see if // there exists any on-going animation that is already adding or remvoing // the matching CSS class. - if (status < 0) { + if (status === false) { //does it have the class or will it have the class if (hasClass || matchingAnimation.event == 'addClass') { toRemove.push(className); } - } else if (status > 0) { + } else if (status === true) { //is the class missing or will it be removed? if (!hasClass || matchingAnimation.event == 'removeClass') { toAdd.push(className); } } @@ -798,19 +797,20 @@ * @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 * @return {Promise} the animation callback promise */ - enter : function(element, parentElement, afterElement) { + enter : function(element, parentElement, afterElement, options) { + options = parseAnimateOptions(options); element = angular.element(element); parentElement = prepareElement(parentElement); afterElement = prepareElement(afterElement); classBasedAnimationsBlocked(element, true); $delegate.enter(element, parentElement, afterElement); return runAnimationPostDigest(function(done) { - return performAnimation('enter', 'ng-enter', stripCommentsFromElement(element), parentElement, afterElement, noop, done); + return performAnimation('enter', 'ng-enter', stripCommentsFromElement(element), parentElement, afterElement, noop, options, done); }); }, /** * @ngdoc method @@ -840,20 +840,20 @@ * | 13. The returned promise is resolved. | ... | * * @param {DOMElement} element the element that will be the focus of the leave animation * @return {Promise} the animation callback promise */ - leave : function(element) { + leave : function(element, options) { + options = parseAnimateOptions(options); element = angular.element(element); cancelChildAnimations(element); classBasedAnimationsBlocked(element, true); - this.enabled(false, element); return runAnimationPostDigest(function(done) { return performAnimation('leave', 'ng-leave', stripCommentsFromElement(element), null, null, function() { $delegate.leave(element); - }, done); + }, options, done); }); }, /** * @ngdoc method @@ -886,20 +886,21 @@ * @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 * @return {Promise} the animation callback promise */ - move : function(element, parentElement, afterElement) { + move : function(element, parentElement, afterElement, options) { + options = parseAnimateOptions(options); element = angular.element(element); parentElement = prepareElement(parentElement); afterElement = prepareElement(afterElement); cancelChildAnimations(element); classBasedAnimationsBlocked(element, true); $delegate.move(element, parentElement, afterElement); return runAnimationPostDigest(function(done) { - return performAnimation('move', 'ng-move', stripCommentsFromElement(element), parentElement, afterElement, noop, done); + return performAnimation('move', 'ng-move', stripCommentsFromElement(element), parentElement, afterElement, noop, options, done); }); }, /** * @ngdoc method @@ -928,12 +929,12 @@ * * @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 * @return {Promise} the animation callback promise */ - addClass : function(element, className) { - return this.setClass(element, className, []); + addClass : function(element, className, options) { + return this.setClass(element, className, [], options); }, /** * @ngdoc method * @name $animate#removeClass @@ -961,12 +962,12 @@ * * @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 * @return {Promise} the animation callback promise */ - removeClass : function(element, className) { - return this.setClass(element, [], className); + removeClass : function(element, className, options) { + return this.setClass(element, [], className, options); }, /** * * @ngdoc method @@ -992,47 +993,83 @@ * @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 * CSS classes have been set on the element * @return {Promise} the animation callback promise */ - setClass : function(element, add, remove) { + setClass : function(element, add, remove, options) { + options = parseAnimateOptions(options); + var STORAGE_KEY = '$$animateClasses'; element = angular.element(element); element = stripCommentsFromElement(element); if (classBasedAnimationsBlocked(element)) { - return $delegate.setClass(element, add, remove); + // TODO(@caitp/@matsko): Don't use private/undocumented API here --- we should not be + // changing the DOM synchronously in this case. The `true` parameter must eventually be + // removed. + return $delegate.setClass(element, add, remove, true); } + // we're using a combined array for both the add and remove + // operations since the ORDER OF addClass and removeClass matters + var classes, cache = element.data(STORAGE_KEY); + var hasCache = !!cache; + if (!cache) { + cache = {}; + cache.classes = {}; + } + classes = cache.classes; + add = isArray(add) ? add : add.split(' '); + forEach(add, function(c) { + if (c && c.length) { + classes[c] = true; + } + }); + remove = isArray(remove) ? remove : remove.split(' '); + forEach(remove, function(c) { + if (c && c.length) { + classes[c] = false; + } + }); - var cache = element.data(STORAGE_KEY); - if (cache) { - cache.add = cache.add.concat(add); - cache.remove = cache.remove.concat(remove); + if (hasCache) { + if (options && cache.options) { + cache.options = cache.options.concat(options); + } //the digest cycle will combine all the animations into one function return cache.promise; } else { element.data(STORAGE_KEY, cache = { - add : add, - remove : remove + classes : classes, + options : options }); } return cache.promise = runAnimationPostDigest(function(done) { + var parentElement = element.parent(); + var elementNode = extractElementNode(element); + var parentNode = elementNode.parentNode; + // TODO(matsko): move this code into the animationsDisabled() function once #8092 is fixed + if (!parentNode || parentNode['$$NG_REMOVED'] || elementNode['$$NG_REMOVED']) { + done(); + return; + } + var cache = element.data(STORAGE_KEY); element.removeData(STORAGE_KEY); var state = element.data(NG_ANIMATE_STATE) || {}; var classes = resolveElementClasses(element, cache, state.active); return !classes ? done() - : performAnimation('setClass', classes, element, null, null, function() { - $delegate.setClass(element, classes[0], classes[1]); - }, done); + : performAnimation('setClass', classes, element, parentElement, null, function() { + if (classes[0]) $delegate.$$addClassImmediately(element, classes[0]); + if (classes[1]) $delegate.$$removeClassImmediately(element, classes[1]); + }, cache.options, done); }); }, /** * @ngdoc method @@ -1090,11 +1127,11 @@ 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, 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) { + function performAnimation(animationEvent, className, element, parentElement, afterElement, domOperation, options, doneCallback) { var noopCancel = noop; var runner = animationRunner(element, animationEvent, className); if (!runner) { fireDOMOperation(); @@ -1198,10 +1235,15 @@ } //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); + if (isArray(options)) { + forEach(options, function(className) { + element.addClass(className); + }); + } var localAnimationCount = globalAnimationCounter++; totalActiveAnimations++; runningAnimations[className] = runner; @@ -1267,12 +1309,19 @@ } function closeAnimation() { if (!closeAnimation.hasBeenRun) { closeAnimation.hasBeenRun = true; + if (isArray(options)) { + forEach(options, function(className) { + element.removeClass(className); + }); + } + 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 (runner && runner.isClassBased) { @@ -1423,10 +1472,20 @@ var lookupCache = {}; var parentCounter = 0; var animationReflowQueue = []; var cancelAnimationReflow; + function clearCacheAfterReflow() { + if (!cancelAnimationReflow) { + cancelAnimationReflow = $$animateReflow(function() { + animationReflowQueue = []; + cancelAnimationReflow = null; + lookupCache = {}; + }); + } + } + function afterReflow(element, callback) { if (cancelAnimationReflow) { cancelAnimationReflow(); } animationReflowQueue.push(callback); @@ -1523,11 +1582,11 @@ return data; } function parseMaxTime(str) { var maxValue = 0; - var values = angular.isString(str) ? + var values = isString(str) ? str.split(/\s*,\s*/) : []; forEach(values, function(value) { maxValue = Math.max(parseFloat(value) || 0, maxValue); }); @@ -1768,10 +1827,11 @@ //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(animationEvent, element, className); if (!preReflowCancellation) { + clearCacheAfterReflow(); animationComplete(); return; } //There are two cancellation functions: one is before the first @@ -1824,27 +1884,30 @@ var cancellationMethod = animateBefore('setClass', element, className); if (cancellationMethod) { afterReflow(element, animationCompleted); return cancellationMethod; } + clearCacheAfterReflow(); animationCompleted(); }, beforeAddClass : function(element, className, animationCompleted) { var cancellationMethod = animateBefore('addClass', element, suffixClasses(className, '-add')); if (cancellationMethod) { afterReflow(element, animationCompleted); return cancellationMethod; } + clearCacheAfterReflow(); animationCompleted(); }, beforeRemoveClass : function(element, className, animationCompleted) { var cancellationMethod = animateBefore('removeClass', element, suffixClasses(className, '-remove')); if (cancellationMethod) { afterReflow(element, animationCompleted); return cancellationMethod; } + clearCacheAfterReflow(); animationCompleted(); }, setClass : function(element, add, remove, animationCompleted) { remove = suffixClasses(remove, '-remove');