http://git-wip-us.apache.org/repos/asf/struts-examples/blob/558c6b0f/rest-angular/src/main/webapp/js/lib/angular/angular-animate.js
----------------------------------------------------------------------
diff --git a/rest-angular/src/main/webapp/js/lib/angular/angular-animate.js
b/rest-angular/src/main/webapp/js/lib/angular/angular-animate.js
index b19f02d..2778fc5 100644
--- a/rest-angular/src/main/webapp/js/lib/angular/angular-animate.js
+++ b/rest-angular/src/main/webapp/js/lib/angular/angular-animate.js
@@ -1,12 +1,13 @@
/**
- * @license AngularJS v1.4.5
- * (c) 2010-2015 Google, Inc. http://angularjs.org
+ * @license AngularJS v1.5.0
+ * (c) 2010-2016 Google, Inc. http://angularjs.org
* License: MIT
*/
(function(window, angular, undefined) {'use strict';
/* jshint ignore:start */
var noop = angular.noop;
+var copy = angular.copy;
var extend = angular.extend;
var jqLite = angular.element;
var forEach = angular.forEach;
@@ -25,6 +26,7 @@ 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';
@@ -41,7 +43,7 @@ var CSS_PREFIX = '', TRANSITION_PROP, TRANSITIONEND_EVENT,
ANIMATION_PROP, ANIMA
// Also, the only modern browser that uses vendor prefixes for
transitions/keyframes is webkit
// therefore there is no reason to test anymore for other vendor prefixes:
// http://caniuse.com/#search=transition
-if (window.ontransitionend === undefined && window.onwebkittransitionend !==
undefined) {
+if (isUndefined(window.ontransitionend) &&
isDefined(window.onwebkittransitionend)) {
CSS_PREFIX = '-webkit-';
TRANSITION_PROP = 'WebkitTransition';
TRANSITIONEND_EVENT = 'webkitTransitionEnd transitionend';
@@ -50,7 +52,7 @@ if (window.ontransitionend === undefined &&
window.onwebkittransitionend !== und
TRANSITIONEND_EVENT = 'transitionend';
}
-if (window.onanimationend === undefined && window.onwebkitanimationend !==
undefined) {
+if (isUndefined(window.onanimationend) &&
isDefined(window.onwebkitanimationend)) {
CSS_PREFIX = '-webkit-';
ANIMATION_PROP = 'WebkitAnimation';
ANIMATIONEND_EVENT = 'webkitAnimationEnd animationend';
@@ -76,6 +78,7 @@ var isPromiseLike = function(p) {
return p && p.then ? true : false;
};
+var ngMinErr = angular.$$minErr('ng');
function assertArg(arg, name, reason) {
if (!arg) {
throw ngMinErr('areq', "Argument '{0}' is {1}", (name || '?'), (reason ||
"required"));
@@ -220,7 +223,10 @@ function applyAnimationToStyles(element, options) {
}
}
-function mergeAnimationOptions(element, target, newOptions) {
+function mergeAnimationDetails(element, oldAnimation, newAnimation) {
+ var target = oldAnimation.options || {};
+ var newOptions = newAnimation.options || {};
+
var toAdd = (target.addClass || '') + ' ' + (newOptions.addClass || '');
var toRemove = (target.removeClass || '') + ' ' + (newOptions.removeClass ||
'');
var classes = resolveElementClasses(element.attr('class'), toAdd, toRemove);
@@ -252,6 +258,9 @@ function mergeAnimationOptions(element, target, newOptions)
{
target.removeClass = null;
}
+ oldAnimation.addClass = target.addClass;
+ oldAnimation.removeClass = target.removeClass;
+
return target;
}
@@ -373,12 +382,6 @@ function concatWithSpace(a,b) {
return a + ' ' + b;
}
-function $$BodyProvider() {
- this.$get = ['$document', function($document) {
- return jqLite($document[0].body);
- }];
-}
-
var $$rAFSchedulerFactory = ['$$rAF', function($$rAF) {
var queue, cancelFn;
@@ -393,7 +396,7 @@ var $$rAFSchedulerFactory = ['$$rAF', function($$rAF) {
queue = scheduler.queue = [];
/* waitUntilQuiet does two things:
- * 1. It will run the FINAL `fn` value only when an uncancelled RAF has
passed through
+ * 1. It will run the FINAL `fn` value only when an uncanceled RAF has
passed through
* 2. It will delay the next wave of tasks from running until the quiet `fn`
has run.
*
* The motivation here is that animation code can request more time from the
scheduler
@@ -428,16 +431,101 @@ var $$rAFSchedulerFactory = ['$$rAF', function($$rAF) {
}
}];
-var $$AnimateChildrenDirective = [function() {
- return function(scope, element, attrs) {
- var val = attrs.ngAnimateChildren;
- if (angular.isString(val) && val.length === 0) { //empty attribute
- element.data(NG_ANIMATE_CHILDREN_DATA, true);
- } else {
- attrs.$observe('ngAnimateChildren', function(value) {
+/**
+ * @ngdoc directive
+ * @name ngAnimateChildren
+ * @restrict AE
+ * @element ANY
+ *
+ * @description
+ *
+ * ngAnimateChildren allows you to specify that children of this element
should animate even if any
+ * 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.
+ *
+ * Note that even if `ngAnimteChildren` 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`,
+ * then child animations are allowed. If the value is `false`, child
animations are not allowed.
+ *
+ * @example
+ * <example module="ngAnimateChildren" name="ngAnimateChildren"
deps="angular-animate.js" animations="true">
+ <file name="index.html">
+ <div ng-controller="mainController as main">
+ <label>Show container? <input type="checkbox"
ng-model="main.enterElement" /></label>
+ <label>Animate children? <input type="checkbox"
ng-model="main.animateChildren" /></label>
+ <hr>
+ <div ng-animate-children="{{main.animateChildren}}">
+ <div ng-if="main.enterElement" class="container">
+ List of items:
+ <div ng-repeat="item in [0, 1, 2, 3]" class="item">Item
{{item}}</div>
+ </div>
+ </div>
+ </div>
+ </file>
+ <file name="animations.css">
+
+ .container.ng-enter,
+ .container.ng-leave {
+ transition: all ease 1.5s;
+ }
+
+ .container.ng-enter,
+ .container.ng-leave-active {
+ opacity: 0;
+ }
+
+ .container.ng-leave,
+ .container.ng-enter-active {
+ opacity: 1;
+ }
+
+ .item {
+ background: firebrick;
+ color: #FFF;
+ margin-bottom: 10px;
+ }
+
+ .item.ng-enter,
+ .item.ng-leave {
+ transition: transform 1.5s ease;
+ }
+
+ .item.ng-enter {
+ transform: translateX(50px);
+ }
+
+ .item.ng-enter-active {
+ transform: translateX(0);
+ }
+ </file>
+ <file name="script.js">
+ angular.module('ngAnimateChildren', ['ngAnimate'])
+ .controller('mainController', function() {
+ this.animateChildren = false;
+ this.enterElement = false;
+ });
+ </file>
+ </example>
+ */
+var $$AnimateChildrenDirective = ['$interpolate', function($interpolate) {
+ return {
+ link: function(scope, element, attrs) {
+ var val = attrs.ngAnimateChildren;
+ if (angular.isString(val) && val.length === 0) { //empty attribute
+ element.data(NG_ANIMATE_CHILDREN_DATA, true);
+ } else {
+ // Interpolate and set the value, so that it is available to
+ // animations that run right after compilation
+ setData($interpolate(val)(scope));
+ attrs.$observe('ngAnimateChildren', setData);
+ }
+
+ function setData(value) {
value = value === 'on' || value === 'true';
element.data(NG_ANIMATE_CHILDREN_DATA, value);
- });
+ }
}
};
}];
@@ -609,7 +697,7 @@ var ANIMATE_TIMER_KEY = '$$animateCss';
* ```
*
* To actually start the animation we need to run `animation.start()` which
will then return a promise that we can hook into to detect when the animation
ends.
- * If we choose not to run the animation then we MUST run `animation.end()` to
perform a cleanup on the element (since some CSS classes and stlyes may have
been
+ * If we choose not to run the animation then we MUST run `animation.end()` to
perform a cleanup on the element (since some CSS classes and styles may have
been
* applied to the element during the preparation phase). Note that all other
properties such as duration, delay, transitions and keyframes are just
properties
* and that changing them will not reconfigure the parameters of the animation.
*
@@ -628,8 +716,10 @@ var ANIMATE_TIMER_KEY = '$$animateCss';
*
* * `event` - The DOM event (e.g. enter, leave, move). When used, a generated
CSS class of `ng-EVENT` and `ng-EVENT-active` will be applied
* to the element during the animation. Multiple events can be provided when
spaces are used as a separator. (Note that this will not perform any DOM
operation.)
+ * * `structural` - Indicates that the `ng-` prefix will be added to the event
class. Setting to `false` or omitting will turn `ng-EVENT` and
+ * `ng-EVENT-active` in `EVENT` and `EVENT-active`. Unused if `event` is
omitted.
* * `easing` - The CSS easing value that will be applied to the transition or
keyframe animation (or both).
- * * `transition` - The raw CSS transition style that will be used (e.g. `1s
linear all`).
+ * * `transitionStyle` - The raw CSS transition style that will be used (e.g.
`1s linear all`).
* * `keyframeStyle` - The raw CSS keyframe animation style that will be used
(e.g. `1s my_animation linear`).
* * `from` - The starting CSS styles (a key/value object) that will be
applied at the start of the animation.
* * `to` - The ending CSS styles (a key/value object) that will be applied
across the animation via a CSS transition.
@@ -644,8 +734,12 @@ var ANIMATE_TIMER_KEY = '$$animateCss';
* * `stagger` - A numeric time value representing the delay between
successively animated elements
* ({@link ngAnimate#css-staggering-animations Click here to learn how
CSS-based staggering works in ngAnimate.})
* * `staggerIndex` - The numeric index representing the stagger item (e.g. a
value of 5 is equal to the sixth item in the stagger; therefore when a
- * * `stagger` option value of `0.1` is used then there will be a stagger
delay of `600ms`)
- * * `applyClassesEarly` - Whether or not the classes being added or removed
will be used when detecting the animation. This is set by `$animate` when
enter/leave/move animations are fired to ensure that the CSS classes are
resolved in time. (Note that this will prevent any transitions from occuring on
the classes being added and removed.)
+ * `stagger` option value of `0.1` is used then there will be a stagger
delay of `600ms`)
+ * * `applyClassesEarly` - Whether or not the classes being added or removed
will be used when detecting the animation. This is set by `$animate` when
enter/leave/move animations are fired to ensure that the CSS classes are
resolved in time. (Note that this will prevent any transitions from occurring
on the classes being added and removed.)
+ * * `cleanupStyles` - Whether or not the provided `from` and `to` styles will
be removed once
+ * the animation is closed. This is useful for when the styles are used
purely for the sake of
+ * the animation and do not have a lasting visual effect on the element
(e.g. a collapse and open animation).
+ * By default this value is set to `false`.
*
* @return {object} an object with start and end methods and details about the
animation.
*
@@ -697,7 +791,7 @@ function computeCssStyles($window, element, properties) {
}
// by setting this to null in the event that the delay is not set or is
set directly as 0
- // then we can still allow for zegative values to be used later on and
not mistake this
+ // then we can still allow for negative values to be used later on and
not mistake this
// value for being greater than any other negative value.
if (val === 0) {
val = null;
@@ -766,14 +860,31 @@ function createLocalCacheLookup() {
};
}
+// we do not reassign an already present style value since
+// if we detect the style property value again we may be
+// detecting styles that were added via the `from` styles.
+// We make use of `isDefined` here since an empty string
+// or null value (which is what getPropertyValue will return
+// for a non-existing style) will still be marked as a valid
+// 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
+// "OR" statement then it would not be enough to catch that.
+function registerRestorableStyles(backup, node, properties) {
+ forEach(properties, function(prop) {
+ backup[prop] = isDefined(backup[prop])
+ ? backup[prop]
+ : node.style.getPropertyValue(prop);
+ });
+}
+
var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
var gcsLookup = createLocalCacheLookup();
var gcsStaggerLookup = createLocalCacheLookup();
this.$get = ['$window', '$$jqLite', '$$AnimateRunner', '$timeout',
- '$$forceReflow', '$sniffer', '$$rAFScheduler', '$animate',
+ '$$forceReflow', '$sniffer', '$$rAFScheduler', '$$animateQueue',
function($window, $$jqLite, $$AnimateRunner, $timeout,
- $$forceReflow, $sniffer, $$rAFScheduler, $animate) {
+ $$forceReflow, $sniffer, $$rAFScheduler, $$animateQueue) {
var applyAnimationClasses = applyAnimationClassesFactory($$jqLite);
@@ -796,7 +907,7 @@ var $AnimateCssProvider = ['$animateProvider',
function($animateProvider) {
}
// we keep putting this in multiple times even though the value and the
cacheKey are the same
- // because we're keeping an interal 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);
return timings;
}
@@ -865,16 +976,24 @@ var $AnimateCssProvider = ['$animateProvider',
function($animateProvider) {
return timings;
}
- return function init(element, options) {
+ return function init(element, initialOptions) {
+ // all of the animation functions should create
+ // a copy of the options data, however, if a
+ // parent service has already created a copy then
+ // we should stick to using that
+ var options = initialOptions || {};
+ if (!options.$$prepared) {
+ options = prepareAnimationOptions(copy(options));
+ }
+
+ var restoreStyles = {};
var node = getDomNode(element);
if (!node
|| !node.parentNode
- || !$animate.enabled()) {
+ || !$$animateQueue.enabled()) {
return closeAndReturnNoopAnimator();
}
- options = prepareAnimationOptions(options);
-
var temporaryStyles = [];
var classes = element.attr('class');
var styles = packageStyles(options);
@@ -887,6 +1006,8 @@ var $AnimateCssProvider = ['$animateProvider',
function($animateProvider) {
var maxDelayTime;
var maxDuration;
var maxDurationTime;
+ var startTime;
+ var events = [];
if (options.duration === 0 || (!$sniffer.animations &&
!$sniffer.transitions)) {
return closeAndReturnNoopAnimator();
@@ -1040,7 +1161,12 @@ var $AnimateCssProvider = ['$animateProvider',
function($animateProvider) {
}
if (options.delay != null) {
- var delayStyle = parseFloat(options.delay);
+ var delayStyle;
+ if (typeof options.delay !== "boolean") {
+ delayStyle = parseFloat(options.delay);
+ // number in options.delay means we have to recalculate the delay
for the closing timeout
+ maxDelay = Math.max(delayStyle, 0);
+ }
if (flags.applyTransitionDelay) {
temporaryStyles.push(getCssDelayStyle(delayStyle));
@@ -1067,7 +1193,12 @@ var $AnimateCssProvider = ['$animateProvider',
function($animateProvider) {
stagger.animationDuration === 0;
}
- applyAnimationFromStyles(element, options);
+ if (options.from) {
+ if (options.cleanupStyles) {
+ registerRestorableStyles(restoreStyles, node,
Object.keys(options.from));
+ }
+ applyAnimationFromStyles(element, options);
+ }
if (flags.blockTransition || flags.blockKeyframeAnimation) {
applyBlocking(maxDuration);
@@ -1134,6 +1265,13 @@ var $AnimateCssProvider = ['$animateProvider',
function($animateProvider) {
applyAnimationClasses(element, options);
applyAnimationStyles(element, options);
+ if (Object.keys(restoreStyles).length) {
+ forEach(restoreStyles, function(value, prop) {
+ value ? node.style.setProperty(prop, value)
+ : node.style.removeProperty(prop);
+ });
+ }
+
// the reason why we have this option is to allow a synchronous
closing callback
// that is fired as SOON as the animation ends (when the CSS is
removed) or if
// the animation never takes off at all. A good example is a leave
animation since
@@ -1143,6 +1281,18 @@ var $AnimateCssProvider = ['$animateProvider',
function($animateProvider) {
options.onDone();
}
+ if (events && events.length) {
+ // Remove the transitionend / animationend listener(s)
+ element.off(events.join(' '), onAnimationProgress);
+ }
+
+ //Cancel the fallback closing timeout and remove the timer data
+ var animationTimerData = element.data(ANIMATE_TIMER_KEY);
+ if (animationTimerData) {
+ $timeout.cancel(animationTimerData[0].timer);
+ element.removeData(ANIMATE_TIMER_KEY);
+ }
+
// if the preparation function fails then the promise is not setup
if (runner) {
runner.complete(!rejected);
@@ -1178,6 +1328,33 @@ var $AnimateCssProvider = ['$animateProvider',
function($animateProvider) {
};
}
+ function onAnimationProgress(event) {
+ event.stopPropagation();
+ var ev = event.originalEvent || event;
+
+ // we now always use `Date.now()` due to the recent changes with
+ // event.timeStamp in Firefox, Webkit and Chrome (see #13494 for more
info)
+ var timeStamp = ev.$manualTimeStamp || Date.now();
+
+ /* Firefox (or possibly just Gecko) likes to not round values up
+ * when a ms measurement is used for the animation */
+ var elapsedTime =
parseFloat(ev.elapsedTime.toFixed(ELAPSED_TIME_MAX_DECIMAL_PLACES));
+
+ /* $manualTimeStamp is a mocked timeStamp value which is set
+ * within browserTrigger(). This is only here so that tests can
+ * mock animations properly. Real events fallback to event.timeStamp,
+ * or, if they don't, then a timeStamp is automatically created for
them.
+ * We're checking to see if the timeStamp surpasses the expected delay,
+ * but we're using elapsedTime instead of the timeStamp on the 2nd
+ * pre-condition since animationPauseds sometimes close off early */
+ if (Math.max(timeStamp - startTime, 0) >= maxDelayTime && elapsedTime
>= maxDuration) {
+ // we set this flag to ensure that if the transition is paused then,
when resumed,
+ // the animation will automatically close itself since transitions
cannot be paused.
+ animationCompleted = true;
+ close();
+ }
+ }
+
function start() {
if (animationClosed) return;
if (!node.parentNode) {
@@ -1185,8 +1362,6 @@ var $AnimateCssProvider = ['$animateProvider',
function($animateProvider) {
return;
}
- var startTime, events = [];
-
// even though we only pause keyframe animations here the pause flag
// will still happen when transitions are used. Only the transition
will
// not be paused since that is not possible. If the animation ends when
@@ -1206,9 +1381,9 @@ var $AnimateCssProvider = ['$animateProvider',
function($animateProvider) {
}
};
- // checking the stagger duration prevents an accidently cascade of the
CSS delay style
+ // checking the stagger duration prevents an accidentally cascade of
the CSS delay style
// being inherited from the parent. If the transition duration is zero
then we can safely
- // rely that the delay value is an intential stagger delay style.
+ // rely that the delay value is an intentional stagger delay style.
var maxStagger = itemIndex > 0
&& ((timings.transitionDuration &&
stagger.transitionDuration === 0) ||
(timings.animationDuration &&
stagger.animationDuration === 0))
@@ -1327,8 +1502,16 @@ var $AnimateCssProvider = ['$animateProvider',
function($animateProvider) {
element.data(ANIMATE_TIMER_KEY, animationsData);
}
- element.on(events.join(' '), onAnimationProgress);
- applyAnimationToStyles(element, options);
+ if (events.length) {
+ element.on(events.join(' '), onAnimationProgress);
+ }
+
+ if (options.to) {
+ if (options.cleanupStyles) {
+ registerRestorableStyles(restoreStyles, node,
Object.keys(options.to));
+ }
+ applyAnimationToStyles(element, options);
+ }
}
function onAnimationExpired() {
@@ -1344,30 +1527,6 @@ var $AnimateCssProvider = ['$animateProvider',
function($animateProvider) {
element.removeData(ANIMATE_TIMER_KEY);
}
}
-
- function onAnimationProgress(event) {
- event.stopPropagation();
- var ev = event.originalEvent || event;
- var timeStamp = ev.$manualTimeStamp || ev.timeStamp || Date.now();
-
- /* Firefox (or possibly just Gecko) likes to not round values up
- * when a ms measurement is used for the animation */
- var elapsedTime =
parseFloat(ev.elapsedTime.toFixed(ELAPSED_TIME_MAX_DECIMAL_PLACES));
-
- /* $manualTimeStamp is a mocked timeStamp value which is set
- * within browserTrigger(). This is only here so that tests can
- * mock animations properly. Real events fallback to event.timeStamp,
- * or, if they don't, then a timeStamp is automatically created for
them.
- * We're checking to see if the timeStamp surpasses the expected
delay,
- * but we're using elapsedTime instead of the timeStamp on the 2nd
- * pre-condition since animations sometimes close off early */
- if (Math.max(timeStamp - startTime, 0) >= maxDelayTime &&
elapsedTime >= maxDuration) {
- // we set this flag to ensure that if the transition is paused
then, when resumed,
- // the animation will automatically close itself since transitions
cannot be paused.
- animationCompleted = true;
- close();
- }
- }
}
};
}];
@@ -1382,16 +1541,25 @@ var $$AnimateCssDriverProvider =
['$$animationProvider', function($$animationPro
var NG_OUT_ANCHOR_CLASS_NAME = 'ng-anchor-out';
var NG_IN_ANCHOR_CLASS_NAME = 'ng-anchor-in';
- this.$get = ['$animateCss', '$rootScope', '$$AnimateRunner', '$rootElement',
'$$body', '$sniffer', '$$jqLite',
- function($animateCss, $rootScope, $$AnimateRunner, $rootElement,
$$body, $sniffer, $$jqLite) {
+ function isDocumentFragment(node) {
+ return node.parentNode && node.parentNode.nodeType === 11;
+ }
+
+ this.$get = ['$animateCss', '$rootScope', '$$AnimateRunner', '$rootElement',
'$sniffer', '$$jqLite', '$document',
+ function($animateCss, $rootScope, $$AnimateRunner, $rootElement,
$sniffer, $$jqLite, $document) {
// only browsers that support these properties can render animations
if (!$sniffer.animations && !$sniffer.transitions) return noop;
- var bodyNode = getDomNode($$body);
+ var bodyNode = $document[0].body;
var rootNode = getDomNode($rootElement);
- var rootBodyElement = jqLite(bodyNode.parentNode === rootNode ? bodyNode :
rootNode);
+ var rootBodyElement = jqLite(
+ // this is to avoid using something that exists outside of the body
+ // we also special case the doc fragment case because our unit test code
+ // appends the $rootElement to the body after the app has been
bootstrapped
+ isDocumentFragment(rootNode) || bodyNode.contains(rootNode) ? rootNode :
bodyNode
+ );
var applyAnimationClasses = applyAnimationClassesFactory($$jqLite);
@@ -1488,7 +1656,7 @@ var $$AnimateCssDriverProvider = ['$$animationProvider',
function($$animationPro
var coords = getDomNode(anchor).getBoundingClientRect();
// we iterate directly since safari messes up and doesn't return
- // all the keys for the coods object when iterated
+ // all the keys for the coords object when iterated
forEach(['width','height','top','left'], function(key) {
var value = coords[key];
switch (key) {
@@ -1643,6 +1811,8 @@ var $$AnimateJsProvider = ['$animateProvider',
function($animateProvider) {
var applyAnimationClasses = applyAnimationClassesFactory($$jqLite);
// $animateJs(element, 'enter');
return function(element, event, classes, options) {
+ var animationClosed = false;
+
// the `classes` argument is optional and if it is not used
// then the classes will be resolved from the element's className
// property as well as options.addClass/options.removeClass.
@@ -1695,8 +1865,32 @@ var $$AnimateJsProvider = ['$animateProvider',
function($animateProvider) {
applyAnimationClasses(element, options);
}
+ function close() {
+ animationClosed = true;
+ applyOptions();
+ applyAnimationStyles(element, options);
+ }
+
+ var runner;
+
return {
+ $$willAnimate: true,
+ end: function() {
+ if (runner) {
+ runner.end();
+ } else {
+ close();
+ runner = new $$AnimateRunner();
+ runner.complete(true);
+ }
+ return runner;
+ },
start: function() {
+ if (runner) {
+ return runner;
+ }
+
+ runner = new $$AnimateRunner();
var closeActiveAnimations;
var chain = [];
@@ -1721,8 +1915,7 @@ var $$AnimateJsProvider = ['$animateProvider',
function($animateProvider) {
});
}
- var animationClosed = false;
- var runner = new $$AnimateRunner({
+ runner.setHost({
end: function() {
endAnimations();
},
@@ -1735,9 +1928,7 @@ var $$AnimateJsProvider = ['$animateProvider',
function($animateProvider) {
return runner;
function onComplete(success) {
- animationClosed = true;
- applyOptions();
- applyAnimationStyles(element, options);
+ close(success);
runner.complete(success);
}
@@ -1957,6 +2148,7 @@ var NG_ANIMATE_PIN_DATA = '$ngAnimatePin';
var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
var PRE_DIGEST_STATE = 1;
var RUNNING_STATE = 2;
+ var ONE_SPACE = ' ';
var rules = this.rules = {
skip: [],
@@ -1964,28 +2156,50 @@ var $$AnimateQueueProvider = ['$animateProvider',
function($animateProvider) {
join: []
};
+ function makeTruthyCssClassMap(classString) {
+ if (!classString) {
+ return null;
+ }
+
+ var keys = classString.split(ONE_SPACE);
+ var map = Object.create(null);
+
+ forEach(keys, function(key) {
+ map[key] = true;
+ });
+ return map;
+ }
+
+ function hasMatchingClasses(newClassString, currentClassString) {
+ if (newClassString && currentClassString) {
+ var currentClassMap = makeTruthyCssClassMap(currentClassString);
+ return newClassString.split(ONE_SPACE).some(function(className) {
+ return currentClassMap[className];
+ });
+ }
+ }
+
function isAllowed(ruleType, element, currentAnimation, previousAnimation) {
return rules[ruleType].some(function(fn) {
return fn(element, currentAnimation, previousAnimation);
});
}
- function hasAnimationClasses(options, and) {
- options = options || {};
- var a = (options.addClass || '').length > 0;
- var b = (options.removeClass || '').length > 0;
+ function hasAnimationClasses(animation, and) {
+ var a = (animation.addClass || '').length > 0;
+ var b = (animation.removeClass || '').length > 0;
return and ? a && b : a || b;
}
rules.join.push(function(element, newAnimation, currentAnimation) {
// if the new animation is class-based then we can just tack that on
- return !newAnimation.structural &&
hasAnimationClasses(newAnimation.options);
+ return !newAnimation.structural && hasAnimationClasses(newAnimation);
});
rules.skip.push(function(element, newAnimation, currentAnimation) {
// there is no need to animate anything if no classes are being added and
// there is no structural animation that will be triggered
- return !newAnimation.structural &&
!hasAnimationClasses(newAnimation.options);
+ return !newAnimation.structural && !hasAnimationClasses(newAnimation);
});
rules.skip.push(function(element, newAnimation, currentAnimation) {
@@ -2011,22 +2225,46 @@ var $$AnimateQueueProvider = ['$animateProvider',
function($animateProvider) {
});
rules.cancel.push(function(element, newAnimation, currentAnimation) {
- var nO = newAnimation.options;
- var cO = currentAnimation.options;
+ var nA = newAnimation.addClass;
+ var nR = newAnimation.removeClass;
+ var cA = currentAnimation.addClass;
+ var cR = currentAnimation.removeClass;
+
+ // early detection to save the global CPU shortage :)
+ if ((isUndefined(nA) && isUndefined(nR)) || (isUndefined(cA) &&
isUndefined(cR))) {
+ return false;
+ }
- // if the exact same CSS class is added/removed then it's safe to cancel it
- return (nO.addClass && nO.addClass === cO.removeClass) || (nO.removeClass
&& nO.removeClass === cO.addClass);
+ return hasMatchingClasses(nA, cR) || hasMatchingClasses(nR, cA);
});
- this.$get = ['$$rAF', '$rootScope', '$rootElement', '$document', '$$body',
'$$HashMap',
+ this.$get = ['$$rAF', '$rootScope', '$rootElement', '$document', '$$HashMap',
'$$animation', '$$AnimateRunner', '$templateRequest',
'$$jqLite', '$$forceReflow',
- function($$rAF, $rootScope, $rootElement, $document, $$body,
$$HashMap,
+ function($$rAF, $rootScope, $rootElement, $document, $$HashMap,
$$animation, $$AnimateRunner, $templateRequest,
$$jqLite, $$forceReflow) {
var activeAnimationsLookup = new $$HashMap();
var disabledElementsLookup = new $$HashMap();
var animationsEnabled = null;
+ function postDigestTaskFactory() {
+ var postDigestCalled = false;
+ return function(fn) {
+ // we only issue a call to postDigest before
+ // it has first passed. This prevents any callbacks
+ // from not firing once the animation has completed
+ // since it will be out of the digest cycle.
+ if (postDigestCalled) {
+ fn();
+ } else {
+ $rootScope.$$postDigest(function() {
+ postDigestCalled = true;
+ fn();
+ });
+ }
+ };
+ }
+
// Wait until all directive and route-related templates are downloaded and
// compiled. The $templateRequest.totalPendingRequests variable keeps
track of
// all of the remote templates being currently downloaded. If there are no
@@ -2069,18 +2307,28 @@ var $$AnimateQueueProvider = ['$animateProvider',
function($animateProvider) {
var applyAnimationClasses = applyAnimationClassesFactory($$jqLite);
- function normalizeAnimationOptions(element, options) {
- return mergeAnimationOptions(element, options, {});
+ function normalizeAnimationDetails(element, animation) {
+ return mergeAnimationDetails(element, animation, {});
}
- function findCallbacks(element, event) {
+ // IE9-11 has no method "contains" in SVG element and in Node.prototype.
Bug #10259.
+ var contains = Node.prototype.contains || function(arg) {
+ // jshint bitwise: false
+ return this === arg || !!(this.compareDocumentPosition(arg) & 16);
+ // jshint bitwise: true
+ };
+
+ function findCallbacks(parent, element, event) {
var targetNode = getDomNode(element);
+ var targetParentNode = getDomNode(parent);
var matches = [];
var entries = callbackRegistry[event];
if (entries) {
forEach(entries, function(entry) {
- if (entry.node.contains(targetNode)) {
+ if (contains.call(entry.node, targetNode)) {
+ matches.push(entry.callback);
+ } else if (event === 'leave' && contains.call(entry.node,
targetParentNode)) {
matches.push(entry.callback);
}
});
@@ -2089,14 +2337,6 @@ var $$AnimateQueueProvider = ['$animateProvider',
function($animateProvider) {
return matches;
}
- function triggerCallback(event, element, phase, data) {
- $$rAF(function() {
- forEach(findCallbacks(element, event), function(callback) {
- callback(element, phase, data);
- });
- });
- }
-
return {
on: function(event, container, callback) {
var node = extractElementNode(container);
@@ -2163,12 +2403,7 @@ var $$AnimateQueueProvider = ['$animateProvider',
function($animateProvider) {
bool = !recordExists;
} else {
// (element, bool) - Element setter
- bool = !!bool;
- if (!bool) {
- disabledElementsLookup.put(node, true);
- } else if (recordExists) {
- disabledElementsLookup.remove(node);
- }
+ disabledElementsLookup.put(node, !bool);
}
}
}
@@ -2177,7 +2412,12 @@ var $$AnimateQueueProvider = ['$animateProvider',
function($animateProvider) {
}
};
- function queueAnimation(element, event, options) {
+ function queueAnimation(element, event, initialOptions) {
+ // we always make a copy of the options since
+ // there should never be any side effects on
+ // the input data when running `$animateCss`.
+ var options = copy(initialOptions);
+
var node, parent;
element = stripCommentsFromElement(element);
if (element) {
@@ -2191,6 +2431,9 @@ var $$AnimateQueueProvider = ['$animateProvider',
function($animateProvider) {
// These methods will become available after the digest has passed
var runner = new $$AnimateRunner();
+ // this is used to trigger callbacks in postDigest mode
+ var runInNextPostDigestOrNow = postDigestTaskFactory();
+
if (isArray(options.addClass)) {
options.addClass = options.addClass.join(' ');
}
@@ -2234,7 +2477,9 @@ var $$AnimateQueueProvider = ['$animateProvider',
function($animateProvider) {
// this is a hard disable of all animations for the application or on
// the element itself, therefore there is no need to continue further
// past this point if not enabled
- var skipAnimations = !animationsEnabled ||
disabledElementsLookup.get(node);
+ // 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
+ var skipAnimations = !animationsEnabled || $document[0].hidden ||
disabledElementsLookup.get(node);
var existingAnimation = (!skipAnimations &&
activeAnimationsLookup.get(node)) || {};
var hasExistingAnimation = !!existingAnimation.state;
@@ -2257,6 +2502,8 @@ var $$AnimateQueueProvider = ['$animateProvider',
function($animateProvider) {
structural: isStructural,
element: element,
event: event,
+ addClass: options.addClass,
+ removeClass: options.removeClass,
close: close,
options: options,
runner: runner
@@ -2269,11 +2516,10 @@ var $$AnimateQueueProvider = ['$animateProvider',
function($animateProvider) {
close();
return runner;
} else {
- mergeAnimationOptions(element, existingAnimation.options, options);
+ mergeAnimationDetails(element, existingAnimation, newAnimation);
return existingAnimation.runner;
}
}
-
var cancelAnimationFlag = isAllowed('cancel', element, newAnimation,
existingAnimation);
if (cancelAnimationFlag) {
if (existingAnimation.state === RUNNING_STATE) {
@@ -2288,7 +2534,8 @@ var $$AnimateQueueProvider = ['$animateProvider',
function($animateProvider) {
existingAnimation.close();
} else {
// this will merge the new animation options into existing
animation options
- mergeAnimationOptions(element, existingAnimation.options,
newAnimation.options);
+ mergeAnimationDetails(element, existingAnimation, newAnimation);
+
return existingAnimation.runner;
}
} else {
@@ -2298,12 +2545,12 @@ var $$AnimateQueueProvider = ['$animateProvider',
function($animateProvider) {
var joinAnimationFlag = isAllowed('join', element, newAnimation,
existingAnimation);
if (joinAnimationFlag) {
if (existingAnimation.state === RUNNING_STATE) {
- normalizeAnimationOptions(element, options);
+ normalizeAnimationDetails(element, newAnimation);
} else {
applyGeneratedPreparationClasses(element, isStructural ? event :
null, options);
event = newAnimation.event = existingAnimation.event;
- options = mergeAnimationOptions(element,
existingAnimation.options, newAnimation.options);
+ options = mergeAnimationDetails(element, existingAnimation,
newAnimation);
//we return the same runner since only the option values of this
animation will
//be fed into the `existingAnimation`.
@@ -2314,7 +2561,7 @@ var $$AnimateQueueProvider = ['$animateProvider',
function($animateProvider) {
} else {
// normalization in this case means that it removes redundant CSS
classes that
// already exist (addClass) or do not exist (removeClass) on the
element
- normalizeAnimationOptions(element, options);
+ normalizeAnimationDetails(element, newAnimation);
}
// when the options are merged and cleaned up we may end up not having
to do
@@ -2324,7 +2571,7 @@ var $$AnimateQueueProvider = ['$animateProvider',
function($animateProvider) {
if (!isValidAnimation) {
// animate (from/to) can be quickly checked first, otherwise we check
if any classes are present
isValidAnimation = (newAnimation.event === 'animate' &&
Object.keys(newAnimation.options.to || {}).length > 0)
- || hasAnimationClasses(newAnimation.options);
+ || hasAnimationClasses(newAnimation);
}
if (!isValidAnimation) {
@@ -2354,7 +2601,7 @@ var $$AnimateQueueProvider = ['$animateProvider',
function($animateProvider) {
var isValidAnimation = parentElement.length > 0
&& (animationDetails.event === 'animate'
|| animationDetails.structural
- ||
hasAnimationClasses(animationDetails.options));
+ || hasAnimationClasses(animationDetails));
// this means that the previous animation was cancelled
// even if the follow-up animation is the same event
@@ -2386,7 +2633,7 @@ var $$AnimateQueueProvider = ['$animateProvider',
function($animateProvider) {
// this combined multiple class to addClass / removeClass into a
setClass event
// so long as a structural event did not take over the animation
- event = !animationDetails.structural &&
hasAnimationClasses(animationDetails.options, true)
+ event = !animationDetails.structural &&
hasAnimationClasses(animationDetails, true)
? 'setClass'
: animationDetails.event;
@@ -2411,7 +2658,20 @@ var $$AnimateQueueProvider = ['$animateProvider',
function($animateProvider) {
return runner;
function notifyProgress(runner, event, phase, data) {
- triggerCallback(event, element, phase, data);
+ runInNextPostDigestOrNow(function() {
+ var callbacks = findCallbacks(parent, element, event);
+ if (callbacks.length) {
+ // do not optimize this call here to RAF because
+ // we don't know how heavy the callback code here will
+ // be and if this code is buffered then this can
+ // lead to a performance regression.
+ $$rAF(function() {
+ forEach(callbacks, function(callback) {
+ callback(element, phase, data);
+ });
+ });
+ }
+ });
runner.progress(event, phase, data);
}
@@ -2430,15 +2690,15 @@ var $$AnimateQueueProvider = ['$animateProvider',
function($animateProvider) {
forEach(children, function(child) {
var state = parseInt(child.getAttribute(NG_ANIMATE_ATTR_NAME));
var animationDetails = activeAnimationsLookup.get(child);
- switch (state) {
- case RUNNING_STATE:
- animationDetails.runner.end();
- /* falls through */
- case PRE_DIGEST_STATE:
- if (animationDetails) {
+ if (animationDetails) {
+ switch (state) {
+ case RUNNING_STATE:
+ animationDetails.runner.end();
+ /* falls through */
+ case PRE_DIGEST_STATE:
activeAnimationsLookup.remove(child);
- }
- break;
+ break;
+ }
}
});
}
@@ -2453,11 +2713,20 @@ var $$AnimateQueueProvider = ['$animateProvider',
function($animateProvider) {
return getDomNode(nodeOrElmA) === getDomNode(nodeOrElmB);
}
+ /**
+ * This fn returns false if any of the following is true:
+ * a) animations on any parent element are disabled, and animations on the
element aren't explicitly allowed
+ * b) a parent element has an ongoing structural animation, and
animateChildren is false
+ * c) the element is not a child of the body
+ * d) the element is not a child of the $rootElement
+ */
function areAnimationsAllowed(element, parentElement, event) {
- var bodyElementDetected = isMatchingElement(element, $$body) ||
element[0].nodeName === 'HTML';
+ var bodyElement = jqLite($document[0].body);
+ var bodyElementDetected = isMatchingElement(element, bodyElement) ||
element[0].nodeName === 'HTML';
var rootElementDetected = isMatchingElement(element, $rootElement);
var parentAnimationDetected = false;
var animateChildren;
+ var elementDisabled = disabledElementsLookup.get(getDomNode(element));
var parentHost = element.data(NG_ANIMATE_PIN_DATA);
if (parentHost) {
@@ -2482,7 +2751,18 @@ var $$AnimateQueueProvider = ['$animateProvider',
function($animateProvider) {
// therefore we can't allow any animations to take place
// but if a parent animation is class-based then that's ok
if (!parentAnimationDetected) {
- parentAnimationDetected = details.structural ||
disabledElementsLookup.get(parentNode);
+ var parentElementDisabled = disabledElementsLookup.get(parentNode);
+
+ if (parentElementDisabled === true && elementDisabled !== false) {
+ // disable animations if the user hasn't explicitly enabled
animations on the
+ // current element
+ elementDisabled = true;
+ // element is disabled via parent element, no need to check
anything else
+ break;
+ } else if (parentElementDisabled === false) {
+ elementDisabled = false;
+ }
+ parentAnimationDetected = details.structural;
}
if (isUndefined(animateChildren) || animateChildren === true) {
@@ -2495,28 +2775,32 @@ var $$AnimateQueueProvider = ['$animateProvider',
function($animateProvider) {
// there is no need to continue traversing at this point
if (parentAnimationDetected && animateChildren === false) break;
- 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
- rootElementDetected = isMatchingElement(parentElement, $rootElement);
- if (!rootElementDetected) {
- parentHost = parentElement.data(NG_ANIMATE_PIN_DATA);
- if (parentHost) {
- parentElement = parentHost;
- }
- }
- }
-
if (!bodyElementDetected) {
- // we also need to ensure that the element is or will be apart 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
- bodyElementDetected = isMatchingElement(parentElement, $$body);
+ bodyElementDetected = isMatchingElement(parentElement, bodyElement);
+ }
+
+ if (bodyElementDetected && rootElementDetected) {
+ // If both body and root have been found, any other checks are
pointless,
+ // as no animation data should live outside the application
+ break;
+ }
+
+ if (!rootElementDetected) {
+ // If no rootElement is detected, check if the parentElement is
pinned to another element
+ parentHost = parentElement.data(NG_ANIMATE_PIN_DATA);
+ if (parentHost) {
+ // The pin target element becomes the next parent element
+ parentElement = parentHost;
+ continue;
+ }
}
parentElement = parentElement.parent();
}
- var allowAnimation = !parentAnimationDetected || animateChildren;
+ var allowAnimation = (!parentAnimationDetected || animateChildren) &&
elementDisabled !== true;
return allowAnimation && rootElementDetected && bodyElementDetected;
}
@@ -2536,171 +2820,6 @@ var $$AnimateQueueProvider = ['$animateProvider',
function($animateProvider) {
}];
}];
-var $$AnimateAsyncRunFactory = ['$$rAF', function($$rAF) {
- var waitQueue = [];
-
- function waitForTick(fn) {
- waitQueue.push(fn);
- if (waitQueue.length > 1) return;
- $$rAF(function() {
- for (var i = 0; i < waitQueue.length; i++) {
- waitQueue[i]();
- }
- waitQueue = [];
- });
- }
-
- return function() {
- var passed = false;
- waitForTick(function() {
- passed = true;
- });
- return function(callback) {
- passed ? callback() : waitForTick(callback);
- };
- };
-}];
-
-var $$AnimateRunnerFactory = ['$q', '$sniffer', '$$animateAsyncRun',
- function($q, $sniffer, $$animateAsyncRun) {
-
- var INITIAL_STATE = 0;
- var DONE_PENDING_STATE = 1;
- var DONE_COMPLETE_STATE = 2;
-
- AnimateRunner.chain = function(chain, callback) {
- var index = 0;
-
- next();
- function next() {
- if (index === chain.length) {
- callback(true);
- return;
- }
-
- chain[index](function(response) {
- if (response === false) {
- callback(false);
- return;
- }
- index++;
- next();
- });
- }
- };
-
- AnimateRunner.all = function(runners, callback) {
- var count = 0;
- var status = true;
- forEach(runners, function(runner) {
- runner.done(onProgress);
- });
-
- function onProgress(response) {
- status = status && response;
- if (++count === runners.length) {
- callback(status);
- }
- }
- };
-
- function AnimateRunner(host) {
- this.setHost(host);
-
- this._doneCallbacks = [];
- this._runInAnimationFrame = $$animateAsyncRun();
- this._state = 0;
- }
-
- AnimateRunner.prototype = {
- setHost: function(host) {
- this.host = host || {};
- },
-
- done: function(fn) {
- if (this._state === DONE_COMPLETE_STATE) {
- fn();
- } else {
- this._doneCallbacks.push(fn);
- }
- },
-
- progress: noop,
-
- getPromise: function() {
- if (!this.promise) {
- var self = this;
- this.promise = $q(function(resolve, reject) {
- self.done(function(status) {
- status === false ? reject() : resolve();
- });
- });
- }
- return this.promise;
- },
-
- then: function(resolveHandler, rejectHandler) {
- return this.getPromise().then(resolveHandler, rejectHandler);
- },
-
- 'catch': function(handler) {
- return this.getPromise()['catch'](handler);
- },
-
- 'finally': function(handler) {
- return this.getPromise()['finally'](handler);
- },
-
- pause: function() {
- if (this.host.pause) {
- this.host.pause();
- }
- },
-
- resume: function() {
- if (this.host.resume) {
- this.host.resume();
- }
- },
-
- end: function() {
- if (this.host.end) {
- this.host.end();
- }
- this._resolve(true);
- },
-
- cancel: function() {
- if (this.host.cancel) {
- this.host.cancel();
- }
- this._resolve(false);
- },
-
- complete: function(response) {
- var self = this;
- if (self._state === INITIAL_STATE) {
- self._state = DONE_PENDING_STATE;
- self._runInAnimationFrame(function() {
- self._resolve(response);
- });
- }
- },
-
- _resolve: function(response) {
- if (this._state !== DONE_COMPLETE_STATE) {
- forEach(this._doneCallbacks, function(fn) {
- fn(response);
- });
- this._doneCallbacks.length = 0;
- this._state = DONE_COMPLETE_STATE;
- }
- }
- };
-
- return AnimateRunner;
-}];
-
var $$AnimationProvider = ['$animateProvider', function($animateProvider) {
var NG_ANIMATE_REF_ATTR = 'ng-animate-ref';
@@ -2836,6 +2955,12 @@ var $$AnimationProvider = ['$animateProvider',
function($animateProvider) {
options.tempClasses = null;
}
+ var prepareClassName;
+ if (isStructural) {
+ prepareClassName = 'ng-' + event + PREPARE_CLASS_SUFFIX;
+ $$jqLite.addClass(element, prepareClassName);
+ }
+
animationQueue.push({
// this data is used by the postDigest code and passed into
// the driver step function
@@ -3000,7 +3125,7 @@ var $$AnimationProvider = ['$animateProvider',
function($animateProvider) {
};
// the anchor animations require that the from and to elements
both have at least
- // one shared CSS class which effictively marries the two elements
together to use
+ // one shared CSS class which effectively marries the two elements
together to use
// the same animation driver and to properly sequence the anchor
animation.
if (group.classes.length) {
preparedAnimations.push(group);
@@ -3058,6 +3183,10 @@ var $$AnimationProvider = ['$animateProvider',
function($animateProvider) {
if (tempClasses) {
$$jqLite.addClass(element, tempClasses);
}
+ if (prepareClassName) {
+ $$jqLite.removeClass(element, prepareClassName);
+ prepareClassName = null;
+ }
}
function updateAnimationRunners(animation, newRunner) {
@@ -3099,13 +3228,121 @@ var $$AnimationProvider = ['$animateProvider',
function($animateProvider) {
}];
}];
+/**
+ * @ngdoc directive
+ * @name ngAnimateSwap
+ * @restrict A
+ * @scope
+ *
+ * @description
+ *
+ * ngAnimateSwap is a animation-oriented directive that allows for the
container to
+ * be removed and entered in whenever the associated expression changes. A
+ * common usecase for this directive is a rotating banner component which
+ * contains one image being present at a time. When the active image changes
+ * then the old image will perform a `leave` animation and the new element
+ * will be inserted via an `enter` animation.
+ *
+ * @example
+ * <example name="ngAnimateSwap-directive" module="ngAnimateSwapExample"
+ * deps="angular-animate.js"
+ * animations="true" fixBase="true">
+ * <file name="index.html">
+ * <div class="container" ng-controller="AppCtrl">
+ * <div ng-animate-swap="number" class="cell swap-animation"
ng-class="colorClass(number)">
+ * {{ number }}
+ * </div>
+ * </div>
+ * </file>
+ * <file name="script.js">
+ * angular.module('ngAnimateSwapExample', ['ngAnimate'])
+ * .controller('AppCtrl', ['$scope', '$interval', function($scope,
$interval) {
+ * $scope.number = 0;
+ * $interval(function() {
+ * $scope.number++;
+ * }, 1000);
+ *
+ * var colors = ['red','blue','green','yellow','orange'];
+ * $scope.colorClass = function(number) {
+ * return colors[number % colors.length];
+ * };
+ * }]);
+ * </file>
+ * <file name="animations.css">
+ * .container {
+ * height:250px;
+ * width:250px;
+ * position:relative;
+ * overflow:hidden;
+ * border:2px solid black;
+ * }
+ * .container .cell {
+ * font-size:150px;
+ * text-align:center;
+ * line-height:250px;
+ * position:absolute;
+ * top:0;
+ * left:0;
+ * right:0;
+ * border-bottom:2px solid black;
+ * }
+ * .swap-animation.ng-enter, .swap-animation.ng-leave {
+ * transition:0.5s linear all;
+ * }
+ * .swap-animation.ng-enter {
+ * top:-250px;
+ * }
+ * .swap-animation.ng-enter-active {
+ * top:0px;
+ * }
+ * .swap-animation.ng-leave {
+ * top:0px;
+ * }
+ * .swap-animation.ng-leave-active {
+ * top:250px;
+ * }
+ * .red { background:red; }
+ * .green { background:green; }
+ * .blue { background:blue; }
+ * .yellow { background:yellow; }
+ * .orange { background:orange; }
+ * </file>
+ * </example>
+ */
+var ngAnimateSwapDirective = ['$animate', '$rootScope', function($animate,
$rootScope) {
+ return {
+ restrict: 'A',
+ transclude: 'element',
+ terminal: true,
+ priority: 600, // we use 600 here to ensure that the directive is caught
before others
+ link: function(scope, $element, attrs, ctrl, $transclude) {
+ var previousElement, previousScope;
+ scope.$watchCollection(attrs.ngAnimateSwap || attrs['for'],
function(value) {
+ if (previousElement) {
+ $animate.leave(previousElement);
+ }
+ if (previousScope) {
+ previousScope.$destroy();
+ previousScope = null;
+ }
+ if (value || value === 0) {
+ previousScope = scope.$new();
+ $transclude(previousScope, function(element) {
+ previousElement = element;
+ $animate.enter(element, null, $element);
+ });
+ }
+ });
+ }
+ };
+}];
+
/* global angularAnimateModule: true,
- $$BodyProvider,
+ ngAnimateSwapDirective,
$$AnimateAsyncRunFactory,
$$rAFSchedulerFactory,
$$AnimateChildrenDirective,
- $$AnimateRunnerFactory,
$$AnimateQueueProvider,
$$AnimationProvider,
$AnimateCssProvider,
@@ -3354,11 +3591,39 @@ var $$AnimationProvider = ['$animateProvider',
function($animateProvider) {
* the CSS class once an animation has completed.)
*
*
+ * ### The `ng-[event]-prepare` class
+ *
+ * This is a special class that can be used to prevent unwanted flickering /
flash of content before
+ * the actual animation starts. The class is added as soon as an animation is
initialized, but removed
+ * before the actual animation starts (after waiting for a $digest).
+ * It is also only added for *structural* animations (`enter`, `move`, and
`leave`).
+ *
+ * In practice, flickering can appear when nesting elements with structural
animations such as `ngIf`
+ * into elements that have class-based animations such as `ngClass`.
+ *
+ * ```html
+ * <div ng-class="{red: myProp}">
+ * <div ng-class="{blue: myProp}">
+ * <div class="message" ng-if="myProp"></div>
+ * </div>
+ * </div>
+ * ```
+ *
+ * It is possible that during the `enter` animation, the `.message` div will
be briefly visible before it starts animating.
+ * In that case, you can add styles to the CSS that make sure the element
stays hidden before the animation starts:
+ *
+ * ```css
+ * .message.ng-enter-prepare {
+ * opacity: 0;
+ * }
+ *
+ * ```
+ *
* ## 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
* CSS class that is referenced in our HTML code) but in addition we need to
register the JavaScript animation on the module. By making use of the
- * `module.animation()` module function we can register the ainmation.
+ * `module.animation()` module function we can register the animation.
*
* Let's see an example of a enter/leave animation using `ngRepeat`:
*
@@ -3390,7 +3655,7 @@ var $$AnimationProvider = ['$animateProvider',
function($animateProvider) {
* jQuery(element).fadeOut(1000, doneFn);
* }
* }
- * }]
+ * }]);
* ```
*
* The nice thing about JS-based animations is that we can inject other
services and make use of advanced animation libraries such as
@@ -3421,7 +3686,7 @@ var $$AnimationProvider = ['$animateProvider',
function($animateProvider) {
* // do some cool animation and call the doneFn
* }
* }
- * }]
+ * }]);
* ```
*
* ## CSS + JS Animations Together
@@ -3443,7 +3708,7 @@ var $$AnimationProvider = ['$animateProvider',
function($animateProvider) {
* jQuery(element).slideIn(1000, doneFn);
* }
* }
- * }]
+ * }]);
* ```
*
* ```css
@@ -3463,16 +3728,15 @@ var $$AnimationProvider = ['$animateProvider',
function($animateProvider) {
* ```js
* myModule.animation('.slide', ['$animateCss', function($animateCss) {
* return {
- * enter: function(element, doneFn) {
+ * enter: function(element) {
* // this will trigger `.slide.ng-enter` and `.slide.ng-enter-active`.
- * var runner = $animateCss(element, {
+ * return $animateCss(element, {
* event: 'enter',
* structural: true
- * }).start();
-* runner.done(doneFn);
+ * });
* }
* }
- * }]
+ * }]);
* ```
*
* The nice thing here is that we can save bandwidth by sticking to our
CSS-based animation code and we don't need to rely on a 3rd-party animation
framework.
@@ -3484,19 +3748,17 @@ var $$AnimationProvider = ['$animateProvider',
function($animateProvider) {
* ```js
* myModule.animation('.slide', ['$animateCss', function($animateCss) {
* return {
- * enter: function(element, doneFn) {
- * var runner = $animateCss(element, {
+ * enter: function(element) {
+ * return $animateCss(element, {
* event: 'enter',
* structural: true,
* addClass: 'maroon-setting',
* from: { height:0 },
* to: { height: 200 }
- * }).start();
- *
- * runner.done(doneFn);
+ * });
* }
* }
- * }]
+ * }]);
* ```
*
* Now we can fill in the rest via our transition CSS code:
@@ -3841,14 +4103,11 @@ var $$AnimationProvider = ['$animateProvider',
function($animateProvider) {
* Click here {@link ng.$animate to learn more about animations with
`$animate`}.
*/
angular.module('ngAnimate', [])
- .provider('$$body', $$BodyProvider)
+ .directive('ngAnimateSwap', ngAnimateSwapDirective)
.directive('ngAnimateChildren', $$AnimateChildrenDirective)
.factory('$$rAFScheduler', $$rAFSchedulerFactory)
- .factory('$$AnimateRunner', $$AnimateRunnerFactory)
- .factory('$$animateAsyncRun', $$AnimateAsyncRunFactory)
-
.provider('$$animateQueue', $$AnimateQueueProvider)
.provider('$$animation', $$AnimationProvider)