Commit 25b27800 authored by bingchuan's avatar bingchuan

[dev] version 1.8.2

parent efc744d0
/** /**
* @license AngularJS v1.5.7 * @license AngularJS v1.8.2
* (c) 2010-2016 Google, Inc. http://angularjs.org * (c) 2010-2020 Google LLC. http://angularjs.org
* License: MIT * License: MIT
*/ */
(function(window, angular) {'use strict'; (function(window, angular) {'use strict';
/* jshint ignore:start */ var ELEMENT_NODE = 1;
var noop = angular.noop; var COMMENT_NODE = 8;
var copy = angular.copy;
var extend = angular.extend; var ADD_CLASS_SUFFIX = '-add';
var jqLite = angular.element; var REMOVE_CLASS_SUFFIX = '-remove';
var forEach = angular.forEach; var EVENT_CLASS_PREFIX = 'ng-';
var isArray = angular.isArray; var ACTIVE_CLASS_SUFFIX = '-active';
var isString = angular.isString; var PREPARE_CLASS_SUFFIX = '-prepare';
var isObject = angular.isObject;
var isUndefined = angular.isUndefined; var NG_ANIMATE_CLASSNAME = 'ng-animate';
var isDefined = angular.isDefined; var NG_ANIMATE_CHILDREN_DATA = '$$ngAnimateChildren';
var isFunction = angular.isFunction;
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 PREPARE_CLASS_SUFFIX = '-prepare';
var NG_ANIMATE_CLASSNAME = 'ng-animate';
var NG_ANIMATE_CHILDREN_DATA = '$$ngAnimateChildren';
// Detect proper transitionend/animationend event names. // Detect proper transitionend/animationend event names.
var CSS_PREFIX = '', TRANSITION_PROP, TRANSITIONEND_EVENT, ANIMATION_PROP, ANIMATIONEND_EVENT; var CSS_PREFIX = '', TRANSITION_PROP, TRANSITIONEND_EVENT, ANIMATION_PROP, ANIMATIONEND_EVENT;
// If unprefixed events are not supported but webkit-prefixed are, use the latter. // 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. // Otherwise, just use W3C names, browsers not supporting them at all will just ignore them.
...@@ -43,68 +29,64 @@ var CSS_PREFIX = '', TRANSITION_PROP, TRANSITIONEND_EVENT, ANIMATION_PROP, ANIMA ...@@ -43,68 +29,64 @@ var CSS_PREFIX = '', TRANSITION_PROP, TRANSITIONEND_EVENT, ANIMATION_PROP, ANIMA
// Also, the only modern browser that uses vendor prefixes for transitions/keyframes is webkit // 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: // therefore there is no reason to test anymore for other vendor prefixes:
// http://caniuse.com/#search=transition // http://caniuse.com/#search=transition
if (isUndefined(window.ontransitionend) && isDefined(window.onwebkittransitionend)) { if ((window.ontransitionend === undefined) && (window.onwebkittransitionend !== undefined)) {
CSS_PREFIX = '-webkit-'; CSS_PREFIX = '-webkit-';
TRANSITION_PROP = 'WebkitTransition'; TRANSITION_PROP = 'WebkitTransition';
TRANSITIONEND_EVENT = 'webkitTransitionEnd transitionend'; TRANSITIONEND_EVENT = 'webkitTransitionEnd transitionend';
} else { } else {
TRANSITION_PROP = 'transition'; TRANSITION_PROP = 'transition';
TRANSITIONEND_EVENT = 'transitionend'; TRANSITIONEND_EVENT = 'transitionend';
} }
if (isUndefined(window.onanimationend) && isDefined(window.onwebkitanimationend)) { if ((window.onanimationend === undefined) && (window.onwebkitanimationend !== undefined)) {
CSS_PREFIX = '-webkit-'; CSS_PREFIX = '-webkit-';
ANIMATION_PROP = 'WebkitAnimation'; ANIMATION_PROP = 'WebkitAnimation';
ANIMATIONEND_EVENT = 'webkitAnimationEnd animationend'; ANIMATIONEND_EVENT = 'webkitAnimationEnd animationend';
} else { } else {
ANIMATION_PROP = 'animation'; ANIMATION_PROP = 'animation';
ANIMATIONEND_EVENT = 'animationend'; ANIMATIONEND_EVENT = 'animationend';
} }
var DURATION_KEY = 'Duration'; var DURATION_KEY = 'Duration';
var PROPERTY_KEY = 'Property'; var PROPERTY_KEY = 'Property';
var DELAY_KEY = 'Delay'; var DELAY_KEY = 'Delay';
var TIMING_KEY = 'TimingFunction'; var TIMING_KEY = 'TimingFunction';
var ANIMATION_ITERATION_COUNT_KEY = 'IterationCount'; var ANIMATION_ITERATION_COUNT_KEY = 'IterationCount';
var ANIMATION_PLAYSTATE_KEY = 'PlayState'; var ANIMATION_PLAYSTATE_KEY = 'PlayState';
var SAFE_FAST_FORWARD_DURATION_VALUE = 9999; var SAFE_FAST_FORWARD_DURATION_VALUE = 9999;
var ANIMATION_DELAY_PROP = ANIMATION_PROP + DELAY_KEY; var ANIMATION_DELAY_PROP = ANIMATION_PROP + DELAY_KEY;
var ANIMATION_DURATION_PROP = ANIMATION_PROP + DURATION_KEY; var ANIMATION_DURATION_PROP = ANIMATION_PROP + DURATION_KEY;
var TRANSITION_DELAY_PROP = TRANSITION_PROP + DELAY_KEY; var TRANSITION_DELAY_PROP = TRANSITION_PROP + DELAY_KEY;
var TRANSITION_DURATION_PROP = TRANSITION_PROP + DURATION_KEY; var TRANSITION_DURATION_PROP = TRANSITION_PROP + DURATION_KEY;
var isPromiseLike = function(p) { var ngMinErr = angular.$$minErr('ng');
return p && p.then ? true : false; function assertArg(arg, name, reason) {
};
var ngMinErr = angular.$$minErr('ng');
function assertArg(arg, name, reason) {
if (!arg) { if (!arg) {
throw ngMinErr('areq', "Argument '{0}' is {1}", (name || '?'), (reason || "required")); throw ngMinErr('areq', 'Argument \'{0}\' is {1}', (name || '?'), (reason || 'required'));
} }
return arg; return arg;
} }
function mergeClasses(a,b) { function mergeClasses(a,b) {
if (!a && !b) return ''; if (!a && !b) return '';
if (!a) return b; if (!a) return b;
if (!b) return a; if (!b) return a;
if (isArray(a)) a = a.join(' '); if (isArray(a)) a = a.join(' ');
if (isArray(b)) b = b.join(' '); if (isArray(b)) b = b.join(' ');
return a + ' ' + b; return a + ' ' + b;
} }
function packageStyles(options) { function packageStyles(options) {
var styles = {}; var styles = {};
if (options && (options.to || options.from)) { if (options && (options.to || options.from)) {
styles.to = options.to; styles.to = options.to;
styles.from = options.from; styles.from = options.from;
} }
return styles; return styles;
} }
function pendClasses(classes, fix, isPrefix) { function pendClasses(classes, fix, isPrefix) {
var className = ''; var className = '';
classes = isArray(classes) classes = isArray(classes)
? classes ? classes
...@@ -119,21 +101,20 @@ function pendClasses(classes, fix, isPrefix) { ...@@ -119,21 +101,20 @@ function pendClasses(classes, fix, isPrefix) {
} }
}); });
return className; return className;
} }
function removeFromArray(arr, val) { function removeFromArray(arr, val) {
var index = arr.indexOf(val); var index = arr.indexOf(val);
if (val >= 0) { if (val >= 0) {
arr.splice(index, 1); arr.splice(index, 1);
} }
} }
function stripCommentsFromElement(element) { function stripCommentsFromElement(element) {
if (element instanceof jqLite) { if (element instanceof jqLite) {
switch (element.length) { switch (element.length) {
case 0: case 0:
return element; return element;
break;
case 1: case 1:
// there is no point of stripping anything if the element // there is no point of stripping anything if the element
...@@ -146,38 +127,37 @@ function stripCommentsFromElement(element) { ...@@ -146,38 +127,37 @@ function stripCommentsFromElement(element) {
default: default:
return jqLite(extractElementNode(element)); return jqLite(extractElementNode(element));
break;
} }
} }
if (element.nodeType === ELEMENT_NODE) { if (element.nodeType === ELEMENT_NODE) {
return jqLite(element); return jqLite(element);
} }
} }
function extractElementNode(element) { function extractElementNode(element) {
if (!element[0]) return element; if (!element[0]) return element;
for (var i = 0; i < element.length; i++) { for (var i = 0; i < element.length; i++) {
var elm = element[i]; var elm = element[i];
if (elm.nodeType == ELEMENT_NODE) { if (elm.nodeType === ELEMENT_NODE) {
return elm; return elm;
} }
} }
} }
function $$addClass($$jqLite, element, className) { function $$addClass($$jqLite, element, className) {
forEach(element, function(elm) { forEach(element, function(elm) {
$$jqLite.addClass(elm, className); $$jqLite.addClass(elm, className);
}); });
} }
function $$removeClass($$jqLite, element, className) { function $$removeClass($$jqLite, element, className) {
forEach(element, function(elm) { forEach(element, function(elm) {
$$jqLite.removeClass(elm, className); $$jqLite.removeClass(elm, className);
}); });
} }
function applyAnimationClassesFactory($$jqLite) { function applyAnimationClassesFactory($$jqLite) {
return function(element, options) { return function(element, options) {
if (options.addClass) { if (options.addClass) {
$$addClass($$jqLite, element, options.addClass); $$addClass($$jqLite, element, options.addClass);
...@@ -187,10 +167,10 @@ function applyAnimationClassesFactory($$jqLite) { ...@@ -187,10 +167,10 @@ function applyAnimationClassesFactory($$jqLite) {
$$removeClass($$jqLite, element, options.removeClass); $$removeClass($$jqLite, element, options.removeClass);
options.removeClass = null; options.removeClass = null;
} }
};
} }
}
function prepareAnimationOptions(options) { function prepareAnimationOptions(options) {
options = options || {}; options = options || {};
if (!options.$$prepared) { if (!options.$$prepared) {
var domOperation = options.domOperation || noop; var domOperation = options.domOperation || noop;
...@@ -202,28 +182,28 @@ function prepareAnimationOptions(options) { ...@@ -202,28 +182,28 @@ function prepareAnimationOptions(options) {
options.$$prepared = true; options.$$prepared = true;
} }
return options; return options;
} }
function applyAnimationStyles(element, options) { function applyAnimationStyles(element, options) {
applyAnimationFromStyles(element, options); applyAnimationFromStyles(element, options);
applyAnimationToStyles(element, options); applyAnimationToStyles(element, options);
} }
function applyAnimationFromStyles(element, options) { function applyAnimationFromStyles(element, options) {
if (options.from) { if (options.from) {
element.css(options.from); element.css(options.from);
options.from = null; options.from = null;
} }
} }
function applyAnimationToStyles(element, options) { function applyAnimationToStyles(element, options) {
if (options.to) { if (options.to) {
element.css(options.to); element.css(options.to);
options.to = null; options.to = null;
} }
} }
function mergeAnimationDetails(element, oldAnimation, newAnimation) { function mergeAnimationDetails(element, oldAnimation, newAnimation) {
var target = oldAnimation.options || {}; var target = oldAnimation.options || {};
var newOptions = newAnimation.options || {}; var newOptions = newAnimation.options || {};
...@@ -262,9 +242,9 @@ function mergeAnimationDetails(element, oldAnimation, newAnimation) { ...@@ -262,9 +242,9 @@ function mergeAnimationDetails(element, oldAnimation, newAnimation) {
oldAnimation.removeClass = target.removeClass; oldAnimation.removeClass = target.removeClass;
return target; return target;
} }
function resolveElementClasses(existing, toAdd, toRemove) { function resolveElementClasses(existing, toAdd, toRemove) {
var ADD_CLASS = 1; var ADD_CLASS = 1;
var REMOVE_CLASS = -1; var REMOVE_CLASS = -1;
...@@ -290,10 +270,10 @@ function resolveElementClasses(existing, toAdd, toRemove) { ...@@ -290,10 +270,10 @@ function resolveElementClasses(existing, toAdd, toRemove) {
var prop, allow; var prop, allow;
if (val === ADD_CLASS) { if (val === ADD_CLASS) {
prop = 'addClass'; prop = 'addClass';
allow = !existing[klass]; allow = !existing[klass] || existing[klass + REMOVE_CLASS_SUFFIX];
} else if (val === REMOVE_CLASS) { } else if (val === REMOVE_CLASS) {
prop = 'removeClass'; prop = 'removeClass';
allow = existing[klass]; allow = existing[klass] || existing[klass + ADD_CLASS_SUFFIX];
} }
if (allow) { if (allow) {
if (classes[prop].length) { if (classes[prop].length) {
...@@ -320,13 +300,13 @@ function resolveElementClasses(existing, toAdd, toRemove) { ...@@ -320,13 +300,13 @@ function resolveElementClasses(existing, toAdd, toRemove) {
} }
return classes; return classes;
} }
function getDomNode(element) { function getDomNode(element) {
return (element instanceof angular.element) ? element[0] : element; return (element instanceof jqLite) ? element[0] : element;
} }
function applyGeneratedPreparationClasses(element, event, options) { function applyGeneratedPreparationClasses($$jqLite, element, event, options) {
var classes = ''; var classes = '';
if (event) { if (event) {
classes = pendClasses(event, EVENT_CLASS_PREFIX, true); classes = pendClasses(event, EVENT_CLASS_PREFIX, true);
...@@ -341,9 +321,9 @@ function applyGeneratedPreparationClasses(element, event, options) { ...@@ -341,9 +321,9 @@ function applyGeneratedPreparationClasses(element, event, options) {
options.preparationClasses = classes; options.preparationClasses = classes;
element.addClass(classes); element.addClass(classes);
} }
} }
function clearGeneratedClasses(element, options) { function clearGeneratedClasses(element, options) {
if (options.preparationClasses) { if (options.preparationClasses) {
element.removeClass(options.preparationClasses); element.removeClass(options.preparationClasses);
options.preparationClasses = null; options.preparationClasses = null;
...@@ -352,37 +332,39 @@ function clearGeneratedClasses(element, options) { ...@@ -352,37 +332,39 @@ function clearGeneratedClasses(element, options) {
element.removeClass(options.activeClasses); element.removeClass(options.activeClasses);
options.activeClasses = null; options.activeClasses = null;
} }
} }
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) { function blockKeyframeAnimations(node, applyBlock) {
var value = applyBlock ? 'paused' : ''; var value = applyBlock ? 'paused' : '';
var key = ANIMATION_PROP + ANIMATION_PLAYSTATE_KEY; var key = ANIMATION_PROP + ANIMATION_PLAYSTATE_KEY;
applyInlineStyle(node, [key, value]); applyInlineStyle(node, [key, value]);
return [key, value]; return [key, value];
} }
function applyInlineStyle(node, styleTuple) { function applyInlineStyle(node, styleTuple) {
var prop = styleTuple[0]; var prop = styleTuple[0];
var value = styleTuple[1]; var value = styleTuple[1];
node.style[prop] = value; node.style[prop] = value;
} }
function concatWithSpace(a,b) { function concatWithSpace(a,b) {
if (!a) return b; if (!a) return b;
if (!b) return a; if (!b) return a;
return a + ' ' + b; return a + ' ' + b;
} }
var helpers = {
blockTransitions: function(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];
}
};
var $$rAFSchedulerFactory = ['$$rAF', function($$rAF) { var $$rAFSchedulerFactory = ['$$rAF', function($$rAF) {
var queue, cancelFn; var queue, cancelFn;
function scheduler(tasks) { function scheduler(tasks) {
...@@ -429,9 +411,9 @@ var $$rAFSchedulerFactory = ['$$rAF', function($$rAF) { ...@@ -429,9 +411,9 @@ var $$rAFSchedulerFactory = ['$$rAF', function($$rAF) {
}); });
} }
} }
}]; }];
/** /**
* @ngdoc directive * @ngdoc directive
* @name ngAnimateChildren * @name ngAnimateChildren
* @restrict AE * @restrict AE
...@@ -443,7 +425,7 @@ var $$rAFSchedulerFactory = ['$$rAF', function($$rAF) { ...@@ -443,7 +425,7 @@ var $$rAFSchedulerFactory = ['$$rAF', function($$rAF) {
* of the children's parents are currently animating. By default, when an element has an active `enter`, `leave`, or `move` * of the children's parents are currently animating. By default, when an element has an active `enter`, `leave`, or `move`
* (structural) animation, child elements that also have an active structural animation are not animated. * (structural) animation, child elements that also have an active structural animation are not animated.
* *
* Note that even if `ngAnimteChildren` is set, no child animations will run when the parent element is removed from the DOM (`leave` animation). * Note that even if `ngAnimateChildren` is set, no child animations will run when the parent element is removed from the DOM (`leave` animation).
* *
* *
* @param {string} ngAnimateChildren If the value is empty, `true` or `on`, * @param {string} ngAnimateChildren If the value is empty, `true` or `on`,
...@@ -452,7 +434,7 @@ var $$rAFSchedulerFactory = ['$$rAF', function($$rAF) { ...@@ -452,7 +434,7 @@ var $$rAFSchedulerFactory = ['$$rAF', function($$rAF) {
* @example * @example
* <example module="ngAnimateChildren" name="ngAnimateChildren" deps="angular-animate.js" animations="true"> * <example module="ngAnimateChildren" name="ngAnimateChildren" deps="angular-animate.js" animations="true">
<file name="index.html"> <file name="index.html">
<div ng-controller="mainController as main"> <div ng-controller="MainController as main">
<label>Show container? <input type="checkbox" ng-model="main.enterElement" /></label> <label>Show container? <input type="checkbox" ng-model="main.enterElement" /></label>
<label>Animate children? <input type="checkbox" ng-model="main.animateChildren" /></label> <label>Animate children? <input type="checkbox" ng-model="main.animateChildren" /></label>
<hr> <hr>
...@@ -502,18 +484,18 @@ var $$rAFSchedulerFactory = ['$$rAF', function($$rAF) { ...@@ -502,18 +484,18 @@ var $$rAFSchedulerFactory = ['$$rAF', function($$rAF) {
</file> </file>
<file name="script.js"> <file name="script.js">
angular.module('ngAnimateChildren', ['ngAnimate']) angular.module('ngAnimateChildren', ['ngAnimate'])
.controller('mainController', function() { .controller('MainController', function MainController() {
this.animateChildren = false; this.animateChildren = false;
this.enterElement = false; this.enterElement = false;
}); });
</file> </file>
</example> </example>
*/ */
var $$AnimateChildrenDirective = ['$interpolate', function($interpolate) { var $$AnimateChildrenDirective = ['$interpolate', function($interpolate) {
return { return {
link: function(scope, element, attrs) { link: function(scope, element, attrs) {
var val = attrs.ngAnimateChildren; var val = attrs.ngAnimateChildren;
if (angular.isString(val) && val.length === 0) { //empty attribute if (isString(val) && val.length === 0) { //empty attribute
element.data(NG_ANIMATE_CHILDREN_DATA, true); element.data(NG_ANIMATE_CHILDREN_DATA, true);
} else { } else {
// Interpolate and set the value, so that it is available to // Interpolate and set the value, so that it is available to
...@@ -528,11 +510,13 @@ var $$AnimateChildrenDirective = ['$interpolate', function($interpolate) { ...@@ -528,11 +510,13 @@ var $$AnimateChildrenDirective = ['$interpolate', function($interpolate) {
} }
} }
}; };
}]; }];
var ANIMATE_TIMER_KEY = '$$animateCss'; /* exported $AnimateCssProvider */
/** var ANIMATE_TIMER_KEY = '$$animateCss';
/**
* @ngdoc service * @ngdoc service
* @name $animateCss * @name $animateCss
* @kind object * @kind object
...@@ -546,11 +530,11 @@ var ANIMATE_TIMER_KEY = '$$animateCss'; ...@@ -546,11 +530,11 @@ var ANIMATE_TIMER_KEY = '$$animateCss';
* Note that only browsers that support CSS transitions and/or keyframe animations are capable of * Note that only browsers that support CSS transitions and/or keyframe animations are capable of
* rendering animations triggered via `$animateCss` (bad news for IE9 and lower). * rendering animations triggered via `$animateCss` (bad news for IE9 and lower).
* *
* ## Usage * ## General Use
* Once again, `$animateCss` is designed to be used inside of a registered JavaScript animation that * Once again, `$animateCss` is designed to be used inside of a registered JavaScript animation that
* is powered by ngAnimate. It is possible to use `$animateCss` directly inside of a directive, however, * is powered by ngAnimate. It is possible to use `$animateCss` directly inside of a directive, however,
* any automatic control over cancelling animations and/or preventing animations from being run on * any automatic control over cancelling animations and/or preventing animations from being run on
* child elements will not be handled by Angular. For this to work as expected, please use `$animate` to * child elements will not be handled by AngularJS. For this to work as expected, please use `$animate` to
* trigger the animation and then setup a JavaScript animation that injects `$animateCss` to trigger * trigger the animation and then setup a JavaScript animation that injects `$animateCss` to trigger
* the CSS animation. * the CSS animation.
* *
...@@ -746,38 +730,37 @@ var ANIMATE_TIMER_KEY = '$$animateCss'; ...@@ -746,38 +730,37 @@ var ANIMATE_TIMER_KEY = '$$animateCss';
* * `start` - The method to start the animation. This will return a `Promise` when called. * * `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. * * `end` - This method will cancel the animation and remove all applied CSS classes and styles.
*/ */
var ONE_SECOND = 1000; var ONE_SECOND = 1000;
var BASE_TEN = 10;
var ELAPSED_TIME_MAX_DECIMAL_PLACES = 3; var ELAPSED_TIME_MAX_DECIMAL_PLACES = 3;
var CLOSING_TIME_BUFFER = 1.5; var CLOSING_TIME_BUFFER = 1.5;
var DETECT_CSS_PROPERTIES = { var DETECT_CSS_PROPERTIES = {
transitionDuration: TRANSITION_DURATION_PROP, transitionDuration: TRANSITION_DURATION_PROP,
transitionDelay: TRANSITION_DELAY_PROP, transitionDelay: TRANSITION_DELAY_PROP,
transitionProperty: TRANSITION_PROP + PROPERTY_KEY, transitionProperty: TRANSITION_PROP + PROPERTY_KEY,
animationDuration: ANIMATION_DURATION_PROP, animationDuration: ANIMATION_DURATION_PROP,
animationDelay: ANIMATION_DELAY_PROP, animationDelay: ANIMATION_DELAY_PROP,
animationIterationCount: ANIMATION_PROP + ANIMATION_ITERATION_COUNT_KEY animationIterationCount: ANIMATION_PROP + ANIMATION_ITERATION_COUNT_KEY
}; };
var DETECT_STAGGER_CSS_PROPERTIES = { var DETECT_STAGGER_CSS_PROPERTIES = {
transitionDuration: TRANSITION_DURATION_PROP, transitionDuration: TRANSITION_DURATION_PROP,
transitionDelay: TRANSITION_DELAY_PROP, transitionDelay: TRANSITION_DELAY_PROP,
animationDuration: ANIMATION_DURATION_PROP, animationDuration: ANIMATION_DURATION_PROP,
animationDelay: ANIMATION_DELAY_PROP animationDelay: ANIMATION_DELAY_PROP
}; };
function getCssKeyframeDurationStyle(duration) { function getCssKeyframeDurationStyle(duration) {
return [ANIMATION_DURATION_PROP, duration + 's']; return [ANIMATION_DURATION_PROP, duration + 's'];
} }
function getCssDelayStyle(delay, isKeyframeAnimation) { function getCssDelayStyle(delay, isKeyframeAnimation) {
var prop = isKeyframeAnimation ? ANIMATION_DELAY_PROP : TRANSITION_DELAY_PROP; var prop = isKeyframeAnimation ? ANIMATION_DELAY_PROP : TRANSITION_DELAY_PROP;
return [prop, delay + 's']; return [prop, delay + 's'];
} }
function computeCssStyles($window, element, properties) { function computeCssStyles($window, element, properties) {
var styles = Object.create(null); var styles = Object.create(null);
var detectedStyles = $window.getComputedStyle(element) || {}; var detectedStyles = $window.getComputedStyle(element) || {};
forEach(properties, function(formalStyleName, actualStyleName) { forEach(properties, function(formalStyleName, actualStyleName) {
...@@ -801,28 +784,28 @@ function computeCssStyles($window, element, properties) { ...@@ -801,28 +784,28 @@ function computeCssStyles($window, element, properties) {
}); });
return styles; return styles;
} }
function parseMaxTime(str) { function parseMaxTime(str) {
var maxValue = 0; var maxValue = 0;
var values = str.split(/\s*,\s*/); var values = str.split(/\s*,\s*/);
forEach(values, function(value) { forEach(values, function(value) {
// it's always safe to consider only second values and omit `ms` values since // it's always safe to consider only second values and omit `ms` values since
// getComputedStyle will always handle the conversion for us // getComputedStyle will always handle the conversion for us
if (value.charAt(value.length - 1) == 's') { if (value.charAt(value.length - 1) === 's') {
value = value.substring(0, value.length - 1); value = value.substring(0, value.length - 1);
} }
value = parseFloat(value) || 0; value = parseFloat(value) || 0;
maxValue = maxValue ? Math.max(value, maxValue) : value; maxValue = maxValue ? Math.max(value, maxValue) : value;
}); });
return maxValue; return maxValue;
} }
function truthyTimingValue(val) { function truthyTimingValue(val) {
return val === 0 || val != null; return val === 0 || val != null;
} }
function getCssTransitionDurationStyle(duration, applyOnlyDuration) { function getCssTransitionDurationStyle(duration, applyOnlyDuration) {
var style = TRANSITION_PROP; var style = TRANSITION_PROP;
var value = duration + 's'; var value = duration + 's';
if (applyOnlyDuration) { if (applyOnlyDuration) {
...@@ -831,34 +814,7 @@ function getCssTransitionDurationStyle(duration, applyOnlyDuration) { ...@@ -831,34 +814,7 @@ function getCssTransitionDurationStyle(duration, applyOnlyDuration) {
value += ' linear all'; value += ' linear all';
} }
return [style, value]; return [style, value];
}
function createLocalCacheLookup() {
var cache = Object.create(null);
return {
flush: function() {
cache = Object.create(null);
},
count: function(key) {
var entry = cache[key];
return entry ? entry.total : 0;
},
get: function(key) {
var entry = cache[key];
return entry && entry.value;
},
put: function(key, value) {
if (!cache[key]) {
cache[key] = { total: 1, value: value };
} else {
cache[key].total++;
} }
}
};
}
// we do not reassign an already present style value since // we do not reassign an already present style value since
// if we detect the style property value again we may be // if we detect the style property value again we may be
...@@ -869,35 +825,25 @@ function createLocalCacheLookup() { ...@@ -869,35 +825,25 @@ function createLocalCacheLookup() {
// value for the style (a falsy value implies that the style // value for the style (a falsy value implies that the style
// is to be removed at the end of the animation). If we had a simple // is to be removed at the end of the animation). If we had a simple
// "OR" statement then it would not be enough to catch that. // "OR" statement then it would not be enough to catch that.
function registerRestorableStyles(backup, node, properties) { function registerRestorableStyles(backup, node, properties) {
forEach(properties, function(prop) { forEach(properties, function(prop) {
backup[prop] = isDefined(backup[prop]) backup[prop] = isDefined(backup[prop])
? backup[prop] ? backup[prop]
: node.style.getPropertyValue(prop); : node.style.getPropertyValue(prop);
}); });
} }
var $AnimateCssProvider = ['$animateProvider', function($animateProvider) { var $AnimateCssProvider = ['$animateProvider', /** @this */ function($animateProvider) {
var gcsLookup = createLocalCacheLookup();
var gcsStaggerLookup = createLocalCacheLookup();
this.$get = ['$window', '$$jqLite', '$$AnimateRunner', '$timeout', this.$get = ['$window', '$$jqLite', '$$AnimateRunner', '$timeout', '$$animateCache',
'$$forceReflow', '$sniffer', '$$rAFScheduler', '$$animateQueue', '$$forceReflow', '$sniffer', '$$rAFScheduler', '$$animateQueue',
function($window, $$jqLite, $$AnimateRunner, $timeout, function($window, $$jqLite, $$AnimateRunner, $timeout, $$animateCache,
$$forceReflow, $sniffer, $$rAFScheduler, $$animateQueue) { $$forceReflow, $sniffer, $$rAFScheduler, $$animateQueue) {
var applyAnimationClasses = applyAnimationClassesFactory($$jqLite); var applyAnimationClasses = applyAnimationClassesFactory($$jqLite);
var parentCounter = 0; function computeCachedCssStyles(node, className, cacheKey, allowNoDuration, properties) {
function gcsHashFn(node, extraClasses) { var timings = $$animateCache.get(cacheKey);
var KEY = "$$ngAnimateParentKey";
var parentNode = node.parentNode;
var parentID = parentNode[KEY] || (parentNode[KEY] = ++parentCounter);
return parentID + '-' + node.getAttribute('class') + '-' + extraClasses;
}
function computeCachedCssStyles(node, className, cacheKey, properties) {
var timings = gcsLookup.get(cacheKey);
if (!timings) { if (!timings) {
timings = computeCssStyles($window, node, properties); timings = computeCssStyles($window, node, properties);
...@@ -906,20 +852,26 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) { ...@@ -906,20 +852,26 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
} }
} }
// if a css animation has no duration we
// should mark that so that repeated addClass/removeClass calls are skipped
var hasDuration = allowNoDuration || (timings.transitionDuration > 0 || timings.animationDuration > 0);
// we keep putting this in multiple times even though the value and the cacheKey are the same // we keep putting this in multiple times even though the value and the cacheKey are the same
// because we're keeping an internal tally of how many duplicate animations are detected. // because we're keeping an internal tally of how many duplicate animations are detected.
gcsLookup.put(cacheKey, timings); $$animateCache.put(cacheKey, timings, hasDuration);
return timings; return timings;
} }
function computeCachedCssStaggerStyles(node, className, cacheKey, properties) { function computeCachedCssStaggerStyles(node, className, cacheKey, properties) {
var stagger; var stagger;
var staggerCacheKey = 'stagger-' + cacheKey;
// if we have one or more existing matches of matching elements // if we have one or more existing matches of matching elements
// containing the same parent + CSS styles (which is how cacheKey works) // containing the same parent + CSS styles (which is how cacheKey works)
// then staggering is possible // then staggering is possible
if (gcsLookup.count(cacheKey) > 0) { if ($$animateCache.count(cacheKey) > 0) {
stagger = gcsStaggerLookup.get(cacheKey); stagger = $$animateCache.get(staggerCacheKey);
if (!stagger) { if (!stagger) {
var staggerClassName = pendClasses(className, '-stagger'); var staggerClassName = pendClasses(className, '-stagger');
...@@ -934,20 +886,18 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) { ...@@ -934,20 +886,18 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
$$jqLite.removeClass(node, staggerClassName); $$jqLite.removeClass(node, staggerClassName);
gcsStaggerLookup.put(cacheKey, stagger); $$animateCache.put(staggerCacheKey, stagger, true);
} }
} }
return stagger || {}; return stagger || {};
} }
var cancelLastRAFRequest;
var rafWaitQueue = []; var rafWaitQueue = [];
function waitUntilQuiet(callback) { function waitUntilQuiet(callback) {
rafWaitQueue.push(callback); rafWaitQueue.push(callback);
$$rAFScheduler.waitUntilQuiet(function() { $$rAFScheduler.waitUntilQuiet(function() {
gcsLookup.flush(); $$animateCache.flush();
gcsStaggerLookup.flush();
// DO NOT REMOVE THIS LINE OR REFACTOR OUT THE `pageWidth` variable. // DO NOT REMOVE THIS LINE OR REFACTOR OUT THE `pageWidth` variable.
// PLEASE EXAMINE THE `$$forceReflow` service to understand why. // PLEASE EXAMINE THE `$$forceReflow` service to understand why.
...@@ -962,8 +912,8 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) { ...@@ -962,8 +912,8 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
}); });
} }
function computeTimings(node, className, cacheKey) { function computeTimings(node, className, cacheKey, allowNoDuration) {
var timings = computeCachedCssStyles(node, className, cacheKey, DETECT_CSS_PROPERTIES); var timings = computeCachedCssStyles(node, className, cacheKey, allowNoDuration, DETECT_CSS_PROPERTIES);
var aD = timings.animationDelay; var aD = timings.animationDelay;
var tD = timings.transitionDelay; var tD = timings.transitionDelay;
timings.maxDelay = aD && tD timings.maxDelay = aD && tD
...@@ -1050,7 +1000,6 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) { ...@@ -1050,7 +1000,6 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
var preparationClasses = [structuralClassName, addRemoveClassName].join(' ').trim(); var preparationClasses = [structuralClassName, addRemoveClassName].join(' ').trim();
var fullClassName = classes + ' ' + preparationClasses; var fullClassName = classes + ' ' + preparationClasses;
var activeClasses = pendClasses(preparationClasses, ACTIVE_CLASS_SUFFIX);
var hasToStyles = styles.to && Object.keys(styles.to).length > 0; var hasToStyles = styles.to && Object.keys(styles.to).length > 0;
var containsKeyframeAnimation = (options.keyframeStyle || '').length > 0; var containsKeyframeAnimation = (options.keyframeStyle || '').length > 0;
...@@ -1063,7 +1012,12 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) { ...@@ -1063,7 +1012,12 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
return closeAndReturnNoopAnimator(); return closeAndReturnNoopAnimator();
} }
var cacheKey, stagger; var stagger, cacheKey = $$animateCache.cacheKey(node, method, options.addClass, options.removeClass);
if ($$animateCache.containsCachedAnimationWithoutDuration(cacheKey)) {
preparationClasses = null;
return closeAndReturnNoopAnimator();
}
if (options.stagger > 0) { if (options.stagger > 0) {
var staggerVal = parseFloat(options.stagger); var staggerVal = parseFloat(options.stagger);
stagger = { stagger = {
...@@ -1073,7 +1027,6 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) { ...@@ -1073,7 +1027,6 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
animationDuration: 0 animationDuration: 0
}; };
} else { } else {
cacheKey = gcsHashFn(node, fullClassName);
stagger = computeCachedCssStaggerStyles(node, preparationClasses, cacheKey, DETECT_STAGGER_CSS_PROPERTIES); stagger = computeCachedCssStaggerStyles(node, preparationClasses, cacheKey, DETECT_STAGGER_CSS_PROPERTIES);
} }
...@@ -1107,7 +1060,7 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) { ...@@ -1107,7 +1060,7 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
var itemIndex = stagger var itemIndex = stagger
? options.staggerIndex >= 0 ? options.staggerIndex >= 0
? options.staggerIndex ? options.staggerIndex
: gcsLookup.count(cacheKey) : $$animateCache.count(cacheKey)
: 0; : 0;
var isFirst = itemIndex === 0; var isFirst = itemIndex === 0;
...@@ -1119,10 +1072,10 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) { ...@@ -1119,10 +1072,10 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
// that if there is no transition defined then nothing will happen and this will also allow // 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. // other transitions to be stacked on top of each other without any chopping them out.
if (isFirst && !options.skipBlocking) { if (isFirst && !options.skipBlocking) {
blockTransitions(node, SAFE_FAST_FORWARD_DURATION_VALUE); helpers.blockTransitions(node, SAFE_FAST_FORWARD_DURATION_VALUE);
} }
var timings = computeTimings(node, fullClassName, cacheKey); var timings = computeTimings(node, fullClassName, cacheKey, !isStructural);
var relativeDelay = timings.maxDelay; var relativeDelay = timings.maxDelay;
maxDelay = Math.max(relativeDelay, 0); maxDelay = Math.max(relativeDelay, 0);
maxDuration = timings.maxDuration; maxDuration = timings.maxDuration;
...@@ -1130,7 +1083,7 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) { ...@@ -1130,7 +1083,7 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
var flags = {}; var flags = {};
flags.hasTransitions = timings.transitionDuration > 0; flags.hasTransitions = timings.transitionDuration > 0;
flags.hasAnimations = timings.animationDuration > 0; flags.hasAnimations = timings.animationDuration > 0;
flags.hasTransitionAll = flags.hasTransitions && timings.transitionProperty == 'all'; flags.hasTransitionAll = flags.hasTransitions && timings.transitionProperty === 'all';
flags.applyTransitionDuration = hasToStyles && ( flags.applyTransitionDuration = hasToStyles && (
(flags.hasTransitions && !flags.hasTransitionAll) (flags.hasTransitions && !flags.hasTransitionAll)
|| (flags.hasAnimations && !flags.hasTransitions)); || (flags.hasAnimations && !flags.hasTransitions));
...@@ -1160,9 +1113,11 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) { ...@@ -1160,9 +1113,11 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
return closeAndReturnNoopAnimator(); return closeAndReturnNoopAnimator();
} }
var activeClasses = pendClasses(preparationClasses, ACTIVE_CLASS_SUFFIX);
if (options.delay != null) { if (options.delay != null) {
var delayStyle; var delayStyle;
if (typeof options.delay !== "boolean") { if (typeof options.delay !== 'boolean') {
delayStyle = parseFloat(options.delay); delayStyle = parseFloat(options.delay);
// number in options.delay means we have to recalculate the delay for the closing timeout // number in options.delay means we have to recalculate the delay for the closing timeout
maxDelay = Math.max(delayStyle, 0); maxDelay = Math.max(delayStyle, 0);
...@@ -1203,7 +1158,7 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) { ...@@ -1203,7 +1158,7 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
if (flags.blockTransition || flags.blockKeyframeAnimation) { if (flags.blockTransition || flags.blockKeyframeAnimation) {
applyBlocking(maxDuration); applyBlocking(maxDuration);
} else if (!options.skipBlocking) { } else if (!options.skipBlocking) {
blockTransitions(node, false); helpers.blockTransitions(node, false);
} }
// TODO(matsko): for 1.5 change this code to have an animator object for better debugging // TODO(matsko): for 1.5 change this code to have an animator object for better debugging
...@@ -1240,20 +1195,23 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) { ...@@ -1240,20 +1195,23 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
close(true); close(true);
} }
function close(rejected) { // jshint ignore:line function close(rejected) {
// if the promise has been called already then we shouldn't close // if the promise has been called already then we shouldn't close
// the animation again // the animation again
if (animationClosed || (animationCompleted && animationPaused)) return; if (animationClosed || (animationCompleted && animationPaused)) return;
animationClosed = true; animationClosed = true;
animationPaused = false; animationPaused = false;
if (!options.$$skipPreparationClasses) { if (preparationClasses && !options.$$skipPreparationClasses) {
$$jqLite.removeClass(element, preparationClasses); $$jqLite.removeClass(element, preparationClasses);
} }
if (activeClasses) {
$$jqLite.removeClass(element, activeClasses); $$jqLite.removeClass(element, activeClasses);
}
blockKeyframeAnimations(node, false); blockKeyframeAnimations(node, false);
blockTransitions(node, false); helpers.blockTransitions(node, false);
forEach(temporaryStyles, function(entry) { forEach(temporaryStyles, function(entry) {
// There is only one way to remove inline style properties entirely from elements. // There is only one way to remove inline style properties entirely from elements.
...@@ -1267,8 +1225,11 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) { ...@@ -1267,8 +1225,11 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
if (Object.keys(restoreStyles).length) { if (Object.keys(restoreStyles).length) {
forEach(restoreStyles, function(value, prop) { forEach(restoreStyles, function(value, prop) {
value ? node.style.setProperty(prop, value) if (value) {
: node.style.removeProperty(prop); node.style.setProperty(prop, value);
} else {
node.style.removeProperty(prop);
}
}); });
} }
...@@ -1301,7 +1262,7 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) { ...@@ -1301,7 +1262,7 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
function applyBlocking(duration) { function applyBlocking(duration) {
if (flags.blockTransition) { if (flags.blockTransition) {
blockTransitions(node, duration); helpers.blockTransitions(node, duration);
} }
if (flags.blockKeyframeAnimation) { if (flags.blockKeyframeAnimation) {
...@@ -1332,6 +1293,12 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) { ...@@ -1332,6 +1293,12 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
event.stopPropagation(); event.stopPropagation();
var ev = event.originalEvent || event; var ev = event.originalEvent || event;
if (ev.target !== node) {
// Since TransitionEvent / AnimationEvent bubble up,
// we have to ignore events by finished child animations
return;
}
// we now always use `Date.now()` due to the recent changes with // we now always use `Date.now()` due to the recent changes with
// event.timeStamp in Firefox, Webkit and Chrome (see #13494 for more info) // event.timeStamp in Firefox, Webkit and Chrome (see #13494 for more info)
var timeStamp = ev.$manualTimeStamp || Date.now(); var timeStamp = ev.$manualTimeStamp || Date.now();
...@@ -1371,9 +1338,11 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) { ...@@ -1371,9 +1338,11 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
animationPaused = !playAnimation; animationPaused = !playAnimation;
if (timings.animationDuration) { if (timings.animationDuration) {
var value = blockKeyframeAnimations(node, animationPaused); var value = blockKeyframeAnimations(node, animationPaused);
animationPaused if (animationPaused) {
? temporaryStyles.push(value) temporaryStyles.push(value);
: removeFromArray(temporaryStyles, value); } else {
removeFromArray(temporaryStyles, value);
}
} }
} else if (animationPaused && playAnimation) { } else if (animationPaused && playAnimation) {
animationPaused = false; animationPaused = false;
...@@ -1422,10 +1391,10 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) { ...@@ -1422,10 +1391,10 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
$$jqLite.addClass(element, activeClasses); $$jqLite.addClass(element, activeClasses);
if (flags.recalculateTimingStyles) { if (flags.recalculateTimingStyles) {
fullClassName = node.className + ' ' + preparationClasses; fullClassName = node.getAttribute('class') + ' ' + preparationClasses;
cacheKey = gcsHashFn(node, fullClassName); cacheKey = $$animateCache.cacheKey(node, method, options.addClass, options.removeClass);
timings = computeTimings(node, fullClassName, cacheKey); timings = computeTimings(node, fullClassName, cacheKey, false);
relativeDelay = timings.maxDelay; relativeDelay = timings.maxDelay;
maxDelay = Math.max(relativeDelay, 0); maxDelay = Math.max(relativeDelay, 0);
maxDuration = timings.maxDuration; maxDuration = timings.maxDuration;
...@@ -1440,7 +1409,7 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) { ...@@ -1440,7 +1409,7 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
} }
if (flags.applyAnimationDelay) { if (flags.applyAnimationDelay) {
relativeDelay = typeof options.delay !== "boolean" && truthyTimingValue(options.delay) relativeDelay = typeof options.delay !== 'boolean' && truthyTimingValue(options.delay)
? parseFloat(options.delay) ? parseFloat(options.delay)
: relativeDelay; : relativeDelay;
...@@ -1530,9 +1499,9 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) { ...@@ -1530,9 +1499,9 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
} }
}; };
}]; }];
}]; }];
var $$AnimateCssDriverProvider = ['$$animationProvider', function($$animationProvider) { var $$AnimateCssDriverProvider = ['$$animationProvider', /** @this */ function($$animationProvider) {
$$animationProvider.drivers.push('$$animateCssDriver'); $$animationProvider.drivers.push('$$animateCssDriver');
var NG_ANIMATE_SHIM_CLASS_NAME = 'ng-animate-shim'; var NG_ANIMATE_SHIM_CLASS_NAME = 'ng-animate-shim';
...@@ -1561,8 +1530,6 @@ var $$AnimateCssDriverProvider = ['$$animationProvider', function($$animationPro ...@@ -1561,8 +1530,6 @@ var $$AnimateCssDriverProvider = ['$$animationProvider', function($$animationPro
isDocumentFragment(rootNode) || bodyNode.contains(rootNode) ? rootNode : bodyNode isDocumentFragment(rootNode) || bodyNode.contains(rootNode) ? rootNode : bodyNode
); );
var applyAnimationClasses = applyAnimationClassesFactory($$jqLite);
return function initDriverFn(animationDetails) { return function initDriverFn(animationDetails) {
return animationDetails.from && animationDetails.to return animationDetails.from && animationDetails.to
? prepareFromToAnchorAnimation(animationDetails.from, ? prepareFromToAnchorAnimation(animationDetails.from,
...@@ -1798,13 +1765,13 @@ var $$AnimateCssDriverProvider = ['$$animationProvider', function($$animationPro ...@@ -1798,13 +1765,13 @@ var $$AnimateCssDriverProvider = ['$$animationProvider', function($$animationPro
return animator.$$willAnimate ? animator : null; return animator.$$willAnimate ? animator : null;
} }
}]; }];
}]; }];
// TODO(matsko): use caching here to speed things up for detection // TODO(matsko): use caching here to speed things up for detection
// TODO(matsko): add documentation // TODO(matsko): add documentation
// by the time... // by the time...
var $$AnimateJsProvider = ['$animateProvider', function($animateProvider) { var $$AnimateJsProvider = ['$animateProvider', /** @this */ function($animateProvider) {
this.$get = ['$injector', '$$AnimateRunner', '$$jqLite', this.$get = ['$injector', '$$AnimateRunner', '$$jqLite',
function($injector, $$AnimateRunner, $$jqLite) { function($injector, $$AnimateRunner, $$jqLite) {
...@@ -1843,7 +1810,7 @@ var $$AnimateJsProvider = ['$animateProvider', function($animateProvider) { ...@@ -1843,7 +1810,7 @@ var $$AnimateJsProvider = ['$animateProvider', function($animateProvider) {
var before, after; var before, after;
if (animations.length) { if (animations.length) {
var afterFn, beforeFn; var afterFn, beforeFn;
if (event == 'leave') { if (event === 'leave') {
beforeFn = 'leave'; beforeFn = 'leave';
afterFn = 'afterLeave'; // TODO(matsko): get rid of this afterFn = 'afterLeave'; // TODO(matsko): get rid of this
} else { } else {
...@@ -2028,7 +1995,7 @@ var $$AnimateJsProvider = ['$animateProvider', function($animateProvider) { ...@@ -2028,7 +1995,7 @@ var $$AnimateJsProvider = ['$animateProvider', function($animateProvider) {
function packageAnimations(element, event, options, animations, fnName) { function packageAnimations(element, event, options, animations, fnName) {
var operations = groupEventedAnimations(element, event, options, animations, fnName); var operations = groupEventedAnimations(element, event, options, animations, fnName);
if (operations.length === 0) { if (operations.length === 0) {
var a,b; var a, b;
if (fnName === 'beforeSetClass') { if (fnName === 'beforeSetClass') {
a = groupEventedAnimations(element, 'removeClass', options, animations, 'beforeRemoveClass'); a = groupEventedAnimations(element, 'removeClass', options, animations, 'beforeRemoveClass');
b = groupEventedAnimations(element, 'addClass', options, animations, 'beforeAddClass'); b = groupEventedAnimations(element, 'addClass', options, animations, 'beforeAddClass');
...@@ -2056,11 +2023,19 @@ var $$AnimateJsProvider = ['$animateProvider', function($animateProvider) { ...@@ -2056,11 +2023,19 @@ var $$AnimateJsProvider = ['$animateProvider', function($animateProvider) {
}); });
} }
runners.length ? $$AnimateRunner.all(runners, callback) : callback(); if (runners.length) {
$$AnimateRunner.all(runners, callback);
} else {
callback();
}
return function endFn(reject) { return function endFn(reject) {
forEach(runners, function(runner) { forEach(runners, function(runner) {
reject ? runner.cancel() : runner.end(); if (reject) {
runner.cancel();
} else {
runner.end();
}
}); });
}; };
}; };
...@@ -2070,7 +2045,7 @@ var $$AnimateJsProvider = ['$animateProvider', function($animateProvider) { ...@@ -2070,7 +2045,7 @@ var $$AnimateJsProvider = ['$animateProvider', function($animateProvider) {
function lookupAnimations(classes) { function lookupAnimations(classes) {
classes = isArray(classes) ? classes : classes.split(' '); classes = isArray(classes) ? classes : classes.split(' ');
var matches = [], flagMap = {}; var matches = [], flagMap = {};
for (var i=0; i < classes.length; i++) { for (var i = 0; i < classes.length; i++) {
var klass = classes[i], var klass = classes[i],
animationFactory = $animateProvider.$$registeredAnimations[klass]; animationFactory = $animateProvider.$$registeredAnimations[klass];
if (animationFactory && !flagMap[klass]) { if (animationFactory && !flagMap[klass]) {
...@@ -2081,9 +2056,9 @@ var $$AnimateJsProvider = ['$animateProvider', function($animateProvider) { ...@@ -2081,9 +2056,9 @@ var $$AnimateJsProvider = ['$animateProvider', function($animateProvider) {
return matches; return matches;
} }
}]; }];
}]; }];
var $$AnimateJsDriverProvider = ['$$animationProvider', function($$animationProvider) { var $$AnimateJsDriverProvider = ['$$animationProvider', /** @this */ function($$animationProvider) {
$$animationProvider.drivers.push('$$animateJsDriver'); $$animationProvider.drivers.push('$$animateJsDriver');
this.$get = ['$$animateJs', '$$AnimateRunner', function($$animateJs, $$AnimateRunner) { this.$get = ['$$animateJs', '$$AnimateRunner', function($$animateJs, $$AnimateRunner) {
return function initDriverFn(animationDetails) { return function initDriverFn(animationDetails) {
...@@ -2141,11 +2116,11 @@ var $$AnimateJsDriverProvider = ['$$animationProvider', function($$animationProv ...@@ -2141,11 +2116,11 @@ var $$AnimateJsDriverProvider = ['$$animationProvider', function($$animationProv
return $$animateJs(element, event, classes, options); return $$animateJs(element, event, classes, options);
} }
}]; }];
}]; }];
var NG_ANIMATE_ATTR_NAME = 'data-ng-animate'; var NG_ANIMATE_ATTR_NAME = 'data-ng-animate';
var NG_ANIMATE_PIN_DATA = '$ngAnimatePin'; var NG_ANIMATE_PIN_DATA = '$ngAnimatePin';
var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) { var $$AnimateQueueProvider = ['$animateProvider', /** @this */ function($animateProvider) {
var PRE_DIGEST_STATE = 1; var PRE_DIGEST_STATE = 1;
var RUNNING_STATE = 2; var RUNNING_STATE = 2;
var ONE_SPACE = ' '; var ONE_SPACE = ' ';
...@@ -2156,6 +2131,15 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) { ...@@ -2156,6 +2131,15 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
join: [] join: []
}; };
function getEventData(options) {
return {
addClass: options.addClass,
removeClass: options.removeClass,
from: options.from,
to: options.to
};
}
function makeTruthyCssClassMap(classString) { function makeTruthyCssClassMap(classString) {
if (!classString) { if (!classString) {
return null; return null;
...@@ -2179,9 +2163,9 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) { ...@@ -2179,9 +2163,9 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
} }
} }
function isAllowed(ruleType, element, currentAnimation, previousAnimation) { function isAllowed(ruleType, currentAnimation, previousAnimation) {
return rules[ruleType].some(function(fn) { return rules[ruleType].some(function(fn) {
return fn(element, currentAnimation, previousAnimation); return fn(currentAnimation, previousAnimation);
}); });
} }
...@@ -2191,40 +2175,40 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) { ...@@ -2191,40 +2175,40 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
return and ? a && b : a || b; return and ? a && b : a || b;
} }
rules.join.push(function(element, newAnimation, currentAnimation) { rules.join.push(function(newAnimation, currentAnimation) {
// if the new animation is class-based then we can just tack that on // if the new animation is class-based then we can just tack that on
return !newAnimation.structural && hasAnimationClasses(newAnimation); return !newAnimation.structural && hasAnimationClasses(newAnimation);
}); });
rules.skip.push(function(element, newAnimation, currentAnimation) { rules.skip.push(function(newAnimation, currentAnimation) {
// there is no need to animate anything if no classes are being added and // there is no need to animate anything if no classes are being added and
// there is no structural animation that will be triggered // there is no structural animation that will be triggered
return !newAnimation.structural && !hasAnimationClasses(newAnimation); return !newAnimation.structural && !hasAnimationClasses(newAnimation);
}); });
rules.skip.push(function(element, newAnimation, currentAnimation) { rules.skip.push(function(newAnimation, currentAnimation) {
// why should we trigger a new structural animation if the element will // why should we trigger a new structural animation if the element will
// be removed from the DOM anyway? // be removed from the DOM anyway?
return currentAnimation.event == 'leave' && newAnimation.structural; return currentAnimation.event === 'leave' && newAnimation.structural;
}); });
rules.skip.push(function(element, newAnimation, currentAnimation) { rules.skip.push(function(newAnimation, currentAnimation) {
// if there is an ongoing current animation then don't even bother running the class-based animation // 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; return currentAnimation.structural && currentAnimation.state === RUNNING_STATE && !newAnimation.structural;
}); });
rules.cancel.push(function(element, newAnimation, currentAnimation) { rules.cancel.push(function(newAnimation, currentAnimation) {
// there can never be two structural animations running at the same time // there can never be two structural animations running at the same time
return currentAnimation.structural && newAnimation.structural; return currentAnimation.structural && newAnimation.structural;
}); });
rules.cancel.push(function(element, newAnimation, currentAnimation) { rules.cancel.push(function(newAnimation, currentAnimation) {
// if the previous animation is already running, but the new animation will // if the previous animation is already running, but the new animation will
// be triggered, but the new animation is structural // be triggered, but the new animation is structural
return currentAnimation.state === RUNNING_STATE && newAnimation.structural; return currentAnimation.state === RUNNING_STATE && newAnimation.structural;
}); });
rules.cancel.push(function(element, newAnimation, currentAnimation) { rules.cancel.push(function(newAnimation, currentAnimation) {
// cancel the animation if classes added / removed in both animation cancel each other out, // cancel the animation if classes added / removed in both animation cancel each other out,
// but only if the current animation isn't structural // but only if the current animation isn't structural
...@@ -2243,15 +2227,21 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) { ...@@ -2243,15 +2227,21 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
return hasMatchingClasses(nA, cR) || hasMatchingClasses(nR, cA); return hasMatchingClasses(nA, cR) || hasMatchingClasses(nR, cA);
}); });
this.$get = ['$$rAF', '$rootScope', '$rootElement', '$document', '$$HashMap', this.$get = ['$$rAF', '$rootScope', '$rootElement', '$document', '$$Map',
'$$animation', '$$AnimateRunner', '$templateRequest', '$$jqLite', '$$forceReflow', '$$animation', '$$AnimateRunner', '$templateRequest', '$$jqLite', '$$forceReflow',
function($$rAF, $rootScope, $rootElement, $document, $$HashMap, '$$isDocumentHidden',
$$animation, $$AnimateRunner, $templateRequest, $$jqLite, $$forceReflow) { function($$rAF, $rootScope, $rootElement, $document, $$Map,
$$animation, $$AnimateRunner, $templateRequest, $$jqLite, $$forceReflow,
$$isDocumentHidden) {
var activeAnimationsLookup = new $$HashMap(); var activeAnimationsLookup = new $$Map();
var disabledElementsLookup = new $$HashMap(); var disabledElementsLookup = new $$Map();
var animationsEnabled = null; var animationsEnabled = null;
function removeFromDisabledElementsLookup(evt) {
disabledElementsLookup.delete(evt.target);
}
function postDigestTaskFactory() { function postDigestTaskFactory() {
var postDigestCalled = false; var postDigestCalled = false;
return function(fn) { return function(fn) {
...@@ -2299,14 +2289,17 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) { ...@@ -2299,14 +2289,17 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
} }
); );
var callbackRegistry = {}; var callbackRegistry = Object.create(null);
// remember that the classNameFilter is set during the provider/config // remember that the `customFilter`/`classNameFilter` are set during the
// stage therefore we can optimize here and setup a helper function // provider/config stage therefore we can optimize here and setup helper functions
var customFilter = $animateProvider.customFilter();
var classNameFilter = $animateProvider.classNameFilter(); var classNameFilter = $animateProvider.classNameFilter();
var isAnimatableClassName = !classNameFilter var returnTrue = function() { return true; };
? function() { return true; }
: function(className) { var isAnimatableByFilter = customFilter || returnTrue;
var isAnimatableClassName = !classNameFilter ? returnTrue : function(node, options) {
var className = [node.getAttribute('class'), options.addClass, options.removeClass].join(' ');
return classNameFilter.test(className); return classNameFilter.test(className);
}; };
...@@ -2317,16 +2310,12 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) { ...@@ -2317,16 +2310,12 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
} }
// IE9-11 has no method "contains" in SVG element and in Node.prototype. Bug #10259. // IE9-11 has no method "contains" in SVG element and in Node.prototype. Bug #10259.
var contains = window.Node.prototype.contains || function(arg) { var contains = window.Node.prototype.contains || /** @this */ function(arg) {
// jshint bitwise: false // eslint-disable-next-line no-bitwise
return this === arg || !!(this.compareDocumentPosition(arg) & 16); return this === arg || !!(this.compareDocumentPosition(arg) & 16);
// jshint bitwise: true
}; };
function findCallbacks(parent, element, event) { function findCallbacks(targetParentNode, targetNode, event) {
var targetNode = getDomNode(element);
var targetParentNode = getDomNode(parent);
var matches = []; var matches = [];
var entries = callbackRegistry[event]; var entries = callbackRegistry[event];
if (entries) { if (entries) {
...@@ -2351,11 +2340,11 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) { ...@@ -2351,11 +2340,11 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
}); });
} }
function cleanupEventListeners(phase, element) { function cleanupEventListeners(phase, node) {
if (phase === 'close' && !element[0].parentNode) { if (phase === 'close' && !node.parentNode) {
// If the element is not attached to a parentNode, it has been removed by // If the element is not attached to a parentNode, it has been removed by
// the domOperation, and we can safely remove the event callbacks // the domOperation, and we can safely remove the event callbacks
$animate.off(element); $animate.off(node);
} }
} }
...@@ -2382,7 +2371,7 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) { ...@@ -2382,7 +2371,7 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
}, },
off: function(event, container, callback) { off: function(event, container, callback) {
if (arguments.length === 1 && !angular.isString(arguments[0])) { if (arguments.length === 1 && !isString(arguments[0])) {
container = arguments[0]; container = arguments[0];
for (var eventType in callbackRegistry) { for (var eventType in callbackRegistry) {
callbackRegistry[eventType] = filterFromRegistry(callbackRegistry[eventType], container); callbackRegistry[eventType] = filterFromRegistry(callbackRegistry[eventType], container);
...@@ -2430,14 +2419,18 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) { ...@@ -2430,14 +2419,18 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
bool = animationsEnabled = !!element; bool = animationsEnabled = !!element;
} else { } else {
var node = getDomNode(element); var node = getDomNode(element);
var recordExists = disabledElementsLookup.get(node);
if (argCount === 1) { if (argCount === 1) {
// (element) - Element getter // (element) - Element getter
bool = !recordExists; bool = !disabledElementsLookup.get(node);
} else { } else {
// (element, bool) - Element setter // (element, bool) - Element setter
disabledElementsLookup.put(node, !bool); if (!disabledElementsLookup.has(node)) {
// The element is added to the map for the first time.
// Create a listener to remove it on `$destroy` (to avoid memory leak).
jqLite(element).on('$destroy', removeFromDisabledElementsLookup);
}
disabledElementsLookup.set(node, !bool);
} }
} }
} }
...@@ -2448,18 +2441,15 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) { ...@@ -2448,18 +2441,15 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
return $animate; return $animate;
function queueAnimation(element, event, initialOptions) { function queueAnimation(originalElement, event, initialOptions) {
// we always make a copy of the options since // we always make a copy of the options since
// there should never be any side effects on // there should never be any side effects on
// the input data when running `$animateCss`. // the input data when running `$animateCss`.
var options = copy(initialOptions); var options = copy(initialOptions);
var node, parent; var element = stripCommentsFromElement(originalElement);
element = stripCommentsFromElement(element); var node = getDomNode(element);
if (element) { var parentNode = node && node.parentNode;
node = getDomNode(element);
parent = element.parent();
}
options = prepareAnimationOptions(options); options = prepareAnimationOptions(options);
...@@ -2494,49 +2484,45 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) { ...@@ -2494,49 +2484,45 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
options.to = null; options.to = null;
} }
// there are situations where a directive issues an animation for // If animations are hard-disabled for the whole application there is no need to continue.
// a jqLite wrapper that contains only comment nodes... If this // There are also situations where a directive issues an animation for a jqLite wrapper that
// happens then there is no way we can perform an animation // contains only comment nodes. In this case, there is no way we can perform an animation.
if (!node) { if (!animationsEnabled ||
close(); !node ||
return runner; !isAnimatableByFilter(node, event, initialOptions) ||
} !isAnimatableClassName(node, options)) {
var className = [node.className, options.addClass, options.removeClass].join(' ');
if (!isAnimatableClassName(className)) {
close(); close();
return runner; return runner;
} }
var isStructural = ['enter', 'move', 'leave'].indexOf(event) >= 0; var isStructural = ['enter', 'move', 'leave'].indexOf(event) >= 0;
var documentHidden = $document[0].hidden; var documentHidden = $$isDocumentHidden();
// this is a hard disable of all animations for the application or on // This is a hard disable of all animations the element itself, therefore there is no need to
// the element itself, therefore there is no need to continue further // continue further past this point if not enabled
// past this point if not enabled
// Animations are also disabled if the document is currently hidden (page is not visible // Animations are also disabled if the document is currently hidden (page is not visible
// to the user), because browsers slow down or do not flush calls to requestAnimationFrame // to the user), because browsers slow down or do not flush calls to requestAnimationFrame
var skipAnimations = !animationsEnabled || documentHidden || disabledElementsLookup.get(node); var skipAnimations = documentHidden || disabledElementsLookup.get(node);
var existingAnimation = (!skipAnimations && activeAnimationsLookup.get(node)) || {}; var existingAnimation = (!skipAnimations && activeAnimationsLookup.get(node)) || {};
var hasExistingAnimation = !!existingAnimation.state; var hasExistingAnimation = !!existingAnimation.state;
// there is no point in traversing the same collection of parent ancestors if a followup // there is no point in traversing the same collection of parent ancestors if a followup
// animation will be run on the same element that already did all that checking work // animation will be run on the same element that already did all that checking work
if (!skipAnimations && (!hasExistingAnimation || existingAnimation.state != PRE_DIGEST_STATE)) { if (!skipAnimations && (!hasExistingAnimation || existingAnimation.state !== PRE_DIGEST_STATE)) {
skipAnimations = !areAnimationsAllowed(element, parent, event); skipAnimations = !areAnimationsAllowed(node, parentNode, event);
} }
if (skipAnimations) { if (skipAnimations) {
// Callbacks should fire even if the document is hidden (regression fix for issue #14120) // Callbacks should fire even if the document is hidden (regression fix for issue #14120)
if (documentHidden) notifyProgress(runner, event, 'start'); if (documentHidden) notifyProgress(runner, event, 'start', getEventData(options));
close(); close();
if (documentHidden) notifyProgress(runner, event, 'close'); if (documentHidden) notifyProgress(runner, event, 'close', getEventData(options));
return runner; return runner;
} }
if (isStructural) { if (isStructural) {
closeChildAnimations(element); closeChildAnimations(node);
} }
var newAnimation = { var newAnimation = {
...@@ -2551,7 +2537,7 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) { ...@@ -2551,7 +2537,7 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
}; };
if (hasExistingAnimation) { if (hasExistingAnimation) {
var skipAnimationFlag = isAllowed('skip', element, newAnimation, existingAnimation); var skipAnimationFlag = isAllowed('skip', newAnimation, existingAnimation);
if (skipAnimationFlag) { if (skipAnimationFlag) {
if (existingAnimation.state === RUNNING_STATE) { if (existingAnimation.state === RUNNING_STATE) {
close(); close();
...@@ -2561,7 +2547,7 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) { ...@@ -2561,7 +2547,7 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
return existingAnimation.runner; return existingAnimation.runner;
} }
} }
var cancelAnimationFlag = isAllowed('cancel', element, newAnimation, existingAnimation); var cancelAnimationFlag = isAllowed('cancel', newAnimation, existingAnimation);
if (cancelAnimationFlag) { if (cancelAnimationFlag) {
if (existingAnimation.state === RUNNING_STATE) { if (existingAnimation.state === RUNNING_STATE) {
// this will end the animation right away and it is safe // this will end the animation right away and it is safe
...@@ -2583,12 +2569,12 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) { ...@@ -2583,12 +2569,12 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
// a joined animation means that this animation will take over the existing one // 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 // so an example would involve a leave animation taking over an enter. Then when
// the postDigest kicks in the enter will be ignored. // the postDigest kicks in the enter will be ignored.
var joinAnimationFlag = isAllowed('join', element, newAnimation, existingAnimation); var joinAnimationFlag = isAllowed('join', newAnimation, existingAnimation);
if (joinAnimationFlag) { if (joinAnimationFlag) {
if (existingAnimation.state === RUNNING_STATE) { if (existingAnimation.state === RUNNING_STATE) {
normalizeAnimationDetails(element, newAnimation); normalizeAnimationDetails(element, newAnimation);
} else { } else {
applyGeneratedPreparationClasses(element, isStructural ? event : null, options); applyGeneratedPreparationClasses($$jqLite, element, isStructural ? event : null, options);
event = newAnimation.event = existingAnimation.event; event = newAnimation.event = existingAnimation.event;
options = mergeAnimationDetails(element, existingAnimation, newAnimation); options = mergeAnimationDetails(element, existingAnimation, newAnimation);
...@@ -2617,7 +2603,7 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) { ...@@ -2617,7 +2603,7 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
if (!isValidAnimation) { if (!isValidAnimation) {
close(); close();
clearElementAnimationState(element); clearElementAnimationState(node);
return runner; return runner;
} }
...@@ -2625,9 +2611,18 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) { ...@@ -2625,9 +2611,18 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
var counter = (existingAnimation.counter || 0) + 1; var counter = (existingAnimation.counter || 0) + 1;
newAnimation.counter = counter; newAnimation.counter = counter;
markElementAnimationState(element, PRE_DIGEST_STATE, newAnimation); markElementAnimationState(node, PRE_DIGEST_STATE, newAnimation);
$rootScope.$$postDigest(function() { $rootScope.$$postDigest(function() {
// It is possible that the DOM nodes inside `originalElement` have been replaced. This can
// happen if the animated element is a transcluded clone and also has a `templateUrl`
// directive on it. Therefore, we must recreate `element` in order to interact with the
// actual DOM nodes.
// Note: We still need to use the old `node` for certain things, such as looking up in
// HashMaps where it was used as the key.
element = stripCommentsFromElement(originalElement);
var animationDetails = activeAnimationsLookup.get(node); var animationDetails = activeAnimationsLookup.get(node);
var animationCancelled = !animationDetails; var animationCancelled = !animationDetails;
animationDetails = animationDetails || {}; animationDetails = animationDetails || {};
...@@ -2666,7 +2661,7 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) { ...@@ -2666,7 +2661,7 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
// isn't allowed to animate from here then we need to clear the state of the element // 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. // so that any future animations won't read the expired animation data.
if (!isValidAnimation) { if (!isValidAnimation) {
clearElementAnimationState(element); clearElementAnimationState(node);
} }
return; return;
...@@ -2678,21 +2673,21 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) { ...@@ -2678,21 +2673,21 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
? 'setClass' ? 'setClass'
: animationDetails.event; : animationDetails.event;
markElementAnimationState(element, RUNNING_STATE); markElementAnimationState(node, RUNNING_STATE);
var realRunner = $$animation(element, event, animationDetails.options); var realRunner = $$animation(element, event, animationDetails.options);
// this will update the runner's flow-control events based on // this will update the runner's flow-control events based on
// the `realRunner` object. // the `realRunner` object.
runner.setHost(realRunner); runner.setHost(realRunner);
notifyProgress(runner, event, 'start', {}); notifyProgress(runner, event, 'start', getEventData(options));
realRunner.done(function(status) { realRunner.done(function(status) {
close(!status); close(!status);
var animationDetails = activeAnimationsLookup.get(node); var animationDetails = activeAnimationsLookup.get(node);
if (animationDetails && animationDetails.counter === counter) { if (animationDetails && animationDetails.counter === counter) {
clearElementAnimationState(getDomNode(element)); clearElementAnimationState(node);
} }
notifyProgress(runner, event, 'close', {}); notifyProgress(runner, event, 'close', getEventData(options));
}); });
}); });
...@@ -2700,7 +2695,7 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) { ...@@ -2700,7 +2695,7 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
function notifyProgress(runner, event, phase, data) { function notifyProgress(runner, event, phase, data) {
runInNextPostDigestOrNow(function() { runInNextPostDigestOrNow(function() {
var callbacks = findCallbacks(parent, element, event); var callbacks = findCallbacks(parentNode, node, event);
if (callbacks.length) { if (callbacks.length) {
// do not optimize this call here to RAF because // do not optimize this call here to RAF because
// we don't know how heavy the callback code here will // we don't know how heavy the callback code here will
...@@ -2710,16 +2705,16 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) { ...@@ -2710,16 +2705,16 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
forEach(callbacks, function(callback) { forEach(callbacks, function(callback) {
callback(element, phase, data); callback(element, phase, data);
}); });
cleanupEventListeners(phase, element); cleanupEventListeners(phase, node);
}); });
} else { } else {
cleanupEventListeners(phase, element); cleanupEventListeners(phase, node);
} }
}); });
runner.progress(event, phase, data); runner.progress(event, phase, data);
} }
function close(reject) { // jshint ignore:line function close(reject) {
clearGeneratedClasses(element, options); clearGeneratedClasses(element, options);
applyAnimationClasses(element, options); applyAnimationClasses(element, options);
applyAnimationStyles(element, options); applyAnimationStyles(element, options);
...@@ -2728,11 +2723,10 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) { ...@@ -2728,11 +2723,10 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
} }
} }
function closeChildAnimations(element) { function closeChildAnimations(node) {
var node = getDomNode(element);
var children = node.querySelectorAll('[' + NG_ANIMATE_ATTR_NAME + ']'); var children = node.querySelectorAll('[' + NG_ANIMATE_ATTR_NAME + ']');
forEach(children, function(child) { forEach(children, function(child) {
var state = parseInt(child.getAttribute(NG_ANIMATE_ATTR_NAME)); var state = parseInt(child.getAttribute(NG_ANIMATE_ATTR_NAME), 10);
var animationDetails = activeAnimationsLookup.get(child); var animationDetails = activeAnimationsLookup.get(child);
if (animationDetails) { if (animationDetails) {
switch (state) { switch (state) {
...@@ -2740,21 +2734,16 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) { ...@@ -2740,21 +2734,16 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
animationDetails.runner.end(); animationDetails.runner.end();
/* falls through */ /* falls through */
case PRE_DIGEST_STATE: case PRE_DIGEST_STATE:
activeAnimationsLookup.remove(child); activeAnimationsLookup.delete(child);
break; break;
} }
} }
}); });
} }
function clearElementAnimationState(element) { function clearElementAnimationState(node) {
var node = getDomNode(element);
node.removeAttribute(NG_ANIMATE_ATTR_NAME); node.removeAttribute(NG_ANIMATE_ATTR_NAME);
activeAnimationsLookup.remove(node); activeAnimationsLookup.delete(node);
}
function isMatchingElement(nodeOrElmA, nodeOrElmB) {
return getDomNode(nodeOrElmA) === getDomNode(nodeOrElmB);
} }
/** /**
...@@ -2764,54 +2753,54 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) { ...@@ -2764,54 +2753,54 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
* c) the element is not a child of the body * c) the element is not a child of the body
* d) the element is not a child of the $rootElement * d) the element is not a child of the $rootElement
*/ */
function areAnimationsAllowed(element, parentElement, event) { function areAnimationsAllowed(node, parentNode, event) {
var bodyElement = jqLite($document[0].body); var bodyNode = $document[0].body;
var bodyElementDetected = isMatchingElement(element, bodyElement) || element[0].nodeName === 'HTML'; var rootNode = getDomNode($rootElement);
var rootElementDetected = isMatchingElement(element, $rootElement);
var bodyNodeDetected = (node === bodyNode) || node.nodeName === 'HTML';
var rootNodeDetected = (node === rootNode);
var parentAnimationDetected = false; var parentAnimationDetected = false;
var elementDisabled = disabledElementsLookup.get(node);
var animateChildren; var animateChildren;
var elementDisabled = disabledElementsLookup.get(getDomNode(element));
var parentHost = jqLite.data(element[0], NG_ANIMATE_PIN_DATA); var parentHost = jqLite.data(node, NG_ANIMATE_PIN_DATA);
if (parentHost) { if (parentHost) {
parentElement = parentHost; parentNode = getDomNode(parentHost);
} }
parentElement = getDomNode(parentElement); while (parentNode) {
if (!rootNodeDetected) {
while (parentElement) { // AngularJS doesn't want to attempt to animate elements outside of the application
if (!rootElementDetected) {
// angular doesn't want to attempt to animate elements outside of the application
// therefore we need to ensure that the rootElement is an ancestor of the current element // therefore we need to ensure that the rootElement is an ancestor of the current element
rootElementDetected = isMatchingElement(parentElement, $rootElement); rootNodeDetected = (parentNode === rootNode);
} }
if (parentElement.nodeType !== ELEMENT_NODE) { if (parentNode.nodeType !== ELEMENT_NODE) {
// no point in inspecting the #document element // no point in inspecting the #document element
break; break;
} }
var details = activeAnimationsLookup.get(parentElement) || {}; var details = activeAnimationsLookup.get(parentNode) || {};
// either an enter, leave or move animation will commence // either an enter, leave or move animation will commence
// therefore we can't allow any animations to take place // therefore we can't allow any animations to take place
// but if a parent animation is class-based then that's ok // but if a parent animation is class-based then that's ok
if (!parentAnimationDetected) { if (!parentAnimationDetected) {
var parentElementDisabled = disabledElementsLookup.get(parentElement); var parentNodeDisabled = disabledElementsLookup.get(parentNode);
if (parentElementDisabled === true && elementDisabled !== false) { if (parentNodeDisabled === true && elementDisabled !== false) {
// disable animations if the user hasn't explicitly enabled animations on the // disable animations if the user hasn't explicitly enabled animations on the
// current element // current element
elementDisabled = true; elementDisabled = true;
// element is disabled via parent element, no need to check anything else // element is disabled via parent element, no need to check anything else
break; break;
} else if (parentElementDisabled === false) { } else if (parentNodeDisabled === false) {
elementDisabled = false; elementDisabled = false;
} }
parentAnimationDetected = details.structural; parentAnimationDetected = details.structural;
} }
if (isUndefined(animateChildren) || animateChildren === true) { if (isUndefined(animateChildren) || animateChildren === true) {
var value = jqLite.data(parentElement, NG_ANIMATE_CHILDREN_DATA); var value = jqLite.data(parentNode, NG_ANIMATE_CHILDREN_DATA);
if (isDefined(value)) { if (isDefined(value)) {
animateChildren = value; animateChildren = value;
} }
...@@ -2820,57 +2809,115 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) { ...@@ -2820,57 +2809,115 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
// there is no need to continue traversing at this point // there is no need to continue traversing at this point
if (parentAnimationDetected && animateChildren === false) break; if (parentAnimationDetected && animateChildren === false) break;
if (!bodyElementDetected) { if (!bodyNodeDetected) {
// we also need to ensure that the element is or will be a part of the body element // we also need to ensure that the element is or will be a part of the body element
// otherwise it is pointless to even issue an animation to be rendered // otherwise it is pointless to even issue an animation to be rendered
bodyElementDetected = isMatchingElement(parentElement, bodyElement); bodyNodeDetected = (parentNode === bodyNode);
} }
if (bodyElementDetected && rootElementDetected) { if (bodyNodeDetected && rootNodeDetected) {
// If both body and root have been found, any other checks are pointless, // If both body and root have been found, any other checks are pointless,
// as no animation data should live outside the application // as no animation data should live outside the application
break; break;
} }
if (!rootElementDetected) { if (!rootNodeDetected) {
// If no rootElement is detected, check if the parentElement is pinned to another element // If `rootNode` is not detected, check if `parentNode` is pinned to another element
parentHost = jqLite.data(parentElement, NG_ANIMATE_PIN_DATA); parentHost = jqLite.data(parentNode, NG_ANIMATE_PIN_DATA);
if (parentHost) { if (parentHost) {
// The pin target element becomes the next parent element // The pin target element becomes the next parent element
parentElement = getDomNode(parentHost); parentNode = getDomNode(parentHost);
continue; continue;
} }
} }
parentElement = parentElement.parentNode; parentNode = parentNode.parentNode;
} }
var allowAnimation = (!parentAnimationDetected || animateChildren) && elementDisabled !== true; var allowAnimation = (!parentAnimationDetected || animateChildren) && elementDisabled !== true;
return allowAnimation && rootElementDetected && bodyElementDetected; return allowAnimation && rootNodeDetected && bodyNodeDetected;
} }
function markElementAnimationState(element, state, details) { function markElementAnimationState(node, state, details) {
details = details || {}; details = details || {};
details.state = state; details.state = state;
var node = getDomNode(element);
node.setAttribute(NG_ANIMATE_ATTR_NAME, state); node.setAttribute(NG_ANIMATE_ATTR_NAME, state);
var oldValue = activeAnimationsLookup.get(node); var oldValue = activeAnimationsLookup.get(node);
var newValue = oldValue var newValue = oldValue
? extend(oldValue, details) ? extend(oldValue, details)
: details; : details;
activeAnimationsLookup.put(node, newValue); activeAnimationsLookup.set(node, newValue);
}
}];
}];
/** @this */
var $$AnimateCacheProvider = function() {
var KEY = '$$ngAnimateParentKey';
var parentCounter = 0;
var cache = Object.create(null);
this.$get = [function() {
return {
cacheKey: function(node, method, addClass, removeClass) {
var parentNode = node.parentNode;
var parentID = parentNode[KEY] || (parentNode[KEY] = ++parentCounter);
var parts = [parentID, method, node.getAttribute('class')];
if (addClass) {
parts.push(addClass);
}
if (removeClass) {
parts.push(removeClass);
}
return parts.join(' ');
},
containsCachedAnimationWithoutDuration: function(key) {
var entry = cache[key];
// nothing cached, so go ahead and animate
// otherwise it should be a valid animation
return (entry && !entry.isValid) || false;
},
flush: function() {
cache = Object.create(null);
},
count: function(key) {
var entry = cache[key];
return entry ? entry.total : 0;
},
get: function(key) {
var entry = cache[key];
return entry && entry.value;
},
put: function(key, value, isValid) {
if (!cache[key]) {
cache[key] = { total: 1, value: value, isValid: isValid };
} else {
cache[key].total++;
cache[key].value = value;
}
} }
};
}]; }];
}]; };
var $$AnimationProvider = ['$animateProvider', function($animateProvider) { /* exported $$AnimationProvider */
var $$AnimationProvider = ['$animateProvider', /** @this */ function($animateProvider) {
var NG_ANIMATE_REF_ATTR = 'ng-animate-ref'; var NG_ANIMATE_REF_ATTR = 'ng-animate-ref';
var drivers = this.drivers = []; var drivers = this.drivers = [];
var RUNNER_STORAGE_KEY = '$$animationRunner'; var RUNNER_STORAGE_KEY = '$$animationRunner';
var PREPARE_CLASSES_KEY = '$$animatePrepareClasses';
function setRunner(element, runner) { function setRunner(element, runner) {
element.data(RUNNER_STORAGE_KEY, runner); element.data(RUNNER_STORAGE_KEY, runner);
...@@ -2884,22 +2931,23 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) { ...@@ -2884,22 +2931,23 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) {
return element.data(RUNNER_STORAGE_KEY); return element.data(RUNNER_STORAGE_KEY);
} }
this.$get = ['$$jqLite', '$rootScope', '$injector', '$$AnimateRunner', '$$HashMap', '$$rAFScheduler', this.$get = ['$$jqLite', '$rootScope', '$injector', '$$AnimateRunner', '$$Map', '$$rAFScheduler', '$$animateCache',
function($$jqLite, $rootScope, $injector, $$AnimateRunner, $$HashMap, $$rAFScheduler) { function($$jqLite, $rootScope, $injector, $$AnimateRunner, $$Map, $$rAFScheduler, $$animateCache) {
var animationQueue = []; var animationQueue = [];
var applyAnimationClasses = applyAnimationClassesFactory($$jqLite); var applyAnimationClasses = applyAnimationClassesFactory($$jqLite);
function sortAnimations(animations) { function sortAnimations(animations) {
var tree = { children: [] }; var tree = { children: [] };
var i, lookup = new $$HashMap(); var i, lookup = new $$Map();
// this is done first beforehand so that the hashmap // this is done first beforehand so that the map
// is filled with a list of the elements that will be animated // is filled with a list of the elements that will be animated
for (i = 0; i < animations.length; i++) { for (i = 0; i < animations.length; i++) {
var animation = animations[i]; var animation = animations[i];
lookup.put(animation.domNode, animations[i] = { lookup.set(animation.domNode, animations[i] = {
domNode: animation.domNode, domNode: animation.domNode,
element: animation.element,
fn: animation.fn, fn: animation.fn,
children: [] children: []
}); });
...@@ -2917,7 +2965,7 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) { ...@@ -2917,7 +2965,7 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) {
var elementNode = entry.domNode; var elementNode = entry.domNode;
var parentNode = elementNode.parentNode; var parentNode = elementNode.parentNode;
lookup.put(elementNode, entry); lookup.set(elementNode, entry);
var parentEntry; var parentEntry;
while (parentNode) { while (parentNode) {
...@@ -2956,7 +3004,7 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) { ...@@ -2956,7 +3004,7 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) {
result.push(row); result.push(row);
row = []; row = [];
} }
row.push(entry.fn); row.push(entry);
entry.children.forEach(function(childEntry) { entry.children.forEach(function(childEntry) {
nextLevelEntries++; nextLevelEntries++;
queue.push(childEntry); queue.push(childEntry);
...@@ -2991,8 +3039,6 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) { ...@@ -2991,8 +3039,6 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) {
return runner; return runner;
} }
setRunner(element, runner);
var classes = mergeClasses(element.attr('class'), mergeClasses(options.addClass, options.removeClass)); var classes = mergeClasses(element.attr('class'), mergeClasses(options.addClass, options.removeClass));
var tempClasses = options.tempClasses; var tempClasses = options.tempClasses;
if (tempClasses) { if (tempClasses) {
...@@ -3000,12 +3046,12 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) { ...@@ -3000,12 +3046,12 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) {
options.tempClasses = null; options.tempClasses = null;
} }
var prepareClassName;
if (isStructural) { if (isStructural) {
prepareClassName = 'ng-' + event + PREPARE_CLASS_SUFFIX; element.data(PREPARE_CLASSES_KEY, 'ng-' + event + PREPARE_CLASS_SUFFIX);
$$jqLite.addClass(element, prepareClassName);
} }
setRunner(element, runner);
animationQueue.push({ animationQueue.push({
// this data is used by the postDigest code and passed into // this data is used by the postDigest code and passed into
// the driver step function // the driver step function
...@@ -3045,16 +3091,31 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) { ...@@ -3045,16 +3091,31 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) {
var toBeSortedAnimations = []; var toBeSortedAnimations = [];
forEach(groupedAnimations, function(animationEntry) { forEach(groupedAnimations, function(animationEntry) {
var element = animationEntry.from ? animationEntry.from.element : animationEntry.element;
var extraClasses = options.addClass;
extraClasses = (extraClasses ? (extraClasses + ' ') : '') + NG_ANIMATE_CLASSNAME;
var cacheKey = $$animateCache.cacheKey(element[0], animationEntry.event, extraClasses, options.removeClass);
toBeSortedAnimations.push({ toBeSortedAnimations.push({
domNode: getDomNode(animationEntry.from ? animationEntry.from.element : animationEntry.element), element: element,
domNode: getDomNode(element),
fn: function triggerAnimationStart() { fn: function triggerAnimationStart() {
var startAnimationFn, closeFn = animationEntry.close;
// in the event that we've cached the animation status for this element
// and it's in fact an invalid animation (something that has duration = 0)
// then we should skip all the heavy work from here on
if ($$animateCache.containsCachedAnimationWithoutDuration(cacheKey)) {
closeFn();
return;
}
// it's important that we apply the `ng-animate` CSS class and the // it's important that we apply the `ng-animate` CSS class and the
// temporary classes before we do any driver invoking since these // temporary classes before we do any driver invoking since these
// CSS classes may be required for proper CSS detection. // CSS classes may be required for proper CSS detection.
animationEntry.beforeStart(); animationEntry.beforeStart();
var startAnimationFn, closeFn = animationEntry.close;
// in the event that the element was removed before the digest runs or // in the event that the element was removed before the digest runs or
// during the RAF sequencing then we should not trigger the animation. // during the RAF sequencing then we should not trigger the animation.
var targetElement = animationEntry.anchors var targetElement = animationEntry.anchors
...@@ -3084,7 +3145,32 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) { ...@@ -3084,7 +3145,32 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) {
// we need to sort each of the animations in order of parent to child // 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 // relationships. This ensures that the child classes are applied at the
// right time. // right time.
$$rAFScheduler(sortAnimations(toBeSortedAnimations)); var finalAnimations = sortAnimations(toBeSortedAnimations);
for (var i = 0; i < finalAnimations.length; i++) {
var innerArray = finalAnimations[i];
for (var j = 0; j < innerArray.length; j++) {
var entry = innerArray[j];
var element = entry.element;
// the RAFScheduler code only uses functions
finalAnimations[i][j] = entry.fn;
// the first row of elements shouldn't have a prepare-class added to them
// since the elements are at the top of the animation hierarchy and they
// will be applied without a RAF having to pass...
if (i === 0) {
element.removeData(PREPARE_CLASSES_KEY);
continue;
}
var prepareClassName = element.data(PREPARE_CLASSES_KEY);
if (prepareClassName) {
$$jqLite.addClass(element, prepareClassName);
}
}
}
$$rAFScheduler(finalAnimations);
}); });
return runner; return runner;
...@@ -3222,10 +3308,10 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) { ...@@ -3222,10 +3308,10 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) {
} }
function beforeStart() { function beforeStart() {
element.addClass(NG_ANIMATE_CLASSNAME); tempClasses = (tempClasses ? (tempClasses + ' ') : '') + NG_ANIMATE_CLASSNAME;
if (tempClasses) {
$$jqLite.addClass(element, tempClasses); $$jqLite.addClass(element, tempClasses);
}
var prepareClassName = element.data(PREPARE_CLASSES_KEY);
if (prepareClassName) { if (prepareClassName) {
$$jqLite.removeClass(element, prepareClassName); $$jqLite.removeClass(element, prepareClassName);
prepareClassName = null; prepareClassName = null;
...@@ -3253,7 +3339,7 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) { ...@@ -3253,7 +3339,7 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) {
} }
} }
function close(rejected) { // jshint ignore:line function close(rejected) {
element.off('$destroy', handleDestroyedElement); element.off('$destroy', handleDestroyedElement);
removeRunner(element); removeRunner(element);
...@@ -3265,14 +3351,13 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) { ...@@ -3265,14 +3351,13 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) {
$$jqLite.removeClass(element, tempClasses); $$jqLite.removeClass(element, tempClasses);
} }
element.removeClass(NG_ANIMATE_CLASSNAME);
runner.complete(!rejected); runner.complete(!rejected);
} }
}; };
}]; }];
}]; }];
/** /**
* @ngdoc directive * @ngdoc directive
* @name ngAnimateSwap * @name ngAnimateSwap
* @restrict A * @restrict A
...@@ -3359,12 +3444,13 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) { ...@@ -3359,12 +3444,13 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) {
* </file> * </file>
* </example> * </example>
*/ */
var ngAnimateSwapDirective = ['$animate', '$rootScope', function($animate, $rootScope) { var ngAnimateSwapDirective = ['$animate', function($animate) {
return { return {
restrict: 'A', restrict: 'A',
transclude: 'element', transclude: 'element',
terminal: true, terminal: true,
priority: 600, // we use 600 here to ensure that the directive is caught before others priority: 550, // We use 550 here to ensure that the directive is caught before others,
// but after `ngIf` (at priority 600).
link: function(scope, $element, attrs, ctrl, $transclude) { link: function(scope, $element, attrs, ctrl, $transclude) {
var previousElement, previousScope; var previousElement, previousScope;
scope.$watchCollection(attrs.ngAnimateSwap || attrs['for'], function(value) { scope.$watchCollection(attrs.ngAnimateSwap || attrs['for'], function(value) {
...@@ -3376,42 +3462,26 @@ var ngAnimateSwapDirective = ['$animate', '$rootScope', function($animate, $root ...@@ -3376,42 +3462,26 @@ var ngAnimateSwapDirective = ['$animate', '$rootScope', function($animate, $root
previousScope = null; previousScope = null;
} }
if (value || value === 0) { if (value || value === 0) {
previousScope = scope.$new(); $transclude(function(clone, childScope) {
$transclude(previousScope, function(element) { previousElement = clone;
previousElement = element; previousScope = childScope;
$animate.enter(element, null, $element); $animate.enter(clone, null, $element);
}); });
} }
}); });
} }
}; };
}]; }];
/* global angularAnimateModule: true,
ngAnimateSwapDirective,
$$AnimateAsyncRunFactory,
$$rAFSchedulerFactory,
$$AnimateChildrenDirective,
$$AnimateQueueProvider,
$$AnimationProvider,
$AnimateCssProvider,
$$AnimateCssDriverProvider,
$$AnimateJsProvider,
$$AnimateJsDriverProvider,
*/
/** /**
* @ngdoc module * @ngdoc module
* @name ngAnimate * @name ngAnimate
* @description * @description
* *
* The `ngAnimate` module provides support for CSS-based animations (keyframes and transitions) as well as JavaScript-based animations via * The `ngAnimate` module provides support for CSS-based animations (keyframes and transitions) as well as JavaScript-based animations via
* callback hooks. Animations are not enabled by default, however, by including `ngAnimate` the animation hooks are enabled for an Angular app. * callback hooks. Animations are not enabled by default, however, by including `ngAnimate` the animation hooks are enabled for an AngularJS app.
* *
* <div doc-module-components="ngAnimate"></div> * ## Usage
*
* # Usage
* Simply put, there are two ways to make use of animations when ngAnimate is used: by using **CSS** and **JavaScript**. The former works purely based * Simply put, there are two ways to make use of animations when ngAnimate is used: by using **CSS** and **JavaScript**. The former works purely based
* using CSS (by using matching CSS selectors/styles) and the latter triggers animations that are registered via `module.animation()`. For * using CSS (by using matching CSS selectors/styles) and the latter triggers animations that are registered via `module.animation()`. For
* both CSS and JS animations the sole requirement is to have a matching `CSS class` that exists both in the registered animation and within * both CSS and JS animations the sole requirement is to have a matching `CSS class` that exists both in the registered animation and within
...@@ -3421,24 +3491,32 @@ var ngAnimateSwapDirective = ['$animate', '$rootScope', function($animate, $root ...@@ -3421,24 +3491,32 @@ var ngAnimateSwapDirective = ['$animate', '$rootScope', function($animate, $root
* The following directives are "animation aware": * The following directives are "animation aware":
* *
* | Directive | Supported Animations | * | Directive | Supported Animations |
* |----------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------| * |-------------------------------------------------------------------------------|---------------------------------------------------------------------------|
* | {@link ng.directive:ngRepeat#animations ngRepeat} | enter, leave and move | * | {@link ng.directive:form#animations form / ngForm} | add and remove ({@link ng.directive:form#css-classes various classes}) |
* | {@link ngRoute.directive:ngView#animations ngView} | enter and leave | * | {@link ngAnimate.directive:ngAnimateSwap#animations ngAnimateSwap} | enter and leave |
* | {@link ng.directive:ngClass#animations ngClass / {{class&#125;&#8203;&#125;} | add and remove |
* | {@link ng.directive:ngClassEven#animations ngClassEven} | add and remove |
* | {@link ng.directive:ngClassOdd#animations ngClassOdd} | add and remove |
* | {@link ng.directive:ngHide#animations ngHide} | add and remove (the `ng-hide` class) |
* | {@link ng.directive:ngIf#animations ngIf} | enter and leave |
* | {@link ng.directive:ngInclude#animations ngInclude} | enter and leave | * | {@link ng.directive:ngInclude#animations ngInclude} | enter and leave |
* | {@link module:ngMessages#animations ngMessage / ngMessageExp} | enter and leave |
* | {@link module:ngMessages#animations ngMessages} | add and remove (the `ng-active`/`ng-inactive` classes) |
* | {@link ng.directive:ngModel#animations ngModel} | add and remove ({@link ng.directive:ngModel#css-classes various classes}) |
* | {@link ng.directive:ngRepeat#animations ngRepeat} | enter, leave, and move |
* | {@link ng.directive:ngShow#animations ngShow} | add and remove (the `ng-hide` class) |
* | {@link ng.directive:ngSwitch#animations ngSwitch} | enter and leave | * | {@link ng.directive:ngSwitch#animations ngSwitch} | enter and leave |
* | {@link ng.directive:ngIf#animations ngIf} | enter and leave | * | {@link ngRoute.directive:ngView#animations ngView} | enter and leave |
* | {@link ng.directive:ngClass#animations ngClass} | add and remove (the CSS class(es) present) |
* | {@link ng.directive:ngShow#animations ngShow} & {@link ng.directive:ngHide#animations ngHide} | add and remove (the ng-hide class value) |
* | {@link ng.directive:form#animation-hooks form} & {@link ng.directive:ngModel#animation-hooks ngModel} | add and remove (dirty, pristine, valid, invalid & all other validations) |
* | {@link module:ngMessages#animations ngMessages} | add and remove (ng-active & ng-inactive) |
* | {@link module:ngMessages#animations ngMessage} | enter and leave |
* *
* (More information can be found by visiting each the documentation associated with each directive.) * (More information can be found by visiting the documentation associated with each directive.)
*
* For a full breakdown of the steps involved during each animation event, refer to the
* {@link ng.$animate `$animate` API docs}.
* *
* ## CSS-based Animations * ## CSS-based Animations
* *
* CSS-based animations with ngAnimate are unique since they require no JavaScript code at all. By using a CSS class that we reference between our HTML * CSS-based animations with ngAnimate are unique since they require no JavaScript code at all. By using a CSS class that we reference between our HTML
* and CSS code we can create an animation that will be picked up by Angular when an the underlying directive performs an operation. * and CSS code we can create an animation that will be picked up by AngularJS when an underlying directive performs an operation.
* *
* The example below shows how an `enter` animation can be made possible on an element using `ng-if`: * The example below shows how an `enter` animation can be made possible on an element using `ng-if`:
* *
...@@ -3578,6 +3656,10 @@ var ngAnimateSwapDirective = ['$animate', '$rootScope', function($animate, $root ...@@ -3578,6 +3656,10 @@ var ngAnimateSwapDirective = ['$animate', '$rootScope', function($animate, $root
* /&#42; As of 1.4.4, this must always be set: it signals ngAnimate * /&#42; As of 1.4.4, this must always be set: it signals ngAnimate
* to not accidentally inherit a delay property from another CSS class &#42;/ * to not accidentally inherit a delay property from another CSS class &#42;/
* transition-duration: 0s; * transition-duration: 0s;
*
* /&#42; if you are using animations instead of transitions you should configure as follows:
* animation-delay: 0.1s;
* animation-duration: 0s; &#42;/
* } * }
* .my-animation.ng-enter.ng-enter-active { * .my-animation.ng-enter.ng-enter-active {
* /&#42; standard transition styles &#42;/ * /&#42; standard transition styles &#42;/
...@@ -3666,9 +3748,22 @@ var ngAnimateSwapDirective = ['$animate', '$rootScope', function($animate, $root ...@@ -3666,9 +3748,22 @@ var ngAnimateSwapDirective = ['$animate', '$rootScope', function($animate, $root
* .message.ng-enter-prepare { * .message.ng-enter-prepare {
* opacity: 0; * opacity: 0;
* } * }
*
* ``` * ```
* *
* ### Animating between value changes
*
* Sometimes you need to animate between different expression states, whose values
* don't necessary need to be known or referenced in CSS styles.
* Unless possible with another {@link ngAnimate#directive-support "animation aware" directive},
* that specific use case can always be covered with {@link ngAnimate.directive:ngAnimateSwap} as
* can be seen in {@link ngAnimate.directive:ngAnimateSwap#examples this example}.
*
* Note that {@link ngAnimate.directive:ngAnimateSwap} is a *structural directive*, which means it
* creates a new instance of the element (including any other/child directives it may have) and
* links it to a new scope every time *swap* happens. In some cases this might not be desirable
* (e.g. for performance reasons, or when you wish to retain internal state on the original
* element instance).
*
* ## JavaScript-based Animations * ## JavaScript-based Animations
* *
* ngAnimate also allows for animations to be consumed by JavaScript code. The approach is similar to CSS-based animations (where there is a shared * ngAnimate also allows for animations to be consumed by JavaScript code. The approach is similar to CSS-based animations (where there is a shared
...@@ -3693,7 +3788,7 @@ var ngAnimateSwapDirective = ['$animate', '$rootScope', function($animate, $root ...@@ -3693,7 +3788,7 @@ var ngAnimateSwapDirective = ['$animate', '$rootScope', function($animate, $root
* enter: function(element, doneFn) { * enter: function(element, doneFn) {
* jQuery(element).fadeIn(1000, doneFn); * jQuery(element).fadeIn(1000, doneFn);
* *
* // remember to call doneFn so that angular * // remember to call doneFn so that AngularJS
* // knows that the animation has concluded * // knows that the animation has concluded
* }, * },
* *
...@@ -3741,7 +3836,7 @@ var ngAnimateSwapDirective = ['$animate', '$rootScope', function($animate, $root ...@@ -3741,7 +3836,7 @@ var ngAnimateSwapDirective = ['$animate', '$rootScope', function($animate, $root
* *
* ## CSS + JS Animations Together * ## CSS + JS Animations Together
* *
* AngularJS 1.4 and higher has taken steps to make the amalgamation of CSS and JS animations more flexible. However, unlike earlier versions of Angular, * AngularJS 1.4 and higher has taken steps to make the amalgamation of CSS and JS animations more flexible. However, unlike earlier versions of AngularJS,
* defining CSS and JS animations to work off of the same CSS class will not work anymore. Therefore the example below will only result in **JS animations taking * defining CSS and JS animations to work off of the same CSS class will not work anymore. Therefore the example below will only result in **JS animations taking
* charge of the animation**: * charge of the animation**:
* *
...@@ -3779,7 +3874,7 @@ var ngAnimateSwapDirective = ['$animate', '$rootScope', function($animate, $root ...@@ -3779,7 +3874,7 @@ var ngAnimateSwapDirective = ['$animate', '$rootScope', function($animate, $root
* myModule.animation('.slide', ['$animateCss', function($animateCss) { * myModule.animation('.slide', ['$animateCss', function($animateCss) {
* return { * return {
* enter: function(element) { * enter: function(element) {
* // this will trigger `.slide.ng-enter` and `.slide.ng-enter-active`. * // this will trigger `.slide.ng-enter` and `.slide.ng-enter-active`.
* return $animateCss(element, { * return $animateCss(element, {
* event: 'enter', * event: 'enter',
* structural: true * structural: true
...@@ -3933,7 +4028,7 @@ var ngAnimateSwapDirective = ['$animate', '$rootScope', function($animate, $root ...@@ -3933,7 +4028,7 @@ var ngAnimateSwapDirective = ['$animate', '$rootScope', function($animate, $root
deps="angular-animate.js;angular-route.js" deps="angular-animate.js;angular-route.js"
animations="true"> animations="true">
<file name="index.html"> <file name="index.html">
<a href="#/">Home</a> <a href="#!/">Home</a>
<hr /> <hr />
<div class="view-container"> <div class="view-container">
<div ng-view class="view"></div> <div ng-view class="view"></div>
...@@ -3953,22 +4048,23 @@ var ngAnimateSwapDirective = ['$animate', '$rootScope', function($animate, $root ...@@ -3953,22 +4048,23 @@ var ngAnimateSwapDirective = ['$animate', '$rootScope', function($animate, $root
}]) }])
.run(['$rootScope', function($rootScope) { .run(['$rootScope', function($rootScope) {
$rootScope.records = [ $rootScope.records = [
{ id:1, title: "Miss Beulah Roob" }, { id: 1, title: 'Miss Beulah Roob' },
{ id:2, title: "Trent Morissette" }, { id: 2, title: 'Trent Morissette' },
{ id:3, title: "Miss Ava Pouros" }, { id: 3, title: 'Miss Ava Pouros' },
{ id:4, title: "Rod Pouros" }, { id: 4, title: 'Rod Pouros' },
{ id:5, title: "Abdul Rice" }, { id: 5, title: 'Abdul Rice' },
{ id:6, title: "Laurie Rutherford Sr." }, { id: 6, title: 'Laurie Rutherford Sr.' },
{ id:7, title: "Nakia McLaughlin" }, { id: 7, title: 'Nakia McLaughlin' },
{ id:8, title: "Jordon Blanda DVM" }, { id: 8, title: 'Jordon Blanda DVM' },
{ id:9, title: "Rhoda Hand" }, { id: 9, title: 'Rhoda Hand' },
{ id:10, title: "Alexandrea Sauer" } { id: 10, title: 'Alexandrea Sauer' }
]; ];
}]) }])
.controller('HomeController', [function() { .controller('HomeController', [function() {
//empty //empty
}]) }])
.controller('ProfileController', ['$rootScope', '$routeParams', function($rootScope, $routeParams) { .controller('ProfileController', ['$rootScope', '$routeParams',
function ProfileController($rootScope, $routeParams) {
var index = parseInt($routeParams.id, 10); var index = parseInt($routeParams.id, 10);
var record = $rootScope.records[index - 1]; var record = $rootScope.records[index - 1];
...@@ -3980,7 +4076,7 @@ var ngAnimateSwapDirective = ['$animate', '$rootScope', function($animate, $root ...@@ -3980,7 +4076,7 @@ var ngAnimateSwapDirective = ['$animate', '$rootScope', function($animate, $root
<h2>Welcome to the home page</h1> <h2>Welcome to the home page</h1>
<p>Please click on an element</p> <p>Please click on an element</p>
<a class="record" <a class="record"
ng-href="#/profile/{{ record.id }}" ng-href="#!/profile/{{ record.id }}"
ng-animate-ref="{{ record.id }}" ng-animate-ref="{{ record.id }}"
ng-repeat="record in records"> ng-repeat="record in records">
{{ record.title }} {{ record.title }}
...@@ -4056,7 +4152,7 @@ var ngAnimateSwapDirective = ['$animate', '$rootScope', function($animate, $root ...@@ -4056,7 +4152,7 @@ var ngAnimateSwapDirective = ['$animate', '$rootScope', function($animate, $root
* *
* ## Using $animate in your directive code * ## Using $animate in your directive code
* *
* So far we've explored how to feed in animations into an Angular application, but how do we trigger animations within our own directives in our application? * So far we've explored how to feed in animations into an AngularJS application, but how do we trigger animations within our own directives in our application?
* By injecting the `$animate` service into our directive code, we can trigger structural and class-based hooks which can then be consumed by animations. Let's * By injecting the `$animate` service into our directive code, we can trigger structural and class-based hooks which can then be consumed by animations. Let's
* imagine we have a greeting box that shows and hides itself when the data changes * imagine we have a greeting box that shows and hides itself when the data changes
* *
...@@ -4099,7 +4195,7 @@ var ngAnimateSwapDirective = ['$animate', '$rootScope', function($animate, $root ...@@ -4099,7 +4195,7 @@ var ngAnimateSwapDirective = ['$animate', '$rootScope', function($animate, $root
* }); * });
* ``` * ```
* *
* (Note that earlier versions of Angular prior to v1.4 required the promise code to be wrapped using `$scope.$apply(...)`. This is not the case * (Note that earlier versions of AngularJS prior to v1.4 required the promise code to be wrapped using `$scope.$apply(...)`. This is not the case
* anymore.) * anymore.)
* *
* In addition to the animation promise, we can also make use of animation-related callbacks within our directives and controller code by registering * In addition to the animation promise, we can also make use of animation-related callbacks within our directives and controller code by registering
...@@ -4114,10 +4210,23 @@ var ngAnimateSwapDirective = ['$animate', '$rootScope', function($animate, $root ...@@ -4114,10 +4210,23 @@ var ngAnimateSwapDirective = ['$animate', '$rootScope', function($animate, $root
* }]) * }])
* ``` * ```
* *
* (Note that you will need to trigger a digest within the callback to get angular to notice any scope-related changes.) * (Note that you will need to trigger a digest within the callback to get AngularJS to notice any scope-related changes.)
*/ */
/** var copy;
var extend;
var forEach;
var isArray;
var isDefined;
var isElement;
var isFunction;
var isObject;
var isString;
var isUndefined;
var jqLite;
var noop;
/**
* @ngdoc service * @ngdoc service
* @name $animate * @name $animate
* @kind object * @kind object
...@@ -4127,13 +4236,30 @@ var ngAnimateSwapDirective = ['$animate', '$rootScope', function($animate, $root ...@@ -4127,13 +4236,30 @@ var ngAnimateSwapDirective = ['$animate', '$rootScope', function($animate, $root
* *
* Click here {@link ng.$animate to learn more about animations with `$animate`}. * Click here {@link ng.$animate to learn more about animations with `$animate`}.
*/ */
angular.module('ngAnimate', []) angular.module('ngAnimate', [], function initAngularHelpers() {
// Access helpers from AngularJS core.
// Do it inside a `config` block to ensure `window.angular` is available.
noop = angular.noop;
copy = angular.copy;
extend = angular.extend;
jqLite = angular.element;
forEach = angular.forEach;
isArray = angular.isArray;
isString = angular.isString;
isObject = angular.isObject;
isUndefined = angular.isUndefined;
isDefined = angular.isDefined;
isFunction = angular.isFunction;
isElement = angular.isElement;
})
.info({ angularVersion: '1.8.2' })
.directive('ngAnimateSwap', ngAnimateSwapDirective) .directive('ngAnimateSwap', ngAnimateSwapDirective)
.directive('ngAnimateChildren', $$AnimateChildrenDirective) .directive('ngAnimateChildren', $$AnimateChildrenDirective)
.factory('$$rAFScheduler', $$rAFSchedulerFactory) .factory('$$rAFScheduler', $$rAFSchedulerFactory)
.provider('$$animateQueue', $$AnimateQueueProvider) .provider('$$animateQueue', $$AnimateQueueProvider)
.provider('$$animateCache', $$AnimateCacheProvider)
.provider('$$animation', $$AnimationProvider) .provider('$$animation', $$AnimationProvider)
.provider('$animateCss', $AnimateCssProvider) .provider('$animateCss', $AnimateCssProvider)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment