vendor/assets/javascripts/angular-animate.js in angularjs-rails-1.4.3 vs vendor/assets/javascripts/angular-animate.js in angularjs-rails-1.4.4
- old
+ new
@@ -1,7 +1,7 @@
/**
- * @license AngularJS v1.4.3
+ * @license AngularJS v1.4.4
* (c) 2010-2015 Google, Inc. http://angularjs.org
* License: MIT
*/
(function(window, angular, undefined) {'use strict';
@@ -19,16 +19,64 @@
var isElement = angular.isElement;
var ELEMENT_NODE = 1;
var COMMENT_NODE = 8;
+var ADD_CLASS_SUFFIX = '-add';
+var REMOVE_CLASS_SUFFIX = '-remove';
+var EVENT_CLASS_PREFIX = 'ng-';
+var ACTIVE_CLASS_SUFFIX = '-active';
+
var NG_ANIMATE_CLASSNAME = 'ng-animate';
var NG_ANIMATE_CHILDREN_DATA = '$$ngAnimateChildren';
+// Detect proper transitionend/animationend event names.
+var CSS_PREFIX = '', TRANSITION_PROP, TRANSITIONEND_EVENT, ANIMATION_PROP, ANIMATIONEND_EVENT;
+
+// If unprefixed events are not supported but webkit-prefixed are, use the latter.
+// Otherwise, just use W3C names, browsers not supporting them at all will just ignore them.
+// Note: Chrome implements `window.onwebkitanimationend` and doesn't implement `window.onanimationend`
+// but at the same time dispatches the `animationend` event and not `webkitAnimationEnd`.
+// Register both events in case `window.onanimationend` is not supported because of that,
+// do the same for `transitionend` as Safari is likely to exhibit similar behavior.
+// Also, the only modern browser that uses vendor prefixes for transitions/keyframes is webkit
+// therefore there is no reason to test anymore for other vendor prefixes:
+// http://caniuse.com/#search=transition
+if (window.ontransitionend === undefined && window.onwebkittransitionend !== undefined) {
+ CSS_PREFIX = '-webkit-';
+ TRANSITION_PROP = 'WebkitTransition';
+ TRANSITIONEND_EVENT = 'webkitTransitionEnd transitionend';
+} else {
+ TRANSITION_PROP = 'transition';
+ TRANSITIONEND_EVENT = 'transitionend';
+}
+
+if (window.onanimationend === undefined && window.onwebkitanimationend !== undefined) {
+ CSS_PREFIX = '-webkit-';
+ ANIMATION_PROP = 'WebkitAnimation';
+ ANIMATIONEND_EVENT = 'webkitAnimationEnd animationend';
+} else {
+ ANIMATION_PROP = 'animation';
+ ANIMATIONEND_EVENT = 'animationend';
+}
+
+var DURATION_KEY = 'Duration';
+var PROPERTY_KEY = 'Property';
+var DELAY_KEY = 'Delay';
+var TIMING_KEY = 'TimingFunction';
+var ANIMATION_ITERATION_COUNT_KEY = 'IterationCount';
+var ANIMATION_PLAYSTATE_KEY = 'PlayState';
+var SAFE_FAST_FORWARD_DURATION_VALUE = 9999;
+
+var ANIMATION_DELAY_PROP = ANIMATION_PROP + DELAY_KEY;
+var ANIMATION_DURATION_PROP = ANIMATION_PROP + DURATION_KEY;
+var TRANSITION_DELAY_PROP = TRANSITION_PROP + DELAY_KEY;
+var TRANSITION_DURATION_PROP = TRANSITION_PROP + DURATION_KEY;
+
var isPromiseLike = function(p) {
return p && p.then ? true : false;
-}
+};
function assertArg(arg, name, reason) {
if (!arg) {
throw ngMinErr('areq', "Argument '{0}' is {1}", (name || '?'), (reason || "required"));
}
@@ -175,12 +223,25 @@
function mergeAnimationOptions(element, target, newOptions) {
var toAdd = (target.addClass || '') + ' ' + (newOptions.addClass || '');
var toRemove = (target.removeClass || '') + ' ' + (newOptions.removeClass || '');
var classes = resolveElementClasses(element.attr('class'), toAdd, toRemove);
+ if (newOptions.preparationClasses) {
+ target.preparationClasses = concatWithSpace(newOptions.preparationClasses, target.preparationClasses);
+ delete newOptions.preparationClasses;
+ }
+
+ // noop is basically when there is no callback; otherwise something has been set
+ var realDomOperation = target.domOperation !== noop ? target.domOperation : null;
+
extend(target, newOptions);
+ // TODO(matsko or sreeramu): proper fix is to maintain all animation callback in array and call at last,but now only leave has the callback so no issue with this.
+ if (realDomOperation) {
+ target.domOperation = realDomOperation;
+ }
+
if (classes.addClass) {
target.addClass = classes.addClass;
} else {
target.addClass = null;
}
@@ -254,68 +315,72 @@
function getDomNode(element) {
return (element instanceof angular.element) ? element[0] : element;
}
-var $$rAFSchedulerFactory = ['$$rAF', function($$rAF) {
- var tickQueue = [];
- var cancelFn;
+function applyGeneratedPreparationClasses(element, event, options) {
+ var classes = '';
+ if (event) {
+ classes = pendClasses(event, EVENT_CLASS_PREFIX, true);
+ }
+ if (options.addClass) {
+ classes = concatWithSpace(classes, pendClasses(options.addClass, ADD_CLASS_SUFFIX));
+ }
+ if (options.removeClass) {
+ classes = concatWithSpace(classes, pendClasses(options.removeClass, REMOVE_CLASS_SUFFIX));
+ }
+ if (classes.length) {
+ options.preparationClasses = classes;
+ element.addClass(classes);
+ }
+}
- function scheduler(tasks) {
- // we make a copy since RAFScheduler mutates the state
- // of the passed in array variable and this would be difficult
- // to track down on the outside code
- tickQueue.push([].concat(tasks));
- nextTick();
+function clearGeneratedClasses(element, options) {
+ if (options.preparationClasses) {
+ element.removeClass(options.preparationClasses);
+ options.preparationClasses = null;
}
+ if (options.activeClasses) {
+ element.removeClass(options.activeClasses);
+ options.activeClasses = null;
+ }
+}
- /* waitUntilQuiet does two things:
- * 1. It will run the FINAL `fn` value only when an uncancelled RAF has passed through
- * 2. It will delay the next wave of tasks from running until the quiet `fn` has run.
- *
- * The motivation here is that animation code can request more time from the scheduler
- * before the next wave runs. This allows for certain DOM properties such as classes to
- * be resolved in time for the next animation to run.
- */
- scheduler.waitUntilQuiet = function(fn) {
- if (cancelFn) cancelFn();
+function blockTransitions(node, duration) {
+ // we use a negative delay value since it performs blocking
+ // yet it doesn't kill any existing transitions running on the
+ // same element which makes this safe for class-based animations
+ var value = duration ? '-' + duration + 's' : '';
+ applyInlineStyle(node, [TRANSITION_DELAY_PROP, value]);
+ return [TRANSITION_DELAY_PROP, value];
+}
- cancelFn = $$rAF(function() {
- cancelFn = null;
- fn();
- nextTick();
- });
- };
+function blockKeyframeAnimations(node, applyBlock) {
+ var value = applyBlock ? 'paused' : '';
+ var key = ANIMATION_PROP + ANIMATION_PLAYSTATE_KEY;
+ applyInlineStyle(node, [key, value]);
+ return [key, value];
+}
- return scheduler;
+function applyInlineStyle(node, styleTuple) {
+ var prop = styleTuple[0];
+ var value = styleTuple[1];
+ node.style[prop] = value;
+}
- function nextTick() {
- if (!tickQueue.length) return;
+function concatWithSpace(a,b) {
+ if (!a) return b;
+ if (!b) return a;
+ return a + ' ' + b;
+}
- var updatedQueue = [];
- for (var i = 0; i < tickQueue.length; i++) {
- var innerQueue = tickQueue[i];
- runNextTask(innerQueue);
- if (innerQueue.length) {
- updatedQueue.push(innerQueue);
- }
- }
- tickQueue = updatedQueue;
+function $$BodyProvider() {
+ this.$get = ['$document', function($document) {
+ return jqLite($document[0].body);
+ }];
+}
- if (!cancelFn) {
- $$rAF(function() {
- if (!cancelFn) nextTick();
- });
- }
- }
-
- function runNextTask(tasks) {
- var nextTask = tasks.shift();
- nextTask();
- }
-}];
-
var $$AnimateChildrenDirective = [function() {
return function(scope, element, attrs) {
var val = attrs.ngAnimateChildren;
if (angular.isString(val) && val.length === 0) { //empty attribute
element.data(NG_ANIMATE_CHILDREN_DATA, true);
@@ -526,68 +591,24 @@
* of the element will be `transition-delay: DETECTED_VALUE`). Using `true` is useful when you want the CSS classes and inline styles to all share the same
* CSS delay value.
* * `stagger` - A numeric time value representing the delay between successively animated elements
* ({@link ngAnimate#css-staggering-animations Click here to learn how CSS-based staggering works in ngAnimate.})
* * `staggerIndex` - The numeric index representing the stagger item (e.g. a value of 5 is equal to the sixth item in the stagger; therefore when a
- * `stagger` option value of `0.1` is used then there will be a stagger delay of `600ms`)
- * `applyClassesEarly` - Whether or not the classes being added or removed will be used when detecting the animation. This is set by `$animate` when enter/leave/move animations are fired to ensure that the CSS classes are resolved in time. (Note that this will prevent any transitions from occuring on the classes being added and removed.)
+ * * `stagger` option value of `0.1` is used then there will be a stagger delay of `600ms`)
+ * * `applyClassesEarly` - Whether or not the classes being added or removed will be used when detecting the animation. This is set by `$animate` when enter/leave/move animations are fired to ensure that the CSS classes are resolved in time. (Note that this will prevent any transitions from occuring on the classes being added and removed.)
*
* @return {object} an object with start and end methods and details about the animation.
*
* * `start` - The method to start the animation. This will return a `Promise` when called.
* * `end` - This method will cancel the animation and remove all applied CSS classes and styles.
*/
+var ONE_SECOND = 1000;
+var BASE_TEN = 10;
-// Detect proper transitionend/animationend event names.
-var CSS_PREFIX = '', TRANSITION_PROP, TRANSITIONEND_EVENT, ANIMATION_PROP, ANIMATIONEND_EVENT;
-
-// If unprefixed events are not supported but webkit-prefixed are, use the latter.
-// Otherwise, just use W3C names, browsers not supporting them at all will just ignore them.
-// Note: Chrome implements `window.onwebkitanimationend` and doesn't implement `window.onanimationend`
-// but at the same time dispatches the `animationend` event and not `webkitAnimationEnd`.
-// Register both events in case `window.onanimationend` is not supported because of that,
-// do the same for `transitionend` as Safari is likely to exhibit similar behavior.
-// Also, the only modern browser that uses vendor prefixes for transitions/keyframes is webkit
-// therefore there is no reason to test anymore for other vendor prefixes:
-// http://caniuse.com/#search=transition
-if (window.ontransitionend === undefined && window.onwebkittransitionend !== undefined) {
- CSS_PREFIX = '-webkit-';
- TRANSITION_PROP = 'WebkitTransition';
- TRANSITIONEND_EVENT = 'webkitTransitionEnd transitionend';
-} else {
- TRANSITION_PROP = 'transition';
- TRANSITIONEND_EVENT = 'transitionend';
-}
-
-if (window.onanimationend === undefined && window.onwebkitanimationend !== undefined) {
- CSS_PREFIX = '-webkit-';
- ANIMATION_PROP = 'WebkitAnimation';
- ANIMATIONEND_EVENT = 'webkitAnimationEnd animationend';
-} else {
- ANIMATION_PROP = 'animation';
- ANIMATIONEND_EVENT = 'animationend';
-}
-
-var DURATION_KEY = 'Duration';
-var PROPERTY_KEY = 'Property';
-var DELAY_KEY = 'Delay';
-var TIMING_KEY = 'TimingFunction';
-var ANIMATION_ITERATION_COUNT_KEY = 'IterationCount';
-var ANIMATION_PLAYSTATE_KEY = 'PlayState';
var ELAPSED_TIME_MAX_DECIMAL_PLACES = 3;
var CLOSING_TIME_BUFFER = 1.5;
-var ONE_SECOND = 1000;
-var BASE_TEN = 10;
-var SAFE_FAST_FORWARD_DURATION_VALUE = 9999;
-
-var ANIMATION_DELAY_PROP = ANIMATION_PROP + DELAY_KEY;
-var ANIMATION_DURATION_PROP = ANIMATION_PROP + DURATION_KEY;
-
-var TRANSITION_DELAY_PROP = TRANSITION_PROP + DELAY_KEY;
-var TRANSITION_DURATION_PROP = TRANSITION_PROP + DURATION_KEY;
-
var DETECT_CSS_PROPERTIES = {
transitionDuration: TRANSITION_DURATION_PROP,
transitionDelay: TRANSITION_DELAY_PROP,
transitionProperty: TRANSITION_PROP + PROPERTY_KEY,
animationDuration: ANIMATION_DURATION_PROP,
@@ -600,10 +621,19 @@
transitionDelay: TRANSITION_DELAY_PROP,
animationDuration: ANIMATION_DURATION_PROP,
animationDelay: ANIMATION_DELAY_PROP
};
+function getCssKeyframeDurationStyle(duration) {
+ return [ANIMATION_DURATION_PROP, duration + 's'];
+}
+
+function getCssDelayStyle(delay, isKeyframeAnimation) {
+ var prop = isKeyframeAnimation ? ANIMATION_DELAY_PROP : TRANSITION_DELAY_PROP;
+ return [prop, delay + 's'];
+}
+
function computeCssStyles($window, element, properties) {
var styles = Object.create(null);
var detectedStyles = $window.getComputedStyle(element) || {};
forEach(properties, function(formalStyleName, actualStyleName) {
var val = detectedStyles[formalStyleName];
@@ -656,41 +686,10 @@
value += ' linear all';
}
return [style, value];
}
-function getCssKeyframeDurationStyle(duration) {
- return [ANIMATION_DURATION_PROP, duration + 's'];
-}
-
-function getCssDelayStyle(delay, isKeyframeAnimation) {
- var prop = isKeyframeAnimation ? ANIMATION_DELAY_PROP : TRANSITION_DELAY_PROP;
- return [prop, delay + 's'];
-}
-
-function blockTransitions(node, duration) {
- // we use a negative delay value since it performs blocking
- // yet it doesn't kill any existing transitions running on the
- // same element which makes this safe for class-based animations
- var value = duration ? '-' + duration + 's' : '';
- applyInlineStyle(node, [TRANSITION_DELAY_PROP, value]);
- return [TRANSITION_DELAY_PROP, value];
-}
-
-function blockKeyframeAnimations(node, applyBlock) {
- var value = applyBlock ? 'paused' : '';
- var key = ANIMATION_PROP + ANIMATION_PLAYSTATE_KEY;
- applyInlineStyle(node, [key, value]);
- return [key, value];
-}
-
-function applyInlineStyle(node, styleTuple) {
- var prop = styleTuple[0];
- var value = styleTuple[1];
- node.style[prop] = value;
-}
-
function createLocalCacheLookup() {
var cache = Object.create(null);
return {
flush: function() {
cache = Object.create(null);
@@ -718,14 +717,12 @@
var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
var gcsLookup = createLocalCacheLookup();
var gcsStaggerLookup = createLocalCacheLookup();
- this.$get = ['$window', '$$jqLite', '$$AnimateRunner', '$timeout',
- '$document', '$sniffer', '$$rAFScheduler',
- function($window, $$jqLite, $$AnimateRunner, $timeout,
- $document, $sniffer, $$rAFScheduler) {
+ this.$get = ['$window', '$$jqLite', '$$AnimateRunner', '$timeout', '$$forceReflow', '$sniffer', '$$rAF',
+ function($window, $$jqLite, $$AnimateRunner, $timeout, $$forceReflow, $sniffer, $$rAF) {
var applyAnimationClasses = applyAnimationClassesFactory($$jqLite);
var parentCounter = 0;
function gcsHashFn(node, extraClasses) {
@@ -778,32 +775,30 @@
}
return stagger || {};
}
- var bod = getDomNode($document).body;
+ var cancelLastRAFRequest;
var rafWaitQueue = [];
function waitUntilQuiet(callback) {
+ if (cancelLastRAFRequest) {
+ cancelLastRAFRequest(); //cancels the request
+ }
rafWaitQueue.push(callback);
- $$rAFScheduler.waitUntilQuiet(function() {
+ cancelLastRAFRequest = $$rAF(function() {
+ cancelLastRAFRequest = null;
gcsLookup.flush();
gcsStaggerLookup.flush();
- //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
- //ensure that the preparation animation is properly flushed so that
- //the active state picks up from there. DO NOT REMOVE THIS LINE.
- //DO NOT OPTIMIZE THIS LINE. THE MINIFIER WILL REMOVE IT OTHERWISE WHICH
- //WILL RESULT IN AN UNPREDICTABLE BUG THAT IS VERY HARD TO TRACK DOWN AND
- //WILL TAKE YEARS AWAY FROM YOUR LIFE.
- var width = bod.offsetWidth + 1;
+ // DO NOT REMOVE THIS LINE OR REFACTOR OUT THE `pageWidth` variable.
+ // PLEASE EXAMINE THE `$$forceReflow` service to understand why.
+ var pageWidth = $$forceReflow();
// we use a for loop to ensure that if the queue is changed
// during this looping then it will consider new requests
for (var i = 0; i < rafWaitQueue.length; i++) {
- rafWaitQueue[i](width);
+ rafWaitQueue[i](pageWidth);
}
rafWaitQueue.length = 0;
});
}
@@ -855,24 +850,24 @@
var isStructural = method && options.structural;
var structuralClassName = '';
var addRemoveClassName = '';
if (isStructural) {
- structuralClassName = pendClasses(method, 'ng-', true);
+ structuralClassName = pendClasses(method, EVENT_CLASS_PREFIX, true);
} else if (method) {
structuralClassName = method;
}
if (options.addClass) {
- addRemoveClassName += pendClasses(options.addClass, '-add');
+ addRemoveClassName += pendClasses(options.addClass, ADD_CLASS_SUFFIX);
}
if (options.removeClass) {
if (addRemoveClassName.length) {
addRemoveClassName += ' ';
}
- addRemoveClassName += pendClasses(options.removeClass, '-remove');
+ addRemoveClassName += pendClasses(options.removeClass, REMOVE_CLASS_SUFFIX);
}
// there may be a situation where a structural animation is combined together
// with CSS classes that need to resolve before the animation is computed.
// However this means that there is no explicit CSS code to block the animation
@@ -882,22 +877,22 @@
if (options.applyClassesEarly && addRemoveClassName.length) {
applyAnimationClasses(element, options);
addRemoveClassName = '';
}
- var setupClasses = [structuralClassName, addRemoveClassName].join(' ').trim();
- var fullClassName = classes + ' ' + setupClasses;
- var activeClasses = pendClasses(setupClasses, '-active');
+ var preparationClasses = [structuralClassName, addRemoveClassName].join(' ').trim();
+ var fullClassName = classes + ' ' + preparationClasses;
+ var activeClasses = pendClasses(preparationClasses, ACTIVE_CLASS_SUFFIX);
var hasToStyles = styles.to && Object.keys(styles.to).length > 0;
var containsKeyframeAnimation = (options.keyframeStyle || '').length > 0;
// there is no way we can trigger an animation if no styles and
// no classes are being applied which would then trigger a transition,
// unless there a is raw keyframe value that is applied to the element.
if (!containsKeyframeAnimation
&& !hasToStyles
- && !setupClasses) {
+ && !preparationClasses) {
return closeAndReturnNoopAnimator();
}
var cacheKey, stagger;
if (options.stagger > 0) {
@@ -908,14 +903,16 @@
transitionDuration: 0,
animationDuration: 0
};
} else {
cacheKey = gcsHashFn(node, fullClassName);
- stagger = computeCachedCssStaggerStyles(node, setupClasses, cacheKey, DETECT_STAGGER_CSS_PROPERTIES);
+ stagger = computeCachedCssStaggerStyles(node, preparationClasses, cacheKey, DETECT_STAGGER_CSS_PROPERTIES);
}
- $$jqLite.addClass(element, setupClasses);
+ if (!options.$$skipPreparationClasses) {
+ $$jqLite.addClass(element, preparationClasses);
+ }
var applyOnlyDuration;
if (options.transitionStyle) {
var transitionStyle = [TRANSITION_PROP, options.transitionStyle];
@@ -950,11 +947,11 @@
// without causing any combination of transitions to kick in. By adding a negative delay value
// it forces the setup class' transition to end immediately. We later then remove the negative
// transition delay to allow for the transition to naturally do it's thing. The beauty here is
// that if there is no transition defined then nothing will happen and this will also allow
// other transitions to be stacked on top of each other without any chopping them out.
- if (isFirst) {
+ if (isFirst && !options.skipBlocking) {
blockTransitions(node, SAFE_FAST_FORWARD_DURATION_VALUE);
}
var timings = computeTimings(node, fullClassName, cacheKey);
var relativeDelay = timings.maxDelay;
@@ -1009,16 +1006,17 @@
stagger.animationDelay > 0 &&
stagger.animationDuration === 0;
}
applyAnimationFromStyles(element, options);
- if (!flags.blockTransition) {
+
+ if (flags.blockTransition || flags.blockKeyframeAnimation) {
+ applyBlocking(maxDuration);
+ } else if (!options.skipBlocking) {
blockTransitions(node, false);
}
- applyBlocking(maxDuration);
-
// TODO(matsko): for 1.5 change this code to have an animator object for better debugging
return {
$$willAnimate: true,
end: endFn,
start: function() {
@@ -1056,11 +1054,13 @@
// the animation again
if (animationClosed || (animationCompleted && animationPaused)) return;
animationClosed = true;
animationPaused = false;
- $$jqLite.removeClass(element, setupClasses);
+ if (!options.$$skipPreparationClasses) {
+ $$jqLite.removeClass(element, preparationClasses);
+ }
$$jqLite.removeClass(element, activeClasses);
blockKeyframeAnimations(node, false);
blockTransitions(node, false);
@@ -1183,11 +1183,11 @@
applyAnimationClasses(element, options);
$$jqLite.addClass(element, activeClasses);
if (flags.recalculateTimingStyles) {
- fullClassName = node.className + ' ' + setupClasses;
+ fullClassName = node.className + ' ' + preparationClasses;
cacheKey = gcsHashFn(node, fullClassName);
timings = computeTimings(node, fullClassName, cacheKey);
relativeDelay = timings.maxDelay;
maxDelay = Math.max(relativeDelay, 0);
@@ -1250,11 +1250,11 @@
events.push(ANIMATIONEND_EVENT);
}
startTime = Date.now();
element.on(events.join(' '), onAnimationProgress);
- $timeout(onAnimationExpired, maxDelayTime + CLOSING_TIME_BUFFER * maxDurationTime);
+ $timeout(onAnimationExpired, maxDelayTime + CLOSING_TIME_BUFFER * maxDurationTime, false);
applyAnimationToStyles(element, options);
}
function onAnimationExpired() {
@@ -1299,28 +1299,30 @@
var NG_ANIMATE_ANCHOR_CLASS_NAME = 'ng-anchor';
var NG_OUT_ANCHOR_CLASS_NAME = 'ng-anchor-out';
var NG_IN_ANCHOR_CLASS_NAME = 'ng-anchor-in';
- this.$get = ['$animateCss', '$rootScope', '$$AnimateRunner', '$rootElement', '$document', '$sniffer',
- function($animateCss, $rootScope, $$AnimateRunner, $rootElement, $document, $sniffer) {
+ this.$get = ['$animateCss', '$rootScope', '$$AnimateRunner', '$rootElement', '$$body', '$sniffer', '$$jqLite',
+ function($animateCss, $rootScope, $$AnimateRunner, $rootElement, $$body, $sniffer, $$jqLite) {
// only browsers that support these properties can render animations
if (!$sniffer.animations && !$sniffer.transitions) return noop;
- var bodyNode = getDomNode($document).body;
+ var bodyNode = getDomNode($$body);
var rootNode = getDomNode($rootElement);
var rootBodyElement = jqLite(bodyNode.parentNode === rootNode ? bodyNode : rootNode);
- return function initDriverFn(animationDetails) {
+ var applyAnimationClasses = applyAnimationClassesFactory($$jqLite);
+
+ return function initDriverFn(animationDetails, onBeforeClassesAppliedCb) {
return animationDetails.from && animationDetails.to
? prepareFromToAnchorAnimation(animationDetails.from,
animationDetails.to,
animationDetails.classes,
animationDetails.anchors)
- : prepareRegularAnimation(animationDetails);
+ : prepareRegularAnimation(animationDetails, onBeforeClassesAppliedCb);
};
function filterCssClasses(classes) {
//remove all the `ng-` stuff
return classes.replace(/\bng-\S+\b/g, '');
@@ -1460,12 +1462,12 @@
inAnchor.removeClass(NG_ANIMATE_SHIM_CLASS_NAME);
}
}
function prepareFromToAnchorAnimation(from, to, classes, anchors) {
- var fromAnimation = prepareRegularAnimation(from);
- var toAnimation = prepareRegularAnimation(to);
+ var fromAnimation = prepareRegularAnimation(from, noop);
+ var toAnimation = prepareRegularAnimation(to, noop);
var anchorAnimations = [];
forEach(anchors, function(anchor) {
var outElement = anchor['out'];
var inElement = anchor['in'];
@@ -1512,30 +1514,46 @@
}
}
};
}
- function prepareRegularAnimation(animationDetails) {
+ function prepareRegularAnimation(animationDetails, onBeforeClassesAppliedCb) {
var element = animationDetails.element;
var options = animationDetails.options || {};
+ // since the ng-EVENT, class-ADD and class-REMOVE classes are applied inside
+ // of the animateQueue pre and postDigest stages then there is no need to add
+ // then them here as well.
+ options.$$skipPreparationClasses = true;
+
+ // during the pre/post digest stages inside of animateQueue we also performed
+ // the blocking (transition:-9999s) so there is no point in doing that again.
+ options.skipBlocking = true;
+
if (animationDetails.structural) {
- // structural animations ensure that the CSS classes are always applied
- // before the detection starts.
- options.structural = options.applyClassesEarly = true;
+ options.event = animationDetails.event;
// we special case the leave animation since we want to ensure that
// the element is removed as soon as the animation is over. Otherwise
// a flicker might appear or the element may not be removed at all
- options.event = animationDetails.event;
- if (options.event === 'leave') {
+ if (animationDetails.event === 'leave') {
options.onDone = options.domOperation;
}
- } else {
- options.event = null;
}
+ // we apply the classes right away since the pre-digest took care of the
+ // preparation classes.
+ onBeforeClassesAppliedCb(element);
+ applyAnimationClasses(element, options);
+
+ // We assign the preparationClasses as the actual animation event since
+ // the internals of $animateCss will just suffix the event token values
+ // with `-active` to trigger the animation.
+ if (options.preparationClasses) {
+ options.event = concatWithSpace(options.event, options.preparationClasses);
+ }
+
var animator = $animateCss(element, options);
// the driver lookup code inside of $$animation attempts to spawn a
// driver one by one until a driver returns a.$$willAnimate animator object.
// $animateCss will always return an object, however, it will pass in
@@ -1906,12 +1924,12 @@
// be removed from the DOM anyway?
return currentAnimation.event == 'leave' && newAnimation.structural;
});
rules.skip.push(function(element, newAnimation, currentAnimation) {
- // if there is a current animation then skip the class-based animation
- return currentAnimation.structural && !newAnimation.structural;
+ // if there is an ongoing current animation then don't even bother running the class-based animation
+ return currentAnimation.structural && currentAnimation.state === RUNNING_STATE && !newAnimation.structural;
});
rules.cancel.push(function(element, newAnimation, currentAnimation) {
// there can never be two structural animations running at the same time
return currentAnimation.structural && newAnimation.structural;
@@ -1929,18 +1947,17 @@
// if the exact same CSS class is added/removed then it's safe to cancel it
return (nO.addClass && nO.addClass === cO.removeClass) || (nO.removeClass && nO.removeClass === cO.addClass);
});
- this.$get = ['$$rAF', '$rootScope', '$rootElement', '$document', '$$HashMap',
- '$$animation', '$$AnimateRunner', '$templateRequest', '$$jqLite',
- function($$rAF, $rootScope, $rootElement, $document, $$HashMap,
- $$animation, $$AnimateRunner, $templateRequest, $$jqLite) {
+ this.$get = ['$$rAF', '$rootScope', '$rootElement', '$document', '$$body', '$$HashMap',
+ '$$animation', '$$AnimateRunner', '$templateRequest', '$$jqLite', '$$forceReflow',
+ function($$rAF, $rootScope, $rootElement, $document, $$body, $$HashMap,
+ $$animation, $$AnimateRunner, $templateRequest, $$jqLite, $$forceReflow) {
var activeAnimationsLookup = new $$HashMap();
var disabledElementsLookup = new $$HashMap();
-
var animationsEnabled = null;
// Wait until all directive and route-related templates are downloaded and
// compiled. The $templateRequest.totalPendingRequests variable keeps track of
// all of the remote templates being currently downloaded. If there are no
@@ -1968,12 +1985,10 @@
});
});
}
);
- var bodyElement = jqLite($document[0].body);
-
var callbackRegistry = {};
// remember that the classNameFilter is set during the provider/config
// stage therefore we can optimize here and setup a helper function
var classNameFilter = $animateProvider.classNameFilter();
@@ -2105,34 +2120,42 @@
// we create a fake runner with a working promise.
// These methods will become available after the digest has passed
var runner = new $$AnimateRunner();
- // there are situations where a directive issues an animation for
- // a jqLite wrapper that contains only comment nodes... If this
- // happens then there is no way we can perform an animation
- if (!node) {
- close();
- return runner;
- }
-
if (isArray(options.addClass)) {
options.addClass = options.addClass.join(' ');
}
+ if (options.addClass && !isString(options.addClass)) {
+ options.addClass = null;
+ }
+
if (isArray(options.removeClass)) {
options.removeClass = options.removeClass.join(' ');
}
+ if (options.removeClass && !isString(options.removeClass)) {
+ options.removeClass = null;
+ }
+
if (options.from && !isObject(options.from)) {
options.from = null;
}
if (options.to && !isObject(options.to)) {
options.to = null;
}
+ // there are situations where a directive issues an animation for
+ // a jqLite wrapper that contains only comment nodes... If this
+ // happens then there is no way we can perform an animation
+ if (!node) {
+ close();
+ return runner;
+ }
+
var className = [node.className, options.addClass, options.removeClass].join(' ');
if (!isAnimatableClassName(className)) {
close();
return runner;
}
@@ -2193,25 +2216,31 @@
// this means that the animation is queued into a digest, but
// hasn't started yet. Therefore it is safe to run the close
// method which will call the runner methods in async.
existingAnimation.close();
} else {
- // this will merge the existing animation options into this new follow-up animation
- mergeAnimationOptions(element, newAnimation.options, existingAnimation.options);
+ // this will merge the new animation options into existing animation options
+ mergeAnimationOptions(element, existingAnimation.options, newAnimation.options);
+ return existingAnimation.runner;
}
} else {
// a joined animation means that this animation will take over the existing one
// so an example would involve a leave animation taking over an enter. Then when
// the postDigest kicks in the enter will be ignored.
var joinAnimationFlag = isAllowed('join', element, newAnimation, existingAnimation);
if (joinAnimationFlag) {
if (existingAnimation.state === RUNNING_STATE) {
normalizeAnimationOptions(element, options);
} else {
+ applyGeneratedPreparationClasses(element, isStructural ? event : null, options);
+
event = newAnimation.event = existingAnimation.event;
options = mergeAnimationOptions(element, existingAnimation.options, newAnimation.options);
- return runner;
+
+ //we return the same runner since only the option values of this animation will
+ //be fed into the `existingAnimation`.
+ return existingAnimation.runner;
}
}
}
} else {
// normalization in this case means that it removes redundant CSS classes that
@@ -2233,13 +2262,12 @@
close();
clearElementAnimationState(element);
return runner;
}
- if (isStructural) {
- closeParentClassBasedAnimations(parent);
- }
+ applyGeneratedPreparationClasses(element, isStructural ? event : null, options);
+ blockTransitions(node, SAFE_FAST_FORWARD_DURATION_VALUE);
// the counter keeps track of cancelled animations
var counter = (existingAnimation.counter || 0) + 1;
newAnimation.counter = counter;
@@ -2294,16 +2322,16 @@
// so long as a structural event did not take over the animation
event = !animationDetails.structural && hasAnimationClasses(animationDetails.options, true)
? 'setClass'
: animationDetails.event;
- if (animationDetails.structural) {
- closeParentClassBasedAnimations(parentElement);
- }
-
markElementAnimationState(element, RUNNING_STATE);
- var realRunner = $$animation(element, event, animationDetails.options);
+ var realRunner = $$animation(element, event, animationDetails.options, function(e) {
+ $$forceReflow();
+ blockTransitions(getDomNode(e), false);
+ });
+
realRunner.done(function(status) {
close(!status);
var animationDetails = activeAnimationsLookup.get(node);
if (animationDetails && animationDetails.counter === counter) {
clearElementAnimationState(getDomNode(element));
@@ -2323,10 +2351,11 @@
triggerCallback(event, element, phase, data);
runner.progress(event, phase, data);
}
function close(reject) { // jshint ignore:line
+ clearGeneratedClasses(element, options);
applyAnimationClasses(element, options);
applyAnimationStyles(element, options);
options.domOperation();
runner.complete(!reject);
}
@@ -2359,40 +2388,13 @@
function isMatchingElement(nodeOrElmA, nodeOrElmB) {
return getDomNode(nodeOrElmA) === getDomNode(nodeOrElmB);
}
- function closeParentClassBasedAnimations(startingElement) {
- var parentNode = getDomNode(startingElement);
- do {
- if (!parentNode || parentNode.nodeType !== ELEMENT_NODE) break;
-
- var animationDetails = activeAnimationsLookup.get(parentNode);
- if (animationDetails) {
- examineParentAnimation(parentNode, animationDetails);
- }
-
- parentNode = parentNode.parentNode;
- } while (true);
-
- // since animations are detected from CSS classes, we need to flush all parent
- // class-based animations so that the parent classes are all present for child
- // animations to properly function (otherwise any CSS selectors may not work)
- function examineParentAnimation(node, animationDetails) {
- // enter/leave/move always have priority
- if (animationDetails.structural || !hasAnimationClasses(animationDetails.options)) return;
-
- if (animationDetails.state === RUNNING_STATE) {
- animationDetails.runner.end();
- }
- clearElementAnimationState(node);
- }
- }
-
function areAnimationsAllowed(element, parentElement, event) {
- var bodyElementDetected = false;
- var rootElementDetected = false;
+ var bodyElementDetected = isMatchingElement(element, $$body) || element[0].nodeName === 'HTML';
+ var rootElementDetected = isMatchingElement(element, $rootElement);
var parentAnimationDetected = false;
var animateChildren;
var parentHost = element.data(NG_ANIMATE_PIN_DATA);
if (parentHost) {
@@ -2443,11 +2445,11 @@
}
if (!bodyElementDetected) {
// we also need to ensure that the element is or will be apart of the body element
// otherwise it is pointless to even issue an animation to be rendered
- bodyElementDetected = isMatchingElement(parentElement, bodyElement);
+ bodyElementDetected = isMatchingElement(parentElement, $$body);
}
parentElement = parentElement.parent();
}
@@ -2638,22 +2640,99 @@
function getRunner(element) {
return element.data(RUNNER_STORAGE_KEY);
}
- this.$get = ['$$jqLite', '$rootScope', '$injector', '$$AnimateRunner', '$$rAFScheduler',
- function($$jqLite, $rootScope, $injector, $$AnimateRunner, $$rAFScheduler) {
+ this.$get = ['$$jqLite', '$rootScope', '$injector', '$$AnimateRunner', '$$HashMap',
+ function($$jqLite, $rootScope, $injector, $$AnimateRunner, $$HashMap) {
var animationQueue = [];
var applyAnimationClasses = applyAnimationClassesFactory($$jqLite);
- var totalPendingClassBasedAnimations = 0;
- var totalActiveClassBasedAnimations = 0;
- var classBasedAnimationsQueue = [];
+ function sortAnimations(animations) {
+ var tree = { children: [] };
+ var i, lookup = new $$HashMap();
+ // this is done first beforehand so that the hashmap
+ // is filled with a list of the elements that will be animated
+ for (i = 0; i < animations.length; i++) {
+ var animation = animations[i];
+ lookup.put(animation.domNode, animations[i] = {
+ domNode: animation.domNode,
+ fn: animation.fn,
+ children: []
+ });
+ }
+
+ for (i = 0; i < animations.length; i++) {
+ processNode(animations[i]);
+ }
+
+ return flatten(tree);
+
+ function processNode(entry) {
+ if (entry.processed) return entry;
+ entry.processed = true;
+
+ var elementNode = entry.domNode;
+ var parentNode = elementNode.parentNode;
+ lookup.put(elementNode, entry);
+
+ var parentEntry;
+ while (parentNode) {
+ parentEntry = lookup.get(parentNode);
+ if (parentEntry) {
+ if (!parentEntry.processed) {
+ parentEntry = processNode(parentEntry);
+ }
+ break;
+ }
+ parentNode = parentNode.parentNode;
+ }
+
+ (parentEntry || tree).children.push(entry);
+ return entry;
+ }
+
+ function flatten(tree) {
+ var result = [];
+ var queue = [];
+ var i;
+
+ for (i = 0; i < tree.children.length; i++) {
+ queue.push(tree.children[i]);
+ }
+
+ var remainingLevelEntries = queue.length;
+ var nextLevelEntries = 0;
+ var row = [];
+
+ for (i = 0; i < queue.length; i++) {
+ var entry = queue[i];
+ if (remainingLevelEntries <= 0) {
+ remainingLevelEntries = nextLevelEntries;
+ nextLevelEntries = 0;
+ result = result.concat(row);
+ row = [];
+ }
+ row.push(entry.fn);
+ forEach(entry.children, function(childEntry) {
+ nextLevelEntries++;
+ queue.push(childEntry);
+ });
+ remainingLevelEntries--;
+ }
+
+ if (row.length) {
+ result = result.concat(row);
+ }
+ return result;
+ }
+ }
+
// TODO(matsko): document the signature in a better way
- return function(element, event, options) {
+ return function(element, event, options, onBeforeClassesAppliedCb) {
options = prepareAnimationOptions(options);
var isStructural = ['enter', 'move', 'leave'].indexOf(event) >= 0;
// there is no animation at the current moment, however
// these runner methods will get later updated with the
@@ -2676,23 +2755,16 @@
if (tempClasses) {
classes += ' ' + tempClasses;
options.tempClasses = null;
}
- var classBasedIndex;
- if (!isStructural) {
- classBasedIndex = totalPendingClassBasedAnimations;
- totalPendingClassBasedAnimations += 1;
- }
-
animationQueue.push({
// this data is used by the postDigest code and passed into
// the driver step function
element: element,
classes: classes,
event: event,
- classBasedIndex: classBasedIndex,
structural: isStructural,
options: options,
beforeStart: beforeStart,
close: close
});
@@ -2703,81 +2775,71 @@
// block. This way we can group animations for all the animations that
// were apart of the same postDigest flush call.
if (animationQueue.length > 1) return runner;
$rootScope.$$postDigest(function() {
- totalActiveClassBasedAnimations = totalPendingClassBasedAnimations;
- totalPendingClassBasedAnimations = 0;
- classBasedAnimationsQueue.length = 0;
-
var animations = [];
forEach(animationQueue, function(entry) {
// the element was destroyed early on which removed the runner
// form its storage. This means we can't animate this element
// at all and it already has been closed due to destruction.
- if (getRunner(entry.element)) {
+ var elm = entry.element;
+ if (getRunner(elm) && getDomNode(elm).parentNode) {
animations.push(entry);
+ } else {
+ entry.close();
}
});
// now any future animations will be in another postDigest
animationQueue.length = 0;
- forEach(groupAnimations(animations), function(animationEntry) {
- if (animationEntry.structural) {
- triggerAnimationStart();
- } else {
- classBasedAnimationsQueue.push({
- node: getDomNode(animationEntry.element),
- fn: triggerAnimationStart
- });
+ var groupedAnimations = groupAnimations(animations);
+ var toBeSortedAnimations = [];
- if (animationEntry.classBasedIndex === totalActiveClassBasedAnimations - 1) {
- // we need to sort each of the animations in order of parent to child
- // relationships. This ensures that the child classes are applied at the
- // right time.
- classBasedAnimationsQueue = classBasedAnimationsQueue.sort(function(a,b) {
- return b.node.contains(a.node);
- }).map(function(entry) {
- return entry.fn;
- });
+ forEach(groupedAnimations, function(animationEntry) {
+ toBeSortedAnimations.push({
+ domNode: getDomNode(animationEntry.from ? animationEntry.from.element : animationEntry.element),
+ fn: function triggerAnimationStart() {
+ // it's important that we apply the `ng-animate` CSS class and the
+ // temporary classes before we do any driver invoking since these
+ // CSS classes may be required for proper CSS detection.
+ animationEntry.beforeStart();
- $$rAFScheduler(classBasedAnimationsQueue);
- }
- }
+ var startAnimationFn, closeFn = animationEntry.close;
- function triggerAnimationStart() {
- // it's important that we apply the `ng-animate` CSS class and the
- // temporary classes before we do any driver invoking since these
- // CSS classes may be required for proper CSS detection.
- animationEntry.beforeStart();
+ // in the event that the element was removed before the digest runs or
+ // during the RAF sequencing then we should not trigger the animation.
+ var targetElement = animationEntry.anchors
+ ? (animationEntry.from.element || animationEntry.to.element)
+ : animationEntry.element;
- var startAnimationFn, closeFn = animationEntry.close;
+ if (getRunner(targetElement)) {
+ var operation = invokeFirstDriver(animationEntry, onBeforeClassesAppliedCb);
+ if (operation) {
+ startAnimationFn = operation.start;
+ }
+ }
- // in the event that the element was removed before the digest runs or
- // during the RAF sequencing then we should not trigger the animation.
- var targetElement = animationEntry.anchors
- ? (animationEntry.from.element || animationEntry.to.element)
- : animationEntry.element;
-
- if (getRunner(targetElement) && getDomNode(targetElement).parentNode) {
- var operation = invokeFirstDriver(animationEntry);
- if (operation) {
- startAnimationFn = operation.start;
+ if (!startAnimationFn) {
+ closeFn();
+ } else {
+ var animationRunner = startAnimationFn();
+ animationRunner.done(function(status) {
+ closeFn(!status);
+ });
+ updateAnimationRunners(animationEntry, animationRunner);
}
}
+ });
+ });
- if (!startAnimationFn) {
- closeFn();
- } else {
- var animationRunner = startAnimationFn();
- animationRunner.done(function(status) {
- closeFn(!status);
- });
- updateAnimationRunners(animationEntry, animationRunner);
- }
- }
+ // we need to sort each of the animations in order of parent to child
+ // relationships. This ensures that the parent to child classes are
+ // applied at the right time.
+ forEach(sortAnimations(toBeSortedAnimations), function(triggerAnimation) {
+ triggerAnimation();
});
});
return runner;
@@ -2844,11 +2906,11 @@
var fromAnimation = animations[from.animationID];
var toAnimation = animations[to.animationID];
var lookupKey = from.animationID.toString();
if (!anchorGroups[lookupKey]) {
var group = anchorGroups[lookupKey] = {
- structural: true,
+ // TODO(matsko): double-check this code
beforeStart: function() {
fromAnimation.beforeStart();
toAnimation.beforeStart();
},
close: function() {
@@ -2898,19 +2960,19 @@
}
return matches.join(' ');
}
- function invokeFirstDriver(animationDetails) {
+ function invokeFirstDriver(animationDetails, onBeforeClassesAppliedCb) {
// we loop in reverse order since the more general drivers (like CSS and JS)
// may attempt more elements, but custom drivers are more particular
for (var i = drivers.length - 1; i >= 0; i--) {
var driverName = drivers[i];
if (!$injector.has(driverName)) continue; // TODO(matsko): remove this check
var factory = $injector.get(driverName);
- var driver = factory(animationDetails);
+ var driver = factory(animationDetails, onBeforeClassesAppliedCb);
if (driver) {
return driver;
}
}
}
@@ -2961,12 +3023,12 @@
}];
}];
/* global angularAnimateModule: true,
+ $$BodyProvider,
$$rAFMutexFactory,
- $$rAFSchedulerFactory,
$$AnimateChildrenDirective,
$$AnimateRunnerFactory,
$$AnimateQueueProvider,
$$AnimationProvider,
$AnimateCssProvider,
@@ -3346,10 +3408,11 @@
* myModule.animation('.slide', ['$animateCss', function($animateCss) {
* return {
* enter: function(element, doneFn) {
* var runner = $animateCss(element, {
* event: 'enter',
+ * structural: true,
* addClass: 'maroon-setting',
* from: { height:0 },
* to: { height: 200 }
* }).start();
*
@@ -3699,13 +3762,14 @@
* The ngAnimate `$animate` service documentation is the same for the core `$animate` service.
*
* Click here {@link ng.$animate $animate to learn more about animations with `$animate`}.
*/
angular.module('ngAnimate', [])
+ .provider('$$body', $$BodyProvider)
+
.directive('ngAnimateChildren', $$AnimateChildrenDirective)
.factory('$$rAFMutex', $$rAFMutexFactory)
- .factory('$$rAFScheduler', $$rAFSchedulerFactory)
.factory('$$AnimateRunner', $$AnimateRunnerFactory)
.provider('$$animateQueue', $$AnimateQueueProvider)
.provider('$$animation', $$AnimationProvider)