summaryrefslogtreecommitdiffstats
path: root/js/vendor/angular-animate/angular-animate.js
diff options
context:
space:
mode:
Diffstat (limited to 'js/vendor/angular-animate/angular-animate.js')
-rw-r--r--js/vendor/angular-animate/angular-animate.js221
1 files changed, 179 insertions, 42 deletions
diff --git a/js/vendor/angular-animate/angular-animate.js b/js/vendor/angular-animate/angular-animate.js
index 126be09b9..42b03e0b5 100644
--- a/js/vendor/angular-animate/angular-animate.js
+++ b/js/vendor/angular-animate/angular-animate.js
@@ -1,5 +1,5 @@
/**
- * @license AngularJS v1.4.0-rc.2
+ * @license AngularJS v1.4.0
* (c) 2010-2015 Google, Inc. http://angularjs.org
* License: MIT
*/
@@ -256,6 +256,64 @@ function getDomNode(element) {
return (element instanceof angular.element) ? element[0] : element;
}
+var $$rAFSchedulerFactory = ['$$rAF', function($$rAF) {
+ var tickQueue = [];
+ var cancelFn;
+
+ 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();
+ }
+
+ /* 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();
+
+ cancelFn = $$rAF(function() {
+ cancelFn = null;
+ fn();
+ nextTick();
+ });
+ };
+
+ return scheduler;
+
+ function nextTick() {
+ if (!tickQueue.length) return;
+
+ var updatedQueue = [];
+ for (var i = 0; i < tickQueue.length; i++) {
+ var innerQueue = tickQueue[i];
+ runNextTask(innerQueue);
+ if (innerQueue.length) {
+ updatedQueue.push(innerQueue);
+ }
+ }
+ tickQueue = updatedQueue;
+
+ 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;
@@ -662,9 +720,9 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
var gcsStaggerLookup = createLocalCacheLookup();
this.$get = ['$window', '$$jqLite', '$$AnimateRunner', '$timeout',
- '$document', '$sniffer', '$$rAF',
+ '$document', '$sniffer', '$$rAFScheduler',
function($window, $$jqLite, $$AnimateRunner, $timeout,
- $document, $sniffer, $$rAF) {
+ $document, $sniffer, $$rAFScheduler) {
var applyAnimationClasses = applyAnimationClassesFactory($$jqLite);
@@ -722,15 +780,10 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
}
var bod = getDomNode($document).body;
- var cancelLastRAFRequest;
var rafWaitQueue = [];
function waitUntilQuiet(callback) {
- if (cancelLastRAFRequest) {
- cancelLastRAFRequest(); //cancels the request
- }
rafWaitQueue.push(callback);
- cancelLastRAFRequest = $$rAF(function() {
- cancelLastRAFRequest = null;
+ $$rAFScheduler.waitUntilQuiet(function() {
gcsLookup.flush();
gcsStaggerLookup.flush();
@@ -935,7 +988,7 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
// we need to recalculate the delay value since we used a pre-emptive negative
// delay value and the delay value is required for the final event checking. This
// property will ensure that this will happen after the RAF phase has passed.
- if (timings.transitionDuration > 0) {
+ if (options.duration == null && timings.transitionDuration > 0) {
flags.recalculateTimingStyles = flags.recalculateTimingStyles || isFirst;
}
@@ -1452,18 +1505,20 @@ var $$AnimateCssDriverProvider = ['$$animationProvider', function($$animationPro
var element = animationDetails.element;
var options = animationDetails.options || {};
- options.structural = animationDetails.structural;
-
- // structural animations ensure that the CSS classes are always applied
- // before the detection starts.
- options.applyClassesEarly = options.structural;
-
- // 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' && animationDetails.domOperation) {
- options.onDone = animationDetails.domOperation;
+ if (animationDetails.structural) {
+ // structural animations ensure that the CSS classes are always applied
+ // before the detection starts.
+ options.structural = options.applyClassesEarly = true;
+
+ // 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') {
+ options.onDone = options.domOperation;
+ }
+ } else {
+ options.event = null;
}
var animator = $animateCss(element, options);
@@ -1855,6 +1910,14 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
return currentAnimation.state === RUNNING_STATE && newAnimation.structural;
});
+ rules.cancel.push(function(element, newAnimation, currentAnimation) {
+ var nO = newAnimation.options;
+ var cO = currentAnimation.options;
+
+ // 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,
@@ -2089,6 +2152,7 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
structural: isStructural,
element: element,
event: event,
+ close: close,
options: options,
runner: runner
};
@@ -2108,8 +2172,17 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
var cancelAnimationFlag = isAllowed('cancel', element, newAnimation, existingAnimation);
if (cancelAnimationFlag) {
if (existingAnimation.state === RUNNING_STATE) {
+ // this will end the animation right away and it is safe
+ // to do so since the animation is already running and the
+ // runner callback code will run in async
existingAnimation.runner.end();
+ } else if (existingAnimation.structural) {
+ // 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);
}
} else {
@@ -2145,10 +2218,13 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
if (!isValidAnimation) {
close();
+ clearElementAnimationState(element);
return runner;
}
- closeParentClassBasedAnimations(parent);
+ if (isStructural) {
+ closeParentClassBasedAnimations(parent);
+ }
// the counter keeps track of cancelled animations
var counter = (existingAnimation.counter || 0) + 1;
@@ -2191,6 +2267,13 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
runner.end();
}
+ // in the event that the element animation was not cancelled or a follow-up animation
+ // isn't allowed to animate from here then we need to clear the state of the element
+ // so that any future animations won't read the expired animation data.
+ if (!isValidAnimation) {
+ clearElementAnimationState(element);
+ }
+
return;
}
@@ -2200,7 +2283,9 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
? 'setClass'
: animationDetails.event;
- closeParentClassBasedAnimations(parentElement);
+ if (animationDetails.structural) {
+ closeParentClassBasedAnimations(parentElement);
+ }
markElementAnimationState(element, RUNNING_STATE);
var realRunner = $$animation(element, event, animationDetails.options);
@@ -2542,12 +2627,16 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) {
return element.data(RUNNER_STORAGE_KEY);
}
- this.$get = ['$$jqLite', '$rootScope', '$injector', '$$AnimateRunner',
- function($$jqLite, $rootScope, $injector, $$AnimateRunner) {
+ this.$get = ['$$jqLite', '$rootScope', '$injector', '$$AnimateRunner', '$$rAFScheduler',
+ function($$jqLite, $rootScope, $injector, $$AnimateRunner, $$rAFScheduler) {
var animationQueue = [];
var applyAnimationClasses = applyAnimationClassesFactory($$jqLite);
+ var totalPendingClassBasedAnimations = 0;
+ var totalActiveClassBasedAnimations = 0;
+ var classBasedAnimationsQueue = [];
+
// TODO(matsko): document the signature in a better way
return function(element, event, options) {
options = prepareAnimationOptions(options);
@@ -2576,12 +2665,19 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) {
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,
@@ -2596,6 +2692,10 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) {
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
@@ -2610,23 +2710,58 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) {
animationQueue.length = 0;
forEach(groupAnimations(animations), function(animationEntry) {
- // 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();
-
- var operation = invokeFirstDriver(animationEntry);
- var triggerAnimationStart = operation && operation.start; /// TODO(matsko): only recognize operation.start()
-
- var closeFn = animationEntry.close;
- if (!triggerAnimationStart) {
- closeFn();
+ if (animationEntry.structural) {
+ triggerAnimationStart();
} else {
- var animationRunner = triggerAnimationStart();
- animationRunner.done(function(status) {
- closeFn(!status);
+ classBasedAnimationsQueue.push({
+ node: getDomNode(animationEntry.element),
+ fn: triggerAnimationStart
});
- updateAnimationRunners(animationEntry, animationRunner);
+
+ 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;
+ });
+
+ $$rAFScheduler(classBasedAnimationsQueue);
+ }
+ }
+
+ 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();
+
+ var startAnimationFn, closeFn = animationEntry.close;
+
+ // 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)) {
+ 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);
+ }
}
});
});
@@ -2698,7 +2833,7 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) {
var lookupKey = from.animationID.toString();
if (!anchorGroups[lookupKey]) {
var group = anchorGroups[lookupKey] = {
- // TODO(matsko): double-check this code
+ structural: true,
beforeStart: function() {
fromAnimation.beforeStart();
toAnimation.beforeStart();
@@ -2816,6 +2951,7 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) {
/* global angularAnimateModule: true,
$$rAFMutexFactory,
+ $$rAFSchedulerFactory,
$$AnimateChildrenDirective,
$$AnimateRunnerFactory,
$$AnimateQueueProvider,
@@ -3555,6 +3691,7 @@ angular.module('ngAnimate', [])
.directive('ngAnimateChildren', $$AnimateChildrenDirective)
.factory('$$rAFMutex', $$rAFMutexFactory)
+ .factory('$$rAFScheduler', $$rAFSchedulerFactory)
.factory('$$AnimateRunner', $$AnimateRunnerFactory)