diff --git a/Gemfile.lock b/Gemfile.lock
index 023dce76..bf9cb8ec 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -1,26 +1,26 @@
GEM
remote: https://rubygems.org/
specs:
- coderay (1.1.0)
+ coderay (1.1.1)
method_source (0.8.2)
- pry (0.10.1)
+ pry (0.10.4)
coderay (~> 1.1.0)
method_source (~> 0.8.1)
slop (~> 3.4)
- puma (2.13.4)
+ puma (3.6.0)
rack (1.6.4)
rack-protection (1.5.3)
rack
rack-rewrite (1.5.1)
- rake (10.4.2)
- sinatra (1.4.6)
- rack (~> 1.4)
+ rake (11.2.2)
+ sinatra (1.4.7)
+ rack (~> 1.5)
rack-protection (~> 1.4)
tilt (>= 1.3, < 3)
sinatra-param (1.4.0)
sinatra (~> 1.3)
slop (3.6.0)
- tilt (2.0.1)
+ tilt (2.0.5)
PLATFORMS
ruby
@@ -34,4 +34,4 @@ DEPENDENCIES
sinatra-param
BUNDLED WITH
- 1.10.6
+ 1.12.5
diff --git a/public/app/bower_components/angular-animate/.bower.json b/public/app/bower_components/angular-animate/.bower.json
index 97350be9..e194c8a9 100644
--- a/public/app/bower_components/angular-animate/.bower.json
+++ b/public/app/bower_components/angular-animate/.bower.json
@@ -1,19 +1,20 @@
{
"name": "angular-animate",
- "version": "1.4.5",
+ "version": "1.5.8",
+ "license": "MIT",
"main": "./angular-animate.js",
"ignore": [],
"dependencies": {
- "angular": "1.4.5"
+ "angular": "1.5.8"
},
"homepage": "https://github.com/angular/bower-angular-animate",
- "_release": "1.4.5",
+ "_release": "1.5.8",
"_resolution": {
"type": "version",
- "tag": "v1.4.5",
- "commit": "830538c7b2faf061471823bbeeb91f86ec54a52b"
+ "tag": "v1.5.8",
+ "commit": "688b68844cf95420e1793327f69d0c25589c23d1"
},
- "_source": "git://github.com/angular/bower-angular-animate.git",
- "_target": "^1.3.0 || >1.4.0-beta.0",
+ "_source": "https://github.com/angular/bower-angular-animate.git",
+ "_target": "^1.4.8",
"_originalSource": "angular-animate"
}
\ No newline at end of file
diff --git a/public/app/bower_components/angular-animate/LICENSE.md b/public/app/bower_components/angular-animate/LICENSE.md
new file mode 100644
index 00000000..2c395eef
--- /dev/null
+++ b/public/app/bower_components/angular-animate/LICENSE.md
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2016 Angular
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/public/app/bower_components/angular-animate/angular-animate.js b/public/app/bower_components/angular-animate/angular-animate.js
index b19f02de..b18b828e 100644
--- a/public/app/bower_components/angular-animate/angular-animate.js
+++ b/public/app/bower_components/angular-animate/angular-animate.js
@@ -1,22 +1,9 @@
/**
- * @license AngularJS v1.4.5
- * (c) 2010-2015 Google, Inc. http://angularjs.org
+ * @license AngularJS v1.5.8
+ * (c) 2010-2016 Google, Inc. http://angularjs.org
* License: MIT
*/
-(function(window, angular, undefined) {'use strict';
-
-/* jshint ignore:start */
-var noop = angular.noop;
-var extend = angular.extend;
-var jqLite = angular.element;
-var forEach = angular.forEach;
-var isArray = angular.isArray;
-var isString = angular.isString;
-var isObject = angular.isObject;
-var isUndefined = angular.isUndefined;
-var isDefined = angular.isDefined;
-var isFunction = angular.isFunction;
-var isElement = angular.isElement;
+(function(window, angular) {'use strict';
var ELEMENT_NODE = 1;
var COMMENT_NODE = 8;
@@ -25,6 +12,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 +29,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 ((window.ontransitionend === void 0) && (window.onwebkittransitionend !== void 0)) {
CSS_PREFIX = '-webkit-';
TRANSITION_PROP = 'WebkitTransition';
TRANSITIONEND_EVENT = 'webkitTransitionEnd transitionend';
@@ -50,7 +38,7 @@ if (window.ontransitionend === undefined && window.onwebkittransitionend !== und
TRANSITIONEND_EVENT = 'transitionend';
}
-if (window.onanimationend === undefined && window.onwebkitanimationend !== undefined) {
+if ((window.onanimationend === void 0) && (window.onwebkitanimationend !== void 0)) {
CSS_PREFIX = '-webkit-';
ANIMATION_PROP = 'WebkitAnimation';
ANIMATIONEND_EVENT = 'webkitAnimationEnd animationend';
@@ -72,10 +60,7 @@ var ANIMATION_DURATION_PROP = ANIMATION_PROP + DURATION_KEY;
var TRANSITION_DELAY_PROP = TRANSITION_PROP + DELAY_KEY;
var TRANSITION_DURATION_PROP = TRANSITION_PROP + DURATION_KEY;
-var isPromiseLike = function(p) {
- return p && p.then ? true : false;
-};
-
+var ngMinErr = angular.$$minErr('ng');
function assertArg(arg, name, reason) {
if (!arg) {
throw ngMinErr('areq', "Argument '{0}' is {1}", (name || '?'), (reason || "required"));
@@ -129,8 +114,7 @@ function stripCommentsFromElement(element) {
if (element instanceof jqLite) {
switch (element.length) {
case 0:
- return [];
- break;
+ return element;
case 1:
// there is no point of stripping anything if the element
@@ -143,7 +127,6 @@ function stripCommentsFromElement(element) {
default:
return jqLite(extractElementNode(element));
- break;
}
}
@@ -184,7 +167,7 @@ function applyAnimationClassesFactory($$jqLite) {
$$removeClass($$jqLite, element, options.removeClass);
options.removeClass = null;
}
- }
+ };
}
function prepareAnimationOptions(options) {
@@ -220,7 +203,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 +238,9 @@ function mergeAnimationOptions(element, target, newOptions) {
target.removeClass = null;
}
+ oldAnimation.addClass = target.addClass;
+ oldAnimation.removeClass = target.removeClass;
+
return target;
}
@@ -281,10 +270,10 @@ function resolveElementClasses(existing, toAdd, toRemove) {
var prop, allow;
if (val === ADD_CLASS) {
prop = 'addClass';
- allow = !existing[klass];
+ allow = !existing[klass] || existing[klass + REMOVE_CLASS_SUFFIX];
} else if (val === REMOVE_CLASS) {
prop = 'removeClass';
- allow = existing[klass];
+ allow = existing[klass] || existing[klass + ADD_CLASS_SUFFIX];
}
if (allow) {
if (classes[prop].length) {
@@ -314,7 +303,7 @@ function resolveElementClasses(existing, toAdd, toRemove) {
}
function getDomNode(element) {
- return (element instanceof angular.element) ? element[0] : element;
+ return (element instanceof jqLite) ? element[0] : element;
}
function applyGeneratedPreparationClasses(element, event, options) {
@@ -373,12 +362,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 +376,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 +411,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
+ *
+
+
+
+
+
+
+
+ List of items:
+
Item {{item}}
+
+
+
+
+
+
+ .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);
+ }
+
+
+ angular.module('ngAnimateChildren', ['ngAnimate'])
+ .controller('mainController', function() {
+ this.animateChildren = false;
+ this.enterElement = false;
+ });
+
+
+ */
+var $$AnimateChildrenDirective = ['$interpolate', function($interpolate) {
+ return {
+ link: function(scope, element, attrs) {
+ var val = attrs.ngAnimateChildren;
+ if (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 +677,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 +696,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 +714,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 +771,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 +840,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 +887,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 +956,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 +986,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 +1141,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 +1173,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 +1245,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 +1261,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 +1308,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 +1342,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 +1361,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 +1482,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 +1507,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 +1521,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 +1636,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 +1791,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 +1845,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 +1895,7 @@ var $$AnimateJsProvider = ['$animateProvider', function($animateProvider) {
});
}
- var animationClosed = false;
- var runner = new $$AnimateRunner({
+ runner.setHost({
end: function() {
endAnimations();
},
@@ -1735,9 +1908,7 @@ var $$AnimateJsProvider = ['$animateProvider', function($animateProvider) {
return runner;
function onComplete(success) {
- animationClosed = true;
- applyOptions();
- applyAnimationStyles(element, options);
+ close(success);
runner.complete(success);
}
@@ -1957,6 +2128,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 +2136,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 +2205,51 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
});
rules.cancel.push(function(element, newAnimation, currentAnimation) {
- var nO = newAnimation.options;
- var cO = currentAnimation.options;
+ // cancel the animation if classes added / removed in both animation cancel each other out,
+ // but only if the current animation isn't structural
- // 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);
+ if (currentAnimation.structural) return false;
+
+ 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;
+ }
+
+ 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
@@ -2056,7 +2279,7 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
}
);
- var callbackRegistry = {};
+ var callbackRegistry = Object.create(null);
// remember that the classNameFilter is set during the provider/config
// stage therefore we can optimize here and setup a helper function
@@ -2069,18 +2292,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 = window.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,15 +2322,24 @@ 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);
- });
+ function filterFromRegistry(list, matchContainer, matchCallback) {
+ var containerNode = extractElementNode(matchContainer);
+ return list.filter(function(entry) {
+ var isMatch = entry.node === containerNode &&
+ (!matchCallback || entry.callback === matchCallback);
+ return !isMatch;
});
}
- return {
+ function cleanupEventListeners(phase, element) {
+ if (phase === 'close' && !element[0].parentNode) {
+ // If the element is not attached to a parentNode, it has been removed by
+ // the domOperation, and we can safely remove the event callbacks
+ $animate.off(element);
+ }
+ }
+
+ var $animate = {
on: function(event, container, callback) {
var node = extractElementNode(container);
callbackRegistry[event] = callbackRegistry[event] || [];
@@ -2105,24 +2347,36 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
node: node,
callback: callback
});
+
+ // Remove the callback when the element is removed from the DOM
+ jqLite(container).on('$destroy', function() {
+ var animationDetails = activeAnimationsLookup.get(node);
+
+ if (!animationDetails) {
+ // If there's an animation ongoing, the callback calling code will remove
+ // the event listeners. If we'd remove here, the callbacks would be removed
+ // before the animation ends
+ $animate.off(event, container, callback);
+ }
+ });
},
off: function(event, container, callback) {
+ if (arguments.length === 1 && !isString(arguments[0])) {
+ container = arguments[0];
+ for (var eventType in callbackRegistry) {
+ callbackRegistry[eventType] = filterFromRegistry(callbackRegistry[eventType], container);
+ }
+
+ return;
+ }
+
var entries = callbackRegistry[event];
if (!entries) return;
callbackRegistry[event] = arguments.length === 1
? null
: filterFromRegistry(entries, container, callback);
-
- function filterFromRegistry(list, matchContainer, matchCallback) {
- var containerNode = extractElementNode(matchContainer);
- return list.filter(function(entry) {
- var isMatch = entry.node === containerNode &&
- (!matchCallback || entry.callback === matchCallback);
- return !isMatch;
- });
- }
},
pin: function(element, parentElement) {
@@ -2156,19 +2410,13 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
bool = animationsEnabled = !!element;
} else {
var node = getDomNode(element);
- var recordExists = disabledElementsLookup.get(node);
if (argCount === 1) {
// (element) - Element getter
- bool = !recordExists;
+ bool = !disabledElementsLookup.get(node);
} 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 +2425,14 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
}
};
- function queueAnimation(element, event, options) {
+ return $animate;
+
+ 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 +2446,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(' ');
}
@@ -2231,10 +2489,14 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
var isStructural = ['enter', 'move', 'leave'].indexOf(event) >= 0;
+ var documentHidden = $document[0].hidden;
+
// 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 || documentHidden || disabledElementsLookup.get(node);
var existingAnimation = (!skipAnimations && activeAnimationsLookup.get(node)) || {};
var hasExistingAnimation = !!existingAnimation.state;
@@ -2245,7 +2507,10 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
}
if (skipAnimations) {
+ // Callbacks should fire even if the document is hidden (regression fix for issue #14120)
+ if (documentHidden) notifyProgress(runner, event, 'start');
close();
+ if (documentHidden) notifyProgress(runner, event, 'close');
return runner;
}
@@ -2257,6 +2522,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 +2536,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 +2554,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 +2565,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 +2581,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 +2591,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 +2621,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,13 +2653,18 @@ 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;
markElementAnimationState(element, RUNNING_STATE);
var realRunner = $$animation(element, event, animationDetails.options);
+ // this will update the runner's flow-control events based on
+ // the `realRunner` object.
+ runner.setHost(realRunner);
+ notifyProgress(runner, event, 'start', {});
+
realRunner.done(function(status) {
close(!status);
var animationDetails = activeAnimationsLookup.get(node);
@@ -2401,17 +2673,28 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
}
notifyProgress(runner, event, 'close', {});
});
-
- // this will update the runner's flow-control events based on
- // the `realRunner` object.
- runner.setHost(realRunner);
- notifyProgress(runner, event, 'start', {});
});
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);
+ });
+ cleanupEventListeners(phase, element);
+ });
+ } else {
+ cleanupEventListeners(phase, element);
+ }
+ });
runner.progress(event, phase, data);
}
@@ -2430,15 +2713,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,40 +2736,61 @@ 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);
+ var parentHost = jqLite.data(element[0], NG_ANIMATE_PIN_DATA);
if (parentHost) {
parentElement = parentHost;
}
- while (parentElement && parentElement.length) {
+ parentElement = getDomNode(parentElement);
+
+ while (parentElement) {
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);
}
- var parentNode = parentElement[0];
- if (parentNode.nodeType !== ELEMENT_NODE) {
+ if (parentElement.nodeType !== ELEMENT_NODE) {
// no point in inspecting the #document element
break;
}
- var details = activeAnimationsLookup.get(parentNode) || {};
+ var details = activeAnimationsLookup.get(parentElement) || {};
// either an enter, leave or move animation will commence
// 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(parentElement);
+
+ 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) {
- var value = parentElement.data(NG_ANIMATE_CHILDREN_DATA);
+ var value = jqLite.data(parentElement, NG_ANIMATE_CHILDREN_DATA);
if (isDefined(value)) {
animateChildren = value;
}
@@ -2495,28 +2799,32 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
// there is no need to continue traversing at this point
if (parentAnimationDetected && animateChildren === false) break;
+ if (!bodyElementDetected) {
+ // 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, 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) {
- // 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 no rootElement is detected, check if the parentElement is pinned to another element
+ parentHost = jqLite.data(parentElement, NG_ANIMATE_PIN_DATA);
+ if (parentHost) {
+ // The pin target element becomes the next parent element
+ parentElement = getDomNode(parentHost);
+ continue;
}
}
- if (!bodyElementDetected) {
- // we also need to ensure that the element is or will be apart of the body element
- // otherwise it is pointless to even issue an animation to be rendered
- bodyElementDetected = isMatchingElement(parentElement, $$body);
- }
-
- parentElement = parentElement.parent();
+ parentElement = parentElement.parentNode;
}
- var allowAnimation = !parentAnimationDetected || animateChildren;
+ var allowAnimation = (!parentAnimationDetected || animateChildren) && elementDisabled !== true;
return allowAnimation && rootElementDetected && bodyElementDetected;
}
@@ -2536,171 +2844,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 +2979,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 +3149,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);
@@ -3043,8 +3192,6 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) {
// may attempt more elements, but custom drivers are more particular
for (var i = drivers.length - 1; i >= 0; i--) {
var driverName = drivers[i];
- if (!$injector.has(driverName)) continue; // TODO(matsko): remove this check
-
var factory = $injector.get(driverName);
var driver = factory(animationDetails);
if (driver) {
@@ -3058,6 +3205,10 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) {
if (tempClasses) {
$$jqLite.addClass(element, tempClasses);
}
+ if (prepareClassName) {
+ $$jqLite.removeClass(element, prepareClassName);
+ prepareClassName = null;
+ }
}
function updateAnimationRunners(animation, newRunner) {
@@ -3069,7 +3220,8 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) {
}
function update(element) {
- getRunner(element).setHost(newRunner);
+ var runner = getRunner(element);
+ if (runner) runner.setHost(newRunner);
}
}
@@ -3099,20 +3251,120 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) {
}];
}];
-/* global angularAnimateModule: true,
-
- $$BodyProvider,
- $$AnimateAsyncRunFactory,
- $$rAFSchedulerFactory,
- $$AnimateChildrenDirective,
- $$AnimateRunnerFactory,
- $$AnimateQueueProvider,
- $$AnimationProvider,
- $AnimateCssProvider,
- $$AnimateCssDriverProvider,
- $$AnimateJsProvider,
- $$AnimateJsDriverProvider,
-*/
+/**
+ * @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 or slider 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.
+ *
+ * @animations
+ * | Animation | Occurs |
+ * |----------------------------------|--------------------------------------|
+ * | {@link ng.$animate#enter enter} | when the new element is inserted to the DOM |
+ * | {@link ng.$animate#leave leave} | when the old element is removed from the DOM |
+ *
+ * @example
+ *
+ *
+ *