d406a15e8c
Some modules used to assume that the angular helpers would always be available when their script was executed. This could be a problem when using `angular-loader` and the module file happened to get loaded before the core `angular.js` file. This commit fixes the issue by delaying the access to angular helpers, until they are guaranteed to be available. Affected modules: - `ngAnimate` - `ngMessageFormat` - `ngMessages` - `ngRoute` - `ngSanitize` Fixes #9140 Closes #14794
726 lines
28 KiB
JavaScript
726 lines
28 KiB
JavaScript
'use strict';
|
|
|
|
var NG_ANIMATE_ATTR_NAME = 'data-ng-animate';
|
|
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: [],
|
|
cancel: [],
|
|
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(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);
|
|
});
|
|
|
|
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);
|
|
});
|
|
|
|
rules.skip.push(function(element, newAnimation, currentAnimation) {
|
|
// why should we trigger a new structural animation if the element will
|
|
// be removed from the DOM anyway?
|
|
return currentAnimation.event === 'leave' && newAnimation.structural;
|
|
});
|
|
|
|
rules.skip.push(function(element, newAnimation, currentAnimation) {
|
|
// if there is an ongoing current animation then don't even bother running the class-based animation
|
|
return currentAnimation.structural && currentAnimation.state === RUNNING_STATE && !newAnimation.structural;
|
|
});
|
|
|
|
rules.cancel.push(function(element, newAnimation, currentAnimation) {
|
|
// there can never be two structural animations running at the same time
|
|
return currentAnimation.structural && newAnimation.structural;
|
|
});
|
|
|
|
rules.cancel.push(function(element, newAnimation, currentAnimation) {
|
|
// if the previous animation is already running, but the new animation will
|
|
// be triggered, but the new animation is structural
|
|
return currentAnimation.state === RUNNING_STATE && newAnimation.structural;
|
|
});
|
|
|
|
rules.cancel.push(function(element, newAnimation, currentAnimation) {
|
|
// cancel the animation if classes added / removed in both animation cancel each other out,
|
|
// but only if the current animation isn't structural
|
|
|
|
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', '$$HashMap',
|
|
'$$animation', '$$AnimateRunner', '$templateRequest', '$$jqLite', '$$forceReflow',
|
|
'$$isDocumentHidden',
|
|
function($$rAF, $rootScope, $rootElement, $document, $$HashMap,
|
|
$$animation, $$AnimateRunner, $templateRequest, $$jqLite, $$forceReflow,
|
|
$$isDocumentHidden) {
|
|
|
|
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
|
|
// templates currently downloading then the watcher will still fire anyway.
|
|
var deregisterWatch = $rootScope.$watch(
|
|
function() { return $templateRequest.totalPendingRequests === 0; },
|
|
function(isEmpty) {
|
|
if (!isEmpty) return;
|
|
deregisterWatch();
|
|
|
|
// Now that all templates have been downloaded, $animate will wait until
|
|
// the post digest queue is empty before enabling animations. By having two
|
|
// calls to $postDigest calls we can ensure that the flag is enabled at the
|
|
// very end of the post digest queue. Since all of the animations in $animate
|
|
// use $postDigest, it's important that the code below executes at the end.
|
|
// This basically means that the page is fully downloaded and compiled before
|
|
// any animations are triggered.
|
|
$rootScope.$$postDigest(function() {
|
|
$rootScope.$$postDigest(function() {
|
|
// we check for null directly in the event that the application already called
|
|
// .enabled() with whatever arguments that it provided it with
|
|
if (animationsEnabled === null) {
|
|
animationsEnabled = true;
|
|
}
|
|
});
|
|
});
|
|
}
|
|
);
|
|
|
|
var callbackRegistry = {};
|
|
|
|
// remember that the classNameFilter is set during the provider/config
|
|
// stage therefore we can optimize here and setup a helper function
|
|
var classNameFilter = $animateProvider.classNameFilter();
|
|
var isAnimatableClassName = !classNameFilter
|
|
? function() { return true; }
|
|
: function(className) {
|
|
return classNameFilter.test(className);
|
|
};
|
|
|
|
var applyAnimationClasses = applyAnimationClassesFactory($$jqLite);
|
|
|
|
function normalizeAnimationDetails(element, animation) {
|
|
return mergeAnimationDetails(element, animation, {});
|
|
}
|
|
|
|
// 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 (contains.call(entry.node, targetNode)) {
|
|
matches.push(entry.callback);
|
|
} else if (event === 'leave' && contains.call(entry.node, targetParentNode)) {
|
|
matches.push(entry.callback);
|
|
}
|
|
});
|
|
}
|
|
|
|
return matches;
|
|
}
|
|
|
|
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;
|
|
});
|
|
}
|
|
|
|
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] || [];
|
|
callbackRegistry[event].push({
|
|
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);
|
|
},
|
|
|
|
pin: function(element, parentElement) {
|
|
assertArg(isElement(element), 'element', 'not an element');
|
|
assertArg(isElement(parentElement), 'parentElement', 'not an element');
|
|
element.data(NG_ANIMATE_PIN_DATA, parentElement);
|
|
},
|
|
|
|
push: function(element, event, options, domOperation) {
|
|
options = options || {};
|
|
options.domOperation = domOperation;
|
|
return queueAnimation(element, event, options);
|
|
},
|
|
|
|
// this method has four signatures:
|
|
// () - global getter
|
|
// (bool) - global setter
|
|
// (element) - element getter
|
|
// (element, bool) - element setter<F37>
|
|
enabled: function(element, bool) {
|
|
var argCount = arguments.length;
|
|
|
|
if (argCount === 0) {
|
|
// () - Global getter
|
|
bool = !!animationsEnabled;
|
|
} else {
|
|
var hasElement = isElement(element);
|
|
|
|
if (!hasElement) {
|
|
// (bool) - Global setter
|
|
bool = animationsEnabled = !!element;
|
|
} else {
|
|
var node = getDomNode(element);
|
|
var recordExists = disabledElementsLookup.get(node);
|
|
|
|
if (argCount === 1) {
|
|
// (element) - Element getter
|
|
bool = !recordExists;
|
|
} else {
|
|
// (element, bool) - Element setter
|
|
disabledElementsLookup.put(node, !bool);
|
|
}
|
|
}
|
|
}
|
|
|
|
return bool;
|
|
}
|
|
};
|
|
|
|
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) {
|
|
node = getDomNode(element);
|
|
parent = element.parent();
|
|
}
|
|
|
|
options = prepareAnimationOptions(options);
|
|
|
|
// we create a fake runner with a working promise.
|
|
// 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(' ');
|
|
}
|
|
|
|
if (options.addClass && !isString(options.addClass)) {
|
|
options.addClass = null;
|
|
}
|
|
|
|
if (isArray(options.removeClass)) {
|
|
options.removeClass = options.removeClass.join(' ');
|
|
}
|
|
|
|
if (options.removeClass && !isString(options.removeClass)) {
|
|
options.removeClass = null;
|
|
}
|
|
|
|
if (options.from && !isObject(options.from)) {
|
|
options.from = null;
|
|
}
|
|
|
|
if (options.to && !isObject(options.to)) {
|
|
options.to = null;
|
|
}
|
|
|
|
// there are situations where a directive issues an animation for
|
|
// a jqLite wrapper that contains only comment nodes... If this
|
|
// happens then there is no way we can perform an animation
|
|
if (!node) {
|
|
close();
|
|
return runner;
|
|
}
|
|
|
|
var className = [node.getAttribute('class'), options.addClass, options.removeClass].join(' ');
|
|
if (!isAnimatableClassName(className)) {
|
|
close();
|
|
return runner;
|
|
}
|
|
|
|
var isStructural = ['enter', 'move', 'leave'].indexOf(event) >= 0;
|
|
|
|
var documentHidden = $$isDocumentHidden();
|
|
|
|
// 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
|
|
// 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;
|
|
|
|
// there is no point in traversing the same collection of parent ancestors if a followup
|
|
// animation will be run on the same element that already did all that checking work
|
|
if (!skipAnimations && (!hasExistingAnimation || existingAnimation.state !== PRE_DIGEST_STATE)) {
|
|
skipAnimations = !areAnimationsAllowed(element, parent, event);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
if (isStructural) {
|
|
closeChildAnimations(element);
|
|
}
|
|
|
|
var newAnimation = {
|
|
structural: isStructural,
|
|
element: element,
|
|
event: event,
|
|
addClass: options.addClass,
|
|
removeClass: options.removeClass,
|
|
close: close,
|
|
options: options,
|
|
runner: runner
|
|
};
|
|
|
|
if (hasExistingAnimation) {
|
|
var skipAnimationFlag = isAllowed('skip', element, newAnimation, existingAnimation);
|
|
if (skipAnimationFlag) {
|
|
if (existingAnimation.state === RUNNING_STATE) {
|
|
close();
|
|
return runner;
|
|
} else {
|
|
mergeAnimationDetails(element, existingAnimation, newAnimation);
|
|
return existingAnimation.runner;
|
|
}
|
|
}
|
|
var cancelAnimationFlag = isAllowed('cancel', element, newAnimation, existingAnimation);
|
|
if (cancelAnimationFlag) {
|
|
if (existingAnimation.state === RUNNING_STATE) {
|
|
// this will end the animation right away and it is safe
|
|
// to do so since the animation is already running and the
|
|
// runner callback code will run in async
|
|
existingAnimation.runner.end();
|
|
} else if (existingAnimation.structural) {
|
|
// this means that the animation is queued into a digest, but
|
|
// hasn't started yet. Therefore it is safe to run the close
|
|
// method which will call the runner methods in async.
|
|
existingAnimation.close();
|
|
} else {
|
|
// this will merge the new animation options into existing animation options
|
|
mergeAnimationDetails(element, existingAnimation, newAnimation);
|
|
|
|
return existingAnimation.runner;
|
|
}
|
|
} else {
|
|
// a joined animation means that this animation will take over the existing one
|
|
// so an example would involve a leave animation taking over an enter. Then when
|
|
// the postDigest kicks in the enter will be ignored.
|
|
var joinAnimationFlag = isAllowed('join', element, newAnimation, existingAnimation);
|
|
if (joinAnimationFlag) {
|
|
if (existingAnimation.state === RUNNING_STATE) {
|
|
normalizeAnimationDetails(element, newAnimation);
|
|
} else {
|
|
applyGeneratedPreparationClasses(element, isStructural ? event : null, options);
|
|
|
|
event = newAnimation.event = existingAnimation.event;
|
|
options = mergeAnimationDetails(element, existingAnimation, newAnimation);
|
|
|
|
//we return the same runner since only the option values of this animation will
|
|
//be fed into the `existingAnimation`.
|
|
return existingAnimation.runner;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
// normalization in this case means that it removes redundant CSS classes that
|
|
// already exist (addClass) or do not exist (removeClass) on the element
|
|
normalizeAnimationDetails(element, newAnimation);
|
|
}
|
|
|
|
// when the options are merged and cleaned up we may end up not having to do
|
|
// an animation at all, therefore we should check this before issuing a post
|
|
// digest callback. Structural animations will always run no matter what.
|
|
var isValidAnimation = newAnimation.structural;
|
|
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);
|
|
}
|
|
|
|
if (!isValidAnimation) {
|
|
close();
|
|
clearElementAnimationState(element);
|
|
return runner;
|
|
}
|
|
|
|
// the counter keeps track of cancelled animations
|
|
var counter = (existingAnimation.counter || 0) + 1;
|
|
newAnimation.counter = counter;
|
|
|
|
markElementAnimationState(element, PRE_DIGEST_STATE, newAnimation);
|
|
|
|
$rootScope.$$postDigest(function() {
|
|
var animationDetails = activeAnimationsLookup.get(node);
|
|
var animationCancelled = !animationDetails;
|
|
animationDetails = animationDetails || {};
|
|
|
|
// if addClass/removeClass is called before something like enter then the
|
|
// registered parent element may not be present. The code below will ensure
|
|
// that a final value for parent element is obtained
|
|
var parentElement = element.parent() || [];
|
|
|
|
// animate/structural/class-based animations all have requirements. Otherwise there
|
|
// is no point in performing an animation. The parent node must also be set.
|
|
var isValidAnimation = parentElement.length > 0
|
|
&& (animationDetails.event === 'animate'
|
|
|| animationDetails.structural
|
|
|| hasAnimationClasses(animationDetails));
|
|
|
|
// this means that the previous animation was cancelled
|
|
// even if the follow-up animation is the same event
|
|
if (animationCancelled || animationDetails.counter !== counter || !isValidAnimation) {
|
|
// if another animation did not take over then we need
|
|
// to make sure that the domOperation and options are
|
|
// handled accordingly
|
|
if (animationCancelled) {
|
|
applyAnimationClasses(element, options);
|
|
applyAnimationStyles(element, options);
|
|
}
|
|
|
|
// if the event changed from something like enter to leave then we do
|
|
// it, otherwise if it's the same then the end result will be the same too
|
|
if (animationCancelled || (isStructural && animationDetails.event !== event)) {
|
|
options.domOperation();
|
|
runner.end();
|
|
}
|
|
|
|
// in the event that the element animation was not cancelled or a follow-up animation
|
|
// isn't allowed to animate from here then we need to clear the state of the element
|
|
// so that any future animations won't read the expired animation data.
|
|
if (!isValidAnimation) {
|
|
clearElementAnimationState(element);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
// 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, 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);
|
|
if (animationDetails && animationDetails.counter === counter) {
|
|
clearElementAnimationState(getDomNode(element));
|
|
}
|
|
notifyProgress(runner, event, 'close', {});
|
|
});
|
|
});
|
|
|
|
return runner;
|
|
|
|
function notifyProgress(runner, event, 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);
|
|
}
|
|
|
|
function close(reject) { // jshint ignore:line
|
|
clearGeneratedClasses(element, options);
|
|
applyAnimationClasses(element, options);
|
|
applyAnimationStyles(element, options);
|
|
options.domOperation();
|
|
runner.complete(!reject);
|
|
}
|
|
}
|
|
|
|
function closeChildAnimations(element) {
|
|
var node = getDomNode(element);
|
|
var children = node.querySelectorAll('[' + NG_ANIMATE_ATTR_NAME + ']');
|
|
forEach(children, function(child) {
|
|
var state = parseInt(child.getAttribute(NG_ANIMATE_ATTR_NAME));
|
|
var animationDetails = activeAnimationsLookup.get(child);
|
|
if (animationDetails) {
|
|
switch (state) {
|
|
case RUNNING_STATE:
|
|
animationDetails.runner.end();
|
|
/* falls through */
|
|
case PRE_DIGEST_STATE:
|
|
activeAnimationsLookup.remove(child);
|
|
break;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
function clearElementAnimationState(element) {
|
|
var node = getDomNode(element);
|
|
node.removeAttribute(NG_ANIMATE_ATTR_NAME);
|
|
activeAnimationsLookup.remove(node);
|
|
}
|
|
|
|
function isMatchingElement(nodeOrElmA, nodeOrElmB) {
|
|
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 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 = jqLite.data(element[0], NG_ANIMATE_PIN_DATA);
|
|
if (parentHost) {
|
|
parentElement = parentHost;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
if (parentElement.nodeType !== ELEMENT_NODE) {
|
|
// no point in inspecting the #document element
|
|
break;
|
|
}
|
|
|
|
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) {
|
|
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 = jqLite.data(parentElement, NG_ANIMATE_CHILDREN_DATA);
|
|
if (isDefined(value)) {
|
|
animateChildren = value;
|
|
}
|
|
}
|
|
|
|
// 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) {
|
|
// 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;
|
|
}
|
|
}
|
|
|
|
parentElement = parentElement.parentNode;
|
|
}
|
|
|
|
var allowAnimation = (!parentAnimationDetected || animateChildren) && elementDisabled !== true;
|
|
return allowAnimation && rootElementDetected && bodyElementDetected;
|
|
}
|
|
|
|
function markElementAnimationState(element, state, details) {
|
|
details = details || {};
|
|
details.state = state;
|
|
|
|
var node = getDomNode(element);
|
|
node.setAttribute(NG_ANIMATE_ATTR_NAME, state);
|
|
|
|
var oldValue = activeAnimationsLookup.get(node);
|
|
var newValue = oldValue
|
|
? extend(oldValue, details)
|
|
: details;
|
|
activeAnimationsLookup.put(node, newValue);
|
|
}
|
|
}];
|
|
}];
|