vendor/assets/javascripts/angular-animate.js in angularjs-rails-1.2.0.rc1 vs vendor/assets/javascripts/angular-animate.js in angularjs-rails-1.2.0.rc2

- old
+ new

@@ -1,31 +1,26 @@ /** - * @license AngularJS v1.2.0rc1 + * @license AngularJS v1.2.0-rc.2 * (c) 2010-2012 Google, Inc. http://angularjs.org * License: MIT */ (function(window, angular, undefined) {'use strict'; /** * @ngdoc overview * @name ngAnimate * @description * - * ngAnimate - * ========= + * # ngAnimate * - * The ngAnimate module is an optional module that comes packed with AngularJS that can be included within an AngularJS - * application to provide support for CSS and JavaScript animation hooks. + * `ngAnimate` is an optional module that provides CSS and JavaScript animation hooks. * - * To make use of animations with AngularJS, the `angular-animate.js` JavaScript file must be included into your application - * and the `ngAnimate` module must be included as a dependency. + * {@installModule animate} * - * <pre> - * angular.module('App', ['ngAnimate']); - * </pre> + * # Usage * - * Then, to see animations in action, all that is required is to define the appropriate CSS classes + * To see animations in action, all that is required is to define the appropriate CSS classes * or to register a JavaScript animation via the $animation service. The directives that support animation automatically are: * `ngRepeat`, `ngInclude`, `ngSwitch`, `ngShow`, `ngHide` and `ngView`. Custom directives can take advantage of animation * by using the `$animate` service. * * Below is a more detailed breakdown of the supported animation events provided by pre-existing ng directives: @@ -51,11 +46,11 @@ * -webkit-transition:0.5s linear all; * -moz-transition:0.5s linear all; * -o-transition:0.5s linear all; * transition:0.5s linear all; * } - * + * * .slide.ng-enter { } /&#42; starting animations for enter &#42;/ * .slide.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;/ * </style> @@ -195,26 +190,28 @@ /** * @ngdoc object * @name ngAnimate.$animateProvider * @description * - * The $AnimationProvider provider allows developers to register and access custom JavaScript animations directly inside + * The `$AnimationProvider` allows developers to register and access custom JavaScript animations directly inside * of a module. When an animation is triggered, the $animate service will query the $animation function to find any * animations that match the provided name value. * - * Please visit the {@link ngAnimate ngAnimate} module overview page learn more about how to use animations in your application. + * 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. + * */ .config(['$provide', '$animateProvider', function($provide, $animateProvider) { var noop = angular.noop; var forEach = angular.forEach; var selectors = $animateProvider.$$selectors; var NG_ANIMATE_STATE = '$$ngAnimateState'; var rootAnimateState = {running:true}; - $provide.decorator('$animate', ['$delegate', '$injector', '$sniffer', '$rootElement', '$timeout', - function($delegate, $injector, $sniffer, $rootElement, $timeout) { + $provide.decorator('$animate', ['$delegate', '$injector', '$sniffer', '$rootElement', '$timeout', '$rootScope', + function($delegate, $injector, $sniffer, $rootElement, $timeout, $rootScope) { $rootElement.data(NG_ANIMATE_STATE, rootAnimateState); function lookup(name) { if (name) { @@ -253,12 +250,14 @@ * as well as any CSS-defined animations against the CSS classes present on the element once the DOM operation is run. * * The `$animate` service is used behind the scenes with pre-existing directives and animation with these directives * will work out of the box without any extra configuration. * - * Please visit the {@link ngAnimate ngAnimate} module overview page learn more about how to use animations in your application. + * 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. + * */ return { /** * @ngdoc function * @name ngAnimate.$animate#enter @@ -273,12 +272,12 @@ * * | Animation Step | What the element class attribute looks like | * |----------------------------------------------------------------------------------------------|-----------------------------------------------| * | 1. $animate.enter(...) is called | class="my-animation" | * | 2. element is inserted into the parent element or beside the after element | class="my-animation" | - * | 3. the .ng-enter class is added to the element | class="my-animation ng-enter" | - * | 4. $animate runs any JavaScript-defined animations on the element | class="my-animation ng-enter" | + * | 3. $animate runs any JavaScript-defined animations on the element | class="my-animation" | + * | 4. the .ng-enter class is added to the element | class="my-animation ng-enter" | * | 5. $animate scans the element styles to get the CSS transition/animation duration and delay | class="my-animation ng-enter" | * | 6. the .ng-enter-active class is added (this triggers the CSS transition/animation) | class="my-animation ng-enter ng-enter-active" | * | 7. $animate waits for X milliseconds for the animation to complete | class="my-animation ng-enter ng-enter-active" | * | 8. The animation ends and both CSS classes are removed from the element | class="my-animation" | * | 9. The done() callback is fired (if provided) | class="my-animation" | @@ -288,11 +287,15 @@ * @param {jQuery/jqLite element} after the sibling element (which is the previous element) of the element that will be the focus of the enter animation * @param {function()=} done callback function that will be called once the animation is complete */ enter : function(element, parent, after, done) { $delegate.enter(element, parent, after); - performAnimation('enter', 'ng-enter', element, parent, after, done); + $rootScope.$$postDigest(function() { + performAnimation('enter', 'ng-enter', element, parent, after, function() { + done && $timeout(done, 0, false); + }); + }); }, /** * @ngdoc function * @name ngAnimate.$animate#leave @@ -306,12 +309,12 @@ * Below is a breakdown of each step that occurs during enter animation: * * | Animation Step | What the element class attribute looks like | * |----------------------------------------------------------------------------------------------|----------------------------------------------| * | 1. $animate.leave(...) is called | class="my-animation" | - * | 2. the .ng-leave class is added to the element | class="my-animation ng-leave" | - * | 3. $animate runs any JavaScript-defined animations on the element | class="my-animation ng-leave" | + * | 2. $animate runs any JavaScript-defined animations on the element | class="my-animation" | + * | 3. the .ng-leave class is added to the element | class="my-animation ng-leave" | * | 4. $animate scans the element styles to get the CSS transition/animation duration and delay | class="my-animation ng-leave" | * | 5. the .ng-leave-active class is added (this triggers the CSS transition/animation) | class="my-animation ng-leave ng-leave-active | * | 6. $animate waits for X milliseconds for the animation to complete | class="my-animation ng-leave ng-leave-active | * | 7. The animation ends and both CSS classes are removed from the element | class="my-animation" | * | 8. The element is removed from the DOM | ... | @@ -319,12 +322,14 @@ * * @param {jQuery/jqLite element} element the element that will be the focus of the leave animation * @param {function()=} done callback function that will be called once the animation is complete */ leave : function(element, done) { - performAnimation('leave', 'ng-leave', element, null, null, function() { - $delegate.leave(element, done); + $rootScope.$$postDigest(function() { + performAnimation('leave', 'ng-leave', element, null, null, function() { + $delegate.leave(element, done); + }); }); }, /** * @ngdoc function @@ -341,12 +346,12 @@ * * | Animation Step | What the element class attribute looks like | * |----------------------------------------------------------------------------------------------|---------------------------------------------| * | 1. $animate.move(...) is called | class="my-animation" | * | 2. element is moved into the parent element or beside the after element | class="my-animation" | - * | 3. the .ng-move class is added to the element | class="my-animation ng-move" | - * | 4. $animate runs any JavaScript-defined animations on the element | class="my-animation ng-move" | + * | 3. $animate runs any JavaScript-defined animations on the element | class="my-animation" | + * | 4. the .ng-move class is added to the element | class="my-animation ng-move" | * | 5. $animate scans the element styles to get the CSS transition/animation duration and delay | class="my-animation ng-move" | * | 6. the .ng-move-active class is added (this triggers the CSS transition/animation) | class="my-animation ng-move ng-move-active" | * | 7. $animate waits for X milliseconds for the animation to complete | class="my-animation ng-move ng-move-active" | * | 8. The animation ends and both CSS classes are removed from the element | class="my-animation" | * | 9. The done() callback is fired (if provided) | class="my-animation" | @@ -356,30 +361,35 @@ * @param {jQuery/jqLite element} after the sibling element (which is the previous element) of the element that will be the focus of the move animation * @param {function()=} done callback function that will be called once the animation is complete */ move : function(element, parent, after, done) { $delegate.move(element, parent, after); - performAnimation('move', 'ng-move', element, null, null, done); + $rootScope.$$postDigest(function() { + performAnimation('move', 'ng-move', element, null, null, function() { + done && $timeout(done, 0, false); + }); + }); }, /** * @ngdoc function * @name ngAnimate.$animate#addClass * @methodOf ngAnimate.$animate * * @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. + * the animate service the setup and active CSS classes in order to trigger the animation (this will be skipped if no CSS transitions + * or keyframes are defined on the -add CSS class). * * Below is a breakdown of each step that occurs during addClass animation: * * | Animation Step | What the element class attribute looks like | * |------------------------------------------------------------------------------------------------|---------------------------------------------| * | 1. $animate.addClass(element, 'super') is called | class="" | - * | 2. the .super-add class is added to the element | class="super-add" | - * | 3. $animate runs any JavaScript-defined animations on the element | class="super-add" | + * | 2. $animate runs any JavaScript-defined animations on the element | class="" | + * | 3. the .super-add class is added to the element | class="super-add" | * | 4. $animate scans the element styles to get the CSS transition/animation duration and delay | class="super-add" | * | 5. the .super-add-active class is added (this triggers the CSS transition/animation) | class="super-add super-add-active" | * | 6. $animate waits for X milliseconds for the animation to complete | class="super-add super-add-active" | * | 7. The animation ends and both CSS classes are removed from the element | class="" | * | 8. The super class is added to the element | class="super" | @@ -401,19 +411,20 @@ * @methodOf ngAnimate.$animate * * @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. + * order to provide the animate service the setup and active CSS classes in order to trigger the animation (this will be skipped if + * no CSS transitions or keyframes are defined on the -remove CSS class). * * Below is a breakdown of each step that occurs during removeClass animation: * * | Animation Step | What the element class attribute looks like | * |-----------------------------------------------------------------------------------------------|-------------------------------------------------| * | 1. $animate.removeClass(element, 'super') is called | class="super" | - * | 2. the .super-remove class is added to the element | class="super super-remove" | - * | 3. $animate runs any JavaScript-defined animations on the element | class="super super-remove" | + * | 2. $animate runs any JavaScript-defined animations on the element | class="super" | + * | 3. the .super-remove class is added to the element | class="super super-remove" | * | 4. $animate scans the element styles to get the CSS transition/animation duration and delay | class="super super-remove" | * | 5. the .super-remove-active class is added (this triggers the CSS transition/animation) | class="super super-remove super-remove-active" | * | 6. $animate waits for X milliseconds for the animation to complete | class="super super-remove super-remove-active" | * | 7. The animation ends and both CSS all three classes are removed from the element | class="" | * | 8. The done() callback is fired (if provided) | class="" | @@ -494,27 +505,18 @@ running:true, animations:animations, done:done }); - var baseClassName = className; - if(event == 'addClass') { - className = suffixClasses(className, '-add'); - } else if(event == 'removeClass') { - className = suffixClasses(className, '-remove'); - } - - element.addClass(className); - forEach(animations, function(animation, index) { var fn = function() { progress(index); }; if(animation.start) { if(event == 'addClass' || event == 'removeClass') { - animation.endFn = animation.start(element, baseClassName, fn); + animation.endFn = animation.start(element, className, fn); } else { animation.endFn = animation.start(element, fn); } } else { fn(); @@ -538,109 +540,139 @@ } function done() { if(!done.hasBeenRun) { done.hasBeenRun = true; - element.removeClass(className); element.removeData(NG_ANIMATE_STATE); (onComplete || noop)(); } } } }]); $animateProvider.register('', ['$window','$sniffer', '$timeout', function($window, $sniffer, $timeout) { var noop = angular.noop; var forEach = angular.forEach; + + //one day all browsers will have these properties + var w3cAnimationProp = 'animation'; + var w3cTransitionProp = 'transition'; + + //but some still use vendor-prefixed styles + var vendorAnimationProp = $sniffer.vendorPrefix + 'Animation'; + var vendorTransitionProp = $sniffer.vendorPrefix + 'Transition'; + + var durationKey = 'Duration', + delayKey = 'Delay', + propertyKey = 'Property', + animationIterationCountKey = 'IterationCount', + ELEMENT_NODE = 1; + function animate(element, className, done) { if (!($sniffer.transitions || $sniffer.animations)) { done(); - } else { - var activeClassName = ''; - $timeout(startAnimation, 1, false); - - //this acts as the cancellation function in case - //a new animation is triggered while another animation - //is still going on (otherwise the active className - //would still hang around until the timer is complete). - return onEnd; + return; } - - function parseMaxTime(str) { - var total = 0, values = angular.isString(str) ? str.split(/\s*,\s*/) : []; - forEach(values, function(value) { - total = Math.max(parseFloat(value) || 0, total); + else if(['ng-enter','ng-leave','ng-move'].indexOf(className) == -1) { + var existingDuration = 0; + forEach(element, function(element) { + if (element.nodeType == ELEMENT_NODE) { + var elementStyles = $window.getComputedStyle(element) || {}; + existingDuration = Math.max(parseMaxTime(elementStyles[w3cTransitionProp + durationKey]), + parseMaxTime(elementStyles[vendorTransitionProp + durationKey]), + existingDuration); + } }); - return total; + if(existingDuration > 0) { + done(); + return; + } } - function startAnimation() { - var duration = 0; - forEach(className.split(' '), function(klass, i) { - activeClassName += (i > 0 ? ' ' : '') + klass + '-active'; - }); + element.addClass(className); - element.addClass(activeClassName); + //we want all the styles defined before and after + var duration = 0; + forEach(element, function(element) { + if (element.nodeType == ELEMENT_NODE) { + var elementStyles = $window.getComputedStyle(element) || {}; - //one day all browsers will have these properties - var w3cAnimationProp = 'animation'; - var w3cTransitionProp = 'transition'; + var transitionDelay = Math.max(parseMaxTime(elementStyles[w3cTransitionProp + delayKey]), + parseMaxTime(elementStyles[vendorTransitionProp + delayKey])); - //but some still use vendor-prefixed styles - var vendorAnimationProp = $sniffer.vendorPrefix + 'Animation'; - var vendorTransitionProp = $sniffer.vendorPrefix + 'Transition'; + var animationDelay = Math.max(parseMaxTime(elementStyles[w3cAnimationProp + delayKey]), + parseMaxTime(elementStyles[vendorAnimationProp + delayKey])); - var durationKey = 'Duration', - delayKey = 'Delay', - animationIterationCountKey = 'IterationCount'; + var transitionDuration = Math.max(parseMaxTime(elementStyles[w3cTransitionProp + durationKey]), + parseMaxTime(elementStyles[vendorTransitionProp + durationKey])); - //we want all the styles defined before and after - var ELEMENT_NODE = 1; - forEach(element, function(element) { - if (element.nodeType == ELEMENT_NODE) { - var elementStyles = $window.getComputedStyle(element) || {}; + var animationDuration = Math.max(parseMaxTime(elementStyles[w3cAnimationProp + durationKey]), + parseMaxTime(elementStyles[vendorAnimationProp + durationKey])); - var transitionDelay = Math.max(parseMaxTime(elementStyles[w3cTransitionProp + delayKey]), - parseMaxTime(elementStyles[vendorTransitionProp + delayKey])); + if(animationDuration > 0) { + animationDuration *= Math.max(parseInt(elementStyles[w3cAnimationProp + animationIterationCountKey]) || 0, + parseInt(elementStyles[vendorAnimationProp + animationIterationCountKey]) || 0, + 1); + } - var animationDelay = Math.max(parseMaxTime(elementStyles[w3cAnimationProp + delayKey]), - parseMaxTime(elementStyles[vendorAnimationProp + delayKey])); + duration = Math.max(animationDelay + animationDuration, + transitionDelay + transitionDuration, + duration); + } + }); - var transitionDuration = Math.max(parseMaxTime(elementStyles[w3cTransitionProp + durationKey]), - parseMaxTime(elementStyles[vendorTransitionProp + durationKey])); + /* there is no point in performing a reflow if the animation + timeout is empty (this would cause a flicker bug normally + in the page */ + if(duration > 0) { + var node = element[0]; - var animationDuration = Math.max(parseMaxTime(elementStyles[w3cAnimationProp + durationKey]), - parseMaxTime(elementStyles[vendorAnimationProp + durationKey])); + //temporarily disable the transition so that the enter styles + //don't animate twice (this is here to avoid a bug in Chrome/FF). + node.style[w3cTransitionProp + propertyKey] = 'none'; + node.style[vendorTransitionProp + propertyKey] = 'none'; - if(animationDuration > 0) { - animationDuration *= Math.max(parseInt(elementStyles[w3cAnimationProp + animationIterationCountKey]) || 0, - parseInt(elementStyles[vendorAnimationProp + animationIterationCountKey]) || 0, - 1); - } - - duration = Math.max(animationDelay + animationDuration, - transitionDelay + transitionDuration, - duration); - } + var activeClassName = ''; + forEach(className.split(' '), function(klass, i) { + activeClassName += (i > 0 ? ' ' : '') + klass + '-active'; }); + //this triggers a reflow which allows for the transition animation to kick in + element.prop('clientWidth'); + node.style[w3cTransitionProp + propertyKey] = ''; + node.style[vendorTransitionProp + propertyKey] = ''; + element.addClass(activeClassName); + $timeout(done, duration * 1000, false); - } - //this will automatically be called by $animate so - //there is no need to attach this internally to the - //timeout done method - function onEnd(cancelled) { - element.removeClass(activeClassName); + //this will automatically be called by $animate so + //there is no need to attach this internally to the + //timeout done method + return function onEnd(cancelled) { + element.removeClass(className); + element.removeClass(activeClassName); - //only when the animation is cancelled is the done() - //function not called for this animation therefore - //this must be also called - if(cancelled) { - done(); + //only when the animation is cancelled is the done() + //function not called for this animation therefore + //this must be also called + if(cancelled) { + done(); + } } } + else { + element.removeClass(className); + done(); + } + + function parseMaxTime(str) { + var total = 0, values = angular.isString(str) ? str.split(/\s*,\s*/) : []; + forEach(values, function(value) { + total = Math.max(parseFloat(value) || 0, total); + }); + return total; + } } return { enter : function(element, done) { return animate(element, 'ng-enter', done); @@ -657,21 +689,20 @@ removeClass : function(element, className, done) { return animate(element, suffixClasses(className, '-remove'), done); } }; + function suffixClasses(classes, suffix) { + var className = ''; + classes = angular.isArray(classes) ? classes : classes.split(/\s+/); + forEach(classes, function(klass, i) { + if(klass && klass.length > 0) { + className += (i > 0 ? ' ' : '') + klass + suffix; + } + }); + return className; + } }]); - - function suffixClasses(classes, suffix) { - var className = ''; - classes = angular.isArray(classes) ? classes : classes.split(/\s+/); - forEach(classes, function(klass, i) { - if(klass && klass.length > 0) { - className += (i > 0 ? ' ' : '') + klass + suffix; - } - }); - return className; - } }]); })(window, window.angular);