+++ /dev/null
-/**\r
- * @license AngularJS v1.4.3\r
- * (c) 2010-2015 Google, Inc. http://angularjs.org\r
- * License: MIT\r
- */\r
-(function(window, angular, undefined) {'use strict';\r
-\r
-/* jshint ignore:start */\r
-var noop = angular.noop;\r
-var extend = angular.extend;\r
-var jqLite = angular.element;\r
-var forEach = angular.forEach;\r
-var isArray = angular.isArray;\r
-var isString = angular.isString;\r
-var isObject = angular.isObject;\r
-var isUndefined = angular.isUndefined;\r
-var isDefined = angular.isDefined;\r
-var isFunction = angular.isFunction;\r
-var isElement = angular.isElement;\r
-\r
-var ELEMENT_NODE = 1;\r
-var COMMENT_NODE = 8;\r
-\r
-var NG_ANIMATE_CLASSNAME = 'ng-animate';\r
-var NG_ANIMATE_CHILDREN_DATA = '$$ngAnimateChildren';\r
-\r
-var isPromiseLike = function(p) {\r
- return p && p.then ? true : false;\r
-}\r
-\r
-function assertArg(arg, name, reason) {\r
- if (!arg) {\r
- throw ngMinErr('areq', "Argument '{0}' is {1}", (name || '?'), (reason || "required"));\r
- }\r
- return arg;\r
-}\r
-\r
-function mergeClasses(a,b) {\r
- if (!a && !b) return '';\r
- if (!a) return b;\r
- if (!b) return a;\r
- if (isArray(a)) a = a.join(' ');\r
- if (isArray(b)) b = b.join(' ');\r
- return a + ' ' + b;\r
-}\r
-\r
-function packageStyles(options) {\r
- var styles = {};\r
- if (options && (options.to || options.from)) {\r
- styles.to = options.to;\r
- styles.from = options.from;\r
- }\r
- return styles;\r
-}\r
-\r
-function pendClasses(classes, fix, isPrefix) {\r
- var className = '';\r
- classes = isArray(classes)\r
- ? classes\r
- : classes && isString(classes) && classes.length\r
- ? classes.split(/\s+/)\r
- : [];\r
- forEach(classes, function(klass, i) {\r
- if (klass && klass.length > 0) {\r
- className += (i > 0) ? ' ' : '';\r
- className += isPrefix ? fix + klass\r
- : klass + fix;\r
- }\r
- });\r
- return className;\r
-}\r
-\r
-function removeFromArray(arr, val) {\r
- var index = arr.indexOf(val);\r
- if (val >= 0) {\r
- arr.splice(index, 1);\r
- }\r
-}\r
-\r
-function stripCommentsFromElement(element) {\r
- if (element instanceof jqLite) {\r
- switch (element.length) {\r
- case 0:\r
- return [];\r
- break;\r
-\r
- case 1:\r
- // there is no point of stripping anything if the element\r
- // is the only element within the jqLite wrapper.\r
- // (it's important that we retain the element instance.)\r
- if (element[0].nodeType === ELEMENT_NODE) {\r
- return element;\r
- }\r
- break;\r
-\r
- default:\r
- return jqLite(extractElementNode(element));\r
- break;\r
- }\r
- }\r
-\r
- if (element.nodeType === ELEMENT_NODE) {\r
- return jqLite(element);\r
- }\r
-}\r
-\r
-function extractElementNode(element) {\r
- if (!element[0]) return element;\r
- for (var i = 0; i < element.length; i++) {\r
- var elm = element[i];\r
- if (elm.nodeType == ELEMENT_NODE) {\r
- return elm;\r
- }\r
- }\r
-}\r
-\r
-function $$addClass($$jqLite, element, className) {\r
- forEach(element, function(elm) {\r
- $$jqLite.addClass(elm, className);\r
- });\r
-}\r
-\r
-function $$removeClass($$jqLite, element, className) {\r
- forEach(element, function(elm) {\r
- $$jqLite.removeClass(elm, className);\r
- });\r
-}\r
-\r
-function applyAnimationClassesFactory($$jqLite) {\r
- return function(element, options) {\r
- if (options.addClass) {\r
- $$addClass($$jqLite, element, options.addClass);\r
- options.addClass = null;\r
- }\r
- if (options.removeClass) {\r
- $$removeClass($$jqLite, element, options.removeClass);\r
- options.removeClass = null;\r
- }\r
- }\r
-}\r
-\r
-function prepareAnimationOptions(options) {\r
- options = options || {};\r
- if (!options.$$prepared) {\r
- var domOperation = options.domOperation || noop;\r
- options.domOperation = function() {\r
- options.$$domOperationFired = true;\r
- domOperation();\r
- domOperation = noop;\r
- };\r
- options.$$prepared = true;\r
- }\r
- return options;\r
-}\r
-\r
-function applyAnimationStyles(element, options) {\r
- applyAnimationFromStyles(element, options);\r
- applyAnimationToStyles(element, options);\r
-}\r
-\r
-function applyAnimationFromStyles(element, options) {\r
- if (options.from) {\r
- element.css(options.from);\r
- options.from = null;\r
- }\r
-}\r
-\r
-function applyAnimationToStyles(element, options) {\r
- if (options.to) {\r
- element.css(options.to);\r
- options.to = null;\r
- }\r
-}\r
-\r
-function mergeAnimationOptions(element, target, newOptions) {\r
- var toAdd = (target.addClass || '') + ' ' + (newOptions.addClass || '');\r
- var toRemove = (target.removeClass || '') + ' ' + (newOptions.removeClass || '');\r
- var classes = resolveElementClasses(element.attr('class'), toAdd, toRemove);\r
-\r
- extend(target, newOptions);\r
-\r
- if (classes.addClass) {\r
- target.addClass = classes.addClass;\r
- } else {\r
- target.addClass = null;\r
- }\r
-\r
- if (classes.removeClass) {\r
- target.removeClass = classes.removeClass;\r
- } else {\r
- target.removeClass = null;\r
- }\r
-\r
- return target;\r
-}\r
-\r
-function resolveElementClasses(existing, toAdd, toRemove) {\r
- var ADD_CLASS = 1;\r
- var REMOVE_CLASS = -1;\r
-\r
- var flags = {};\r
- existing = splitClassesToLookup(existing);\r
-\r
- toAdd = splitClassesToLookup(toAdd);\r
- forEach(toAdd, function(value, key) {\r
- flags[key] = ADD_CLASS;\r
- });\r
-\r
- toRemove = splitClassesToLookup(toRemove);\r
- forEach(toRemove, function(value, key) {\r
- flags[key] = flags[key] === ADD_CLASS ? null : REMOVE_CLASS;\r
- });\r
-\r
- var classes = {\r
- addClass: '',\r
- removeClass: ''\r
- };\r
-\r
- forEach(flags, function(val, klass) {\r
- var prop, allow;\r
- if (val === ADD_CLASS) {\r
- prop = 'addClass';\r
- allow = !existing[klass];\r
- } else if (val === REMOVE_CLASS) {\r
- prop = 'removeClass';\r
- allow = existing[klass];\r
- }\r
- if (allow) {\r
- if (classes[prop].length) {\r
- classes[prop] += ' ';\r
- }\r
- classes[prop] += klass;\r
- }\r
- });\r
-\r
- function splitClassesToLookup(classes) {\r
- if (isString(classes)) {\r
- classes = classes.split(' ');\r
- }\r
-\r
- var obj = {};\r
- forEach(classes, function(klass) {\r
- // sometimes the split leaves empty string values\r
- // incase extra spaces were applied to the options\r
- if (klass.length) {\r
- obj[klass] = true;\r
- }\r
- });\r
- return obj;\r
- }\r
-\r
- return classes;\r
-}\r
-\r
-function getDomNode(element) {\r
- return (element instanceof angular.element) ? element[0] : element;\r
-}\r
-\r
-var $$rAFSchedulerFactory = ['$$rAF', function($$rAF) {\r
- var tickQueue = [];\r
- var cancelFn;\r
-\r
- function scheduler(tasks) {\r
- // we make a copy since RAFScheduler mutates the state\r
- // of the passed in array variable and this would be difficult\r
- // to track down on the outside code\r
- tickQueue.push([].concat(tasks));\r
- nextTick();\r
- }\r
-\r
- /* waitUntilQuiet does two things:\r
- * 1. It will run the FINAL `fn` value only when an uncancelled RAF has passed through\r
- * 2. It will delay the next wave of tasks from running until the quiet `fn` has run.\r
- *\r
- * The motivation here is that animation code can request more time from the scheduler\r
- * before the next wave runs. This allows for certain DOM properties such as classes to\r
- * be resolved in time for the next animation to run.\r
- */\r
- scheduler.waitUntilQuiet = function(fn) {\r
- if (cancelFn) cancelFn();\r
-\r
- cancelFn = $$rAF(function() {\r
- cancelFn = null;\r
- fn();\r
- nextTick();\r
- });\r
- };\r
-\r
- return scheduler;\r
-\r
- function nextTick() {\r
- if (!tickQueue.length) return;\r
-\r
- var updatedQueue = [];\r
- for (var i = 0; i < tickQueue.length; i++) {\r
- var innerQueue = tickQueue[i];\r
- runNextTask(innerQueue);\r
- if (innerQueue.length) {\r
- updatedQueue.push(innerQueue);\r
- }\r
- }\r
- tickQueue = updatedQueue;\r
-\r
- if (!cancelFn) {\r
- $$rAF(function() {\r
- if (!cancelFn) nextTick();\r
- });\r
- }\r
- }\r
-\r
- function runNextTask(tasks) {\r
- var nextTask = tasks.shift();\r
- nextTask();\r
- }\r
-}];\r
-\r
-var $$AnimateChildrenDirective = [function() {\r
- return function(scope, element, attrs) {\r
- var val = attrs.ngAnimateChildren;\r
- if (angular.isString(val) && val.length === 0) { //empty attribute\r
- element.data(NG_ANIMATE_CHILDREN_DATA, true);\r
- } else {\r
- attrs.$observe('ngAnimateChildren', function(value) {\r
- value = value === 'on' || value === 'true';\r
- element.data(NG_ANIMATE_CHILDREN_DATA, value);\r
- });\r
- }\r
- };\r
-}];\r
-\r
-/**\r
- * @ngdoc service\r
- * @name $animateCss\r
- * @kind object\r
- *\r
- * @description\r
- * The `$animateCss` service is a useful utility to trigger customized CSS-based transitions/keyframes\r
- * from a JavaScript-based animation or directly from a directive. The purpose of `$animateCss` is NOT\r
- * to side-step how `$animate` and ngAnimate work, but the goal is to allow pre-existing animations or\r
- * directives to create more complex animations that can be purely driven using CSS code.\r
- *\r
- * Note that only browsers that support CSS transitions and/or keyframe animations are capable of\r
- * rendering animations triggered via `$animateCss` (bad news for IE9 and lower).\r
- *\r
- * ## Usage\r
- * Once again, `$animateCss` is designed to be used inside of a registered JavaScript animation that\r
- * is powered by ngAnimate. It is possible to use `$animateCss` directly inside of a directive, however,\r
- * any automatic control over cancelling animations and/or preventing animations from being run on\r
- * child elements will not be handled by Angular. For this to work as expected, please use `$animate` to\r
- * trigger the animation and then setup a JavaScript animation that injects `$animateCss` to trigger\r
- * the CSS animation.\r
- *\r
- * The example below shows how we can create a folding animation on an element using `ng-if`:\r
- *\r
- * ```html\r
- * <!-- notice the `fold-animation` CSS class -->\r
- * <div ng-if="onOff" class="fold-animation">\r
- * This element will go BOOM\r
- * </div>\r
- * <button ng-click="onOff=true">Fold In</button>\r
- * ```\r
- *\r
- * Now we create the **JavaScript animation** that will trigger the CSS transition:\r
- *\r
- * ```js\r
- * ngModule.animation('.fold-animation', ['$animateCss', function($animateCss) {\r
- * return {\r
- * enter: function(element, doneFn) {\r
- * var height = element[0].offsetHeight;\r
- * return $animateCss(element, {\r
- * from: { height:'0px' },\r
- * to: { height:height + 'px' },\r
- * duration: 1 // one second\r
- * });\r
- * }\r
- * }\r
- * }]);\r
- * ```\r
- *\r
- * ## More Advanced Uses\r
- *\r
- * `$animateCss` is the underlying code that ngAnimate uses to power **CSS-based animations** behind the scenes. Therefore CSS hooks\r
- * like `.ng-EVENT`, `.ng-EVENT-active`, `.ng-EVENT-stagger` are all features that can be triggered using `$animateCss` via JavaScript code.\r
- *\r
- * This also means that just about any combination of adding classes, removing classes, setting styles, dynamically setting a keyframe animation,\r
- * applying a hardcoded duration or delay value, changing the animation easing or applying a stagger animation are all options that work with\r
- * `$animateCss`. The service itself is smart enough to figure out the combination of options and examine the element styling properties in order\r
- * to provide a working animation that will run in CSS.\r
- *\r
- * The example below showcases a more advanced version of the `.fold-animation` from the example above:\r
- *\r
- * ```js\r
- * ngModule.animation('.fold-animation', ['$animateCss', function($animateCss) {\r
- * return {\r
- * enter: function(element, doneFn) {\r
- * var height = element[0].offsetHeight;\r
- * return $animateCss(element, {\r
- * addClass: 'red large-text pulse-twice',\r
- * easing: 'ease-out',\r
- * from: { height:'0px' },\r
- * to: { height:height + 'px' },\r
- * duration: 1 // one second\r
- * });\r
- * }\r
- * }\r
- * }]);\r
- * ```\r
- *\r
- * Since we're adding/removing CSS classes then the CSS transition will also pick those up:\r
- *\r
- * ```css\r
- * /* since a hardcoded duration value of 1 was provided in the JavaScript animation code,\r
- * the CSS classes below will be transitioned despite them being defined as regular CSS classes */\r
- * .red { background:red; }\r
- * .large-text { font-size:20px; }\r
- *\r
- * /* we can also use a keyframe animation and $animateCss will make it work alongside the transition */\r
- * .pulse-twice {\r
- * animation: 0.5s pulse linear 2;\r
- * -webkit-animation: 0.5s pulse linear 2;\r
- * }\r
- *\r
- * @keyframes pulse {\r
- * from { transform: scale(0.5); }\r
- * to { transform: scale(1.5); }\r
- * }\r
- *\r
- * @-webkit-keyframes pulse {\r
- * from { -webkit-transform: scale(0.5); }\r
- * to { -webkit-transform: scale(1.5); }\r
- * }\r
- * ```\r
- *\r
- * Given this complex combination of CSS classes, styles and options, `$animateCss` will figure everything out and make the animation happen.\r
- *\r
- * ## How the Options are handled\r
- *\r
- * `$animateCss` is very versatile and intelligent when it comes to figuring out what configurations to apply to the element to ensure the animation\r
- * works with the options provided. Say for example we were adding a class that contained a keyframe value and we wanted to also animate some inline\r
- * styles using the `from` and `to` properties.\r
- *\r
- * ```js\r
- * var animator = $animateCss(element, {\r
- * from: { background:'red' },\r
- * to: { background:'blue' }\r
- * });\r
- * animator.start();\r
- * ```\r
- *\r
- * ```css\r
- * .rotating-animation {\r
- * animation:0.5s rotate linear;\r
- * -webkit-animation:0.5s rotate linear;\r
- * }\r
- *\r
- * @keyframes rotate {\r
- * from { transform: rotate(0deg); }\r
- * to { transform: rotate(360deg); }\r
- * }\r
- *\r
- * @-webkit-keyframes rotate {\r
- * from { -webkit-transform: rotate(0deg); }\r
- * to { -webkit-transform: rotate(360deg); }\r
- * }\r
- * ```\r
- *\r
- * The missing pieces here are that we do not have a transition set (within the CSS code nor within the `$animateCss` options) and the duration of the animation is\r
- * going to be detected from what the keyframe styles on the CSS class are. In this event, `$animateCss` will automatically create an inline transition\r
- * style matching the duration detected from the keyframe style (which is present in the CSS class that is being added) and then prepare both the transition\r
- * and keyframe animations to run in parallel on the element. Then when the animation is underway the provided `from` and `to` CSS styles will be applied\r
- * and spread across the transition and keyframe animation.\r
- *\r
- * ## What is returned\r
- *\r
- * `$animateCss` works in two stages: a preparation phase and an animation phase. Therefore when `$animateCss` is first called it will NOT actually\r
- * start the animation. All that is going on here is that the element is being prepared for the animation (which means that the generated CSS classes are\r
- * added and removed on the element). Once `$animateCss` is called it will return an object with the following properties:\r
- *\r
- * ```js\r
- * var animator = $animateCss(element, { ... });\r
- * ```\r
- *\r
- * Now what do the contents of our `animator` variable look like:\r
- *\r
- * ```js\r
- * {\r
- * // starts the animation\r
- * start: Function,\r
- *\r
- * // ends (aborts) the animation\r
- * end: Function\r
- * }\r
- * ```\r
- *\r
- * 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.\r
- * 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\r
- * applied to the element during the preparation phase). Note that all other properties such as duration, delay, transitions and keyframes are just properties\r
- * and that changing them will not reconfigure the parameters of the animation.\r
- *\r
- * ### runner.done() vs runner.then()\r
- * It is documented that `animation.start()` will return a promise object and this is true, however, there is also an additional method available on the\r
- * runner called `.done(callbackFn)`. The done method works the same as `.finally(callbackFn)`, however, it does **not trigger a digest to occur**.\r
- * Therefore, for performance reasons, it's always best to use `runner.done(callback)` instead of `runner.then()`, `runner.catch()` or `runner.finally()`\r
- * unless you really need a digest to kick off afterwards.\r
- *\r
- * Keep in mind that, to make this easier, ngAnimate has tweaked the JS animations API to recognize when a runner instance is returned from $animateCss\r
- * (so there is no need to call `runner.done(doneFn)` inside of your JavaScript animation code).\r
- * Check the {@link ngAnimate.$animateCss#usage animation code above} to see how this works.\r
- *\r
- * @param {DOMElement} element the element that will be animated\r
- * @param {object} options the animation-related options that will be applied during the animation\r
- *\r
- * * `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\r
- * 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.)\r
- * * `easing` - The CSS easing value that will be applied to the transition or keyframe animation (or both).\r
- * * `transition` - The raw CSS transition style that will be used (e.g. `1s linear all`).\r
- * * `keyframeStyle` - The raw CSS keyframe animation style that will be used (e.g. `1s my_animation linear`).\r
- * * `from` - The starting CSS styles (a key/value object) that will be applied at the start of the animation.\r
- * * `to` - The ending CSS styles (a key/value object) that will be applied across the animation via a CSS transition.\r
- * * `addClass` - A space separated list of CSS classes that will be added to the element and spread across the animation.\r
- * * `removeClass` - A space separated list of CSS classes that will be removed from the element and spread across the animation.\r
- * * `duration` - A number value representing the total duration of the transition and/or keyframe (note that a value of 1 is 1000ms). If a value of `0`\r
- * is provided then the animation will be skipped entirely.\r
- * * `delay` - A number value representing the total delay of the transition and/or keyframe (note that a value of 1 is 1000ms). If a value of `true` is\r
- * used then whatever delay value is detected from the CSS classes will be mirrored on the elements styles (e.g. by setting delay true then the style value\r
- * of the element will be `transition-delay: DETECTED_VALUE`). Using `true` is useful when you want the CSS classes and inline styles to all share the same\r
- * CSS delay value.\r
- * * `stagger` - A numeric time value representing the delay between successively animated elements\r
- * ({@link ngAnimate#css-staggering-animations Click here to learn how CSS-based staggering works in ngAnimate.})\r
- * * `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\r
- * `stagger` option value of `0.1` is used then there will be a stagger delay of `600ms`)\r
- * `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.)\r
- *\r
- * @return {object} an object with start and end methods and details about the animation.\r
- *\r
- * * `start` - The method to start the animation. This will return a `Promise` when called.\r
- * * `end` - This method will cancel the animation and remove all applied CSS classes and styles.\r
- */\r
-\r
-// Detect proper transitionend/animationend event names.\r
-var CSS_PREFIX = '', TRANSITION_PROP, TRANSITIONEND_EVENT, ANIMATION_PROP, ANIMATIONEND_EVENT;\r
-\r
-// If unprefixed events are not supported but webkit-prefixed are, use the latter.\r
-// Otherwise, just use W3C names, browsers not supporting them at all will just ignore them.\r
-// Note: Chrome implements `window.onwebkitanimationend` and doesn't implement `window.onanimationend`\r
-// but at the same time dispatches the `animationend` event and not `webkitAnimationEnd`.\r
-// Register both events in case `window.onanimationend` is not supported because of that,\r
-// do the same for `transitionend` as Safari is likely to exhibit similar behavior.\r
-// Also, the only modern browser that uses vendor prefixes for transitions/keyframes is webkit\r
-// therefore there is no reason to test anymore for other vendor prefixes:\r
-// http://caniuse.com/#search=transition\r
-if (window.ontransitionend === undefined && window.onwebkittransitionend !== undefined) {\r
- CSS_PREFIX = '-webkit-';\r
- TRANSITION_PROP = 'WebkitTransition';\r
- TRANSITIONEND_EVENT = 'webkitTransitionEnd transitionend';\r
-} else {\r
- TRANSITION_PROP = 'transition';\r
- TRANSITIONEND_EVENT = 'transitionend';\r
-}\r
-\r
-if (window.onanimationend === undefined && window.onwebkitanimationend !== undefined) {\r
- CSS_PREFIX = '-webkit-';\r
- ANIMATION_PROP = 'WebkitAnimation';\r
- ANIMATIONEND_EVENT = 'webkitAnimationEnd animationend';\r
-} else {\r
- ANIMATION_PROP = 'animation';\r
- ANIMATIONEND_EVENT = 'animationend';\r
-}\r
-\r
-var DURATION_KEY = 'Duration';\r
-var PROPERTY_KEY = 'Property';\r
-var DELAY_KEY = 'Delay';\r
-var TIMING_KEY = 'TimingFunction';\r
-var ANIMATION_ITERATION_COUNT_KEY = 'IterationCount';\r
-var ANIMATION_PLAYSTATE_KEY = 'PlayState';\r
-var ELAPSED_TIME_MAX_DECIMAL_PLACES = 3;\r
-var CLOSING_TIME_BUFFER = 1.5;\r
-var ONE_SECOND = 1000;\r
-var BASE_TEN = 10;\r
-\r
-var SAFE_FAST_FORWARD_DURATION_VALUE = 9999;\r
-\r
-var ANIMATION_DELAY_PROP = ANIMATION_PROP + DELAY_KEY;\r
-var ANIMATION_DURATION_PROP = ANIMATION_PROP + DURATION_KEY;\r
-\r
-var TRANSITION_DELAY_PROP = TRANSITION_PROP + DELAY_KEY;\r
-var TRANSITION_DURATION_PROP = TRANSITION_PROP + DURATION_KEY;\r
-\r
-var DETECT_CSS_PROPERTIES = {\r
- transitionDuration: TRANSITION_DURATION_PROP,\r
- transitionDelay: TRANSITION_DELAY_PROP,\r
- transitionProperty: TRANSITION_PROP + PROPERTY_KEY,\r
- animationDuration: ANIMATION_DURATION_PROP,\r
- animationDelay: ANIMATION_DELAY_PROP,\r
- animationIterationCount: ANIMATION_PROP + ANIMATION_ITERATION_COUNT_KEY\r
-};\r
-\r
-var DETECT_STAGGER_CSS_PROPERTIES = {\r
- transitionDuration: TRANSITION_DURATION_PROP,\r
- transitionDelay: TRANSITION_DELAY_PROP,\r
- animationDuration: ANIMATION_DURATION_PROP,\r
- animationDelay: ANIMATION_DELAY_PROP\r
-};\r
-\r
-function computeCssStyles($window, element, properties) {\r
- var styles = Object.create(null);\r
- var detectedStyles = $window.getComputedStyle(element) || {};\r
- forEach(properties, function(formalStyleName, actualStyleName) {\r
- var val = detectedStyles[formalStyleName];\r
- if (val) {\r
- var c = val.charAt(0);\r
-\r
- // only numerical-based values have a negative sign or digit as the first value\r
- if (c === '-' || c === '+' || c >= 0) {\r
- val = parseMaxTime(val);\r
- }\r
-\r
- // by setting this to null in the event that the delay is not set or is set directly as 0\r
- // then we can still allow for zegative values to be used later on and not mistake this\r
- // value for being greater than any other negative value.\r
- if (val === 0) {\r
- val = null;\r
- }\r
- styles[actualStyleName] = val;\r
- }\r
- });\r
-\r
- return styles;\r
-}\r
-\r
-function parseMaxTime(str) {\r
- var maxValue = 0;\r
- var values = str.split(/\s*,\s*/);\r
- forEach(values, function(value) {\r
- // it's always safe to consider only second values and omit `ms` values since\r
- // getComputedStyle will always handle the conversion for us\r
- if (value.charAt(value.length - 1) == 's') {\r
- value = value.substring(0, value.length - 1);\r
- }\r
- value = parseFloat(value) || 0;\r
- maxValue = maxValue ? Math.max(value, maxValue) : value;\r
- });\r
- return maxValue;\r
-}\r
-\r
-function truthyTimingValue(val) {\r
- return val === 0 || val != null;\r
-}\r
-\r
-function getCssTransitionDurationStyle(duration, applyOnlyDuration) {\r
- var style = TRANSITION_PROP;\r
- var value = duration + 's';\r
- if (applyOnlyDuration) {\r
- style += DURATION_KEY;\r
- } else {\r
- value += ' linear all';\r
- }\r
- return [style, value];\r
-}\r
-\r
-function getCssKeyframeDurationStyle(duration) {\r
- return [ANIMATION_DURATION_PROP, duration + 's'];\r
-}\r
-\r
-function getCssDelayStyle(delay, isKeyframeAnimation) {\r
- var prop = isKeyframeAnimation ? ANIMATION_DELAY_PROP : TRANSITION_DELAY_PROP;\r
- return [prop, delay + 's'];\r
-}\r
-\r
-function blockTransitions(node, duration) {\r
- // we use a negative delay value since it performs blocking\r
- // yet it doesn't kill any existing transitions running on the\r
- // same element which makes this safe for class-based animations\r
- var value = duration ? '-' + duration + 's' : '';\r
- applyInlineStyle(node, [TRANSITION_DELAY_PROP, value]);\r
- return [TRANSITION_DELAY_PROP, value];\r
-}\r
-\r
-function blockKeyframeAnimations(node, applyBlock) {\r
- var value = applyBlock ? 'paused' : '';\r
- var key = ANIMATION_PROP + ANIMATION_PLAYSTATE_KEY;\r
- applyInlineStyle(node, [key, value]);\r
- return [key, value];\r
-}\r
-\r
-function applyInlineStyle(node, styleTuple) {\r
- var prop = styleTuple[0];\r
- var value = styleTuple[1];\r
- node.style[prop] = value;\r
-}\r
-\r
-function createLocalCacheLookup() {\r
- var cache = Object.create(null);\r
- return {\r
- flush: function() {\r
- cache = Object.create(null);\r
- },\r
-\r
- count: function(key) {\r
- var entry = cache[key];\r
- return entry ? entry.total : 0;\r
- },\r
-\r
- get: function(key) {\r
- var entry = cache[key];\r
- return entry && entry.value;\r
- },\r
-\r
- put: function(key, value) {\r
- if (!cache[key]) {\r
- cache[key] = { total: 1, value: value };\r
- } else {\r
- cache[key].total++;\r
- }\r
- }\r
- };\r
-}\r
-\r
-var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {\r
- var gcsLookup = createLocalCacheLookup();\r
- var gcsStaggerLookup = createLocalCacheLookup();\r
-\r
- this.$get = ['$window', '$$jqLite', '$$AnimateRunner', '$timeout',\r
- '$document', '$sniffer', '$$rAFScheduler',\r
- function($window, $$jqLite, $$AnimateRunner, $timeout,\r
- $document, $sniffer, $$rAFScheduler) {\r
-\r
- var applyAnimationClasses = applyAnimationClassesFactory($$jqLite);\r
-\r
- var parentCounter = 0;\r
- function gcsHashFn(node, extraClasses) {\r
- var KEY = "$$ngAnimateParentKey";\r
- var parentNode = node.parentNode;\r
- var parentID = parentNode[KEY] || (parentNode[KEY] = ++parentCounter);\r
- return parentID + '-' + node.getAttribute('class') + '-' + extraClasses;\r
- }\r
-\r
- function computeCachedCssStyles(node, className, cacheKey, properties) {\r
- var timings = gcsLookup.get(cacheKey);\r
-\r
- if (!timings) {\r
- timings = computeCssStyles($window, node, properties);\r
- if (timings.animationIterationCount === 'infinite') {\r
- timings.animationIterationCount = 1;\r
- }\r
- }\r
-\r
- // we keep putting this in multiple times even though the value and the cacheKey are the same\r
- // because we're keeping an interal tally of how many duplicate animations are detected.\r
- gcsLookup.put(cacheKey, timings);\r
- return timings;\r
- }\r
-\r
- function computeCachedCssStaggerStyles(node, className, cacheKey, properties) {\r
- var stagger;\r
-\r
- // if we have one or more existing matches of matching elements\r
- // containing the same parent + CSS styles (which is how cacheKey works)\r
- // then staggering is possible\r
- if (gcsLookup.count(cacheKey) > 0) {\r
- stagger = gcsStaggerLookup.get(cacheKey);\r
-\r
- if (!stagger) {\r
- var staggerClassName = pendClasses(className, '-stagger');\r
-\r
- $$jqLite.addClass(node, staggerClassName);\r
-\r
- stagger = computeCssStyles($window, node, properties);\r
-\r
- // force the conversion of a null value to zero incase not set\r
- stagger.animationDuration = Math.max(stagger.animationDuration, 0);\r
- stagger.transitionDuration = Math.max(stagger.transitionDuration, 0);\r
-\r
- $$jqLite.removeClass(node, staggerClassName);\r
-\r
- gcsStaggerLookup.put(cacheKey, stagger);\r
- }\r
- }\r
-\r
- return stagger || {};\r
- }\r
-\r
- var bod = getDomNode($document).body;\r
- var rafWaitQueue = [];\r
- function waitUntilQuiet(callback) {\r
- rafWaitQueue.push(callback);\r
- $$rAFScheduler.waitUntilQuiet(function() {\r
- gcsLookup.flush();\r
- gcsStaggerLookup.flush();\r
-\r
- //the line below will force the browser to perform a repaint so\r
- //that all the animated elements within the animation frame will\r
- //be properly updated and drawn on screen. This is required to\r
- //ensure that the preparation animation is properly flushed so that\r
- //the active state picks up from there. DO NOT REMOVE THIS LINE.\r
- //DO NOT OPTIMIZE THIS LINE. THE MINIFIER WILL REMOVE IT OTHERWISE WHICH\r
- //WILL RESULT IN AN UNPREDICTABLE BUG THAT IS VERY HARD TO TRACK DOWN AND\r
- //WILL TAKE YEARS AWAY FROM YOUR LIFE.\r
- var width = bod.offsetWidth + 1;\r
-\r
- // we use a for loop to ensure that if the queue is changed\r
- // during this looping then it will consider new requests\r
- for (var i = 0; i < rafWaitQueue.length; i++) {\r
- rafWaitQueue[i](width);\r
- }\r
- rafWaitQueue.length = 0;\r
- });\r
- }\r
-\r
- return init;\r
-\r
- function computeTimings(node, className, cacheKey) {\r
- var timings = computeCachedCssStyles(node, className, cacheKey, DETECT_CSS_PROPERTIES);\r
- var aD = timings.animationDelay;\r
- var tD = timings.transitionDelay;\r
- timings.maxDelay = aD && tD\r
- ? Math.max(aD, tD)\r
- : (aD || tD);\r
- timings.maxDuration = Math.max(\r
- timings.animationDuration * timings.animationIterationCount,\r
- timings.transitionDuration);\r
-\r
- return timings;\r
- }\r
-\r
- function init(element, options) {\r
- var node = getDomNode(element);\r
- if (!node || !node.parentNode) {\r
- return closeAndReturnNoopAnimator();\r
- }\r
-\r
- options = prepareAnimationOptions(options);\r
-\r
- var temporaryStyles = [];\r
- var classes = element.attr('class');\r
- var styles = packageStyles(options);\r
- var animationClosed;\r
- var animationPaused;\r
- var animationCompleted;\r
- var runner;\r
- var runnerHost;\r
- var maxDelay;\r
- var maxDelayTime;\r
- var maxDuration;\r
- var maxDurationTime;\r
-\r
- if (options.duration === 0 || (!$sniffer.animations && !$sniffer.transitions)) {\r
- return closeAndReturnNoopAnimator();\r
- }\r
-\r
- var method = options.event && isArray(options.event)\r
- ? options.event.join(' ')\r
- : options.event;\r
-\r
- var isStructural = method && options.structural;\r
- var structuralClassName = '';\r
- var addRemoveClassName = '';\r
-\r
- if (isStructural) {\r
- structuralClassName = pendClasses(method, 'ng-', true);\r
- } else if (method) {\r
- structuralClassName = method;\r
- }\r
-\r
- if (options.addClass) {\r
- addRemoveClassName += pendClasses(options.addClass, '-add');\r
- }\r
-\r
- if (options.removeClass) {\r
- if (addRemoveClassName.length) {\r
- addRemoveClassName += ' ';\r
- }\r
- addRemoveClassName += pendClasses(options.removeClass, '-remove');\r
- }\r
-\r
- // there may be a situation where a structural animation is combined together\r
- // with CSS classes that need to resolve before the animation is computed.\r
- // However this means that there is no explicit CSS code to block the animation\r
- // from happening (by setting 0s none in the class name). If this is the case\r
- // we need to apply the classes before the first rAF so we know to continue if\r
- // there actually is a detected transition or keyframe animation\r
- if (options.applyClassesEarly && addRemoveClassName.length) {\r
- applyAnimationClasses(element, options);\r
- addRemoveClassName = '';\r
- }\r
-\r
- var setupClasses = [structuralClassName, addRemoveClassName].join(' ').trim();\r
- var fullClassName = classes + ' ' + setupClasses;\r
- var activeClasses = pendClasses(setupClasses, '-active');\r
- var hasToStyles = styles.to && Object.keys(styles.to).length > 0;\r
- var containsKeyframeAnimation = (options.keyframeStyle || '').length > 0;\r
-\r
- // there is no way we can trigger an animation if no styles and\r
- // no classes are being applied which would then trigger a transition,\r
- // unless there a is raw keyframe value that is applied to the element.\r
- if (!containsKeyframeAnimation\r
- && !hasToStyles\r
- && !setupClasses) {\r
- return closeAndReturnNoopAnimator();\r
- }\r
-\r
- var cacheKey, stagger;\r
- if (options.stagger > 0) {\r
- var staggerVal = parseFloat(options.stagger);\r
- stagger = {\r
- transitionDelay: staggerVal,\r
- animationDelay: staggerVal,\r
- transitionDuration: 0,\r
- animationDuration: 0\r
- };\r
- } else {\r
- cacheKey = gcsHashFn(node, fullClassName);\r
- stagger = computeCachedCssStaggerStyles(node, setupClasses, cacheKey, DETECT_STAGGER_CSS_PROPERTIES);\r
- }\r
-\r
- $$jqLite.addClass(element, setupClasses);\r
-\r
- var applyOnlyDuration;\r
-\r
- if (options.transitionStyle) {\r
- var transitionStyle = [TRANSITION_PROP, options.transitionStyle];\r
- applyInlineStyle(node, transitionStyle);\r
- temporaryStyles.push(transitionStyle);\r
- }\r
-\r
- if (options.duration >= 0) {\r
- applyOnlyDuration = node.style[TRANSITION_PROP].length > 0;\r
- var durationStyle = getCssTransitionDurationStyle(options.duration, applyOnlyDuration);\r
-\r
- // we set the duration so that it will be picked up by getComputedStyle later\r
- applyInlineStyle(node, durationStyle);\r
- temporaryStyles.push(durationStyle);\r
- }\r
-\r
- if (options.keyframeStyle) {\r
- var keyframeStyle = [ANIMATION_PROP, options.keyframeStyle];\r
- applyInlineStyle(node, keyframeStyle);\r
- temporaryStyles.push(keyframeStyle);\r
- }\r
-\r
- var itemIndex = stagger\r
- ? options.staggerIndex >= 0\r
- ? options.staggerIndex\r
- : gcsLookup.count(cacheKey)\r
- : 0;\r
-\r
- var isFirst = itemIndex === 0;\r
-\r
- // this is a pre-emptive way of forcing the setup classes to be added and applied INSTANTLY\r
- // without causing any combination of transitions to kick in. By adding a negative delay value\r
- // it forces the setup class' transition to end immediately. We later then remove the negative\r
- // transition delay to allow for the transition to naturally do it's thing. The beauty here is\r
- // that if there is no transition defined then nothing will happen and this will also allow\r
- // other transitions to be stacked on top of each other without any chopping them out.\r
- if (isFirst) {\r
- blockTransitions(node, SAFE_FAST_FORWARD_DURATION_VALUE);\r
- }\r
-\r
- var timings = computeTimings(node, fullClassName, cacheKey);\r
- var relativeDelay = timings.maxDelay;\r
- maxDelay = Math.max(relativeDelay, 0);\r
- maxDuration = timings.maxDuration;\r
-\r
- var flags = {};\r
- flags.hasTransitions = timings.transitionDuration > 0;\r
- flags.hasAnimations = timings.animationDuration > 0;\r
- flags.hasTransitionAll = flags.hasTransitions && timings.transitionProperty == 'all';\r
- flags.applyTransitionDuration = hasToStyles && (\r
- (flags.hasTransitions && !flags.hasTransitionAll)\r
- || (flags.hasAnimations && !flags.hasTransitions));\r
- flags.applyAnimationDuration = options.duration && flags.hasAnimations;\r
- flags.applyTransitionDelay = truthyTimingValue(options.delay) && (flags.applyTransitionDuration || flags.hasTransitions);\r
- flags.applyAnimationDelay = truthyTimingValue(options.delay) && flags.hasAnimations;\r
- flags.recalculateTimingStyles = addRemoveClassName.length > 0;\r
-\r
- if (flags.applyTransitionDuration || flags.applyAnimationDuration) {\r
- maxDuration = options.duration ? parseFloat(options.duration) : maxDuration;\r
-\r
- if (flags.applyTransitionDuration) {\r
- flags.hasTransitions = true;\r
- timings.transitionDuration = maxDuration;\r
- applyOnlyDuration = node.style[TRANSITION_PROP + PROPERTY_KEY].length > 0;\r
- temporaryStyles.push(getCssTransitionDurationStyle(maxDuration, applyOnlyDuration));\r
- }\r
-\r
- if (flags.applyAnimationDuration) {\r
- flags.hasAnimations = true;\r
- timings.animationDuration = maxDuration;\r
- temporaryStyles.push(getCssKeyframeDurationStyle(maxDuration));\r
- }\r
- }\r
-\r
- if (maxDuration === 0 && !flags.recalculateTimingStyles) {\r
- return closeAndReturnNoopAnimator();\r
- }\r
-\r
- // we need to recalculate the delay value since we used a pre-emptive negative\r
- // delay value and the delay value is required for the final event checking. This\r
- // property will ensure that this will happen after the RAF phase has passed.\r
- if (options.duration == null && timings.transitionDuration > 0) {\r
- flags.recalculateTimingStyles = flags.recalculateTimingStyles || isFirst;\r
- }\r
-\r
- maxDelayTime = maxDelay * ONE_SECOND;\r
- maxDurationTime = maxDuration * ONE_SECOND;\r
- if (!options.skipBlocking) {\r
- flags.blockTransition = timings.transitionDuration > 0;\r
- flags.blockKeyframeAnimation = timings.animationDuration > 0 &&\r
- stagger.animationDelay > 0 &&\r
- stagger.animationDuration === 0;\r
- }\r
-\r
- applyAnimationFromStyles(element, options);\r
- if (!flags.blockTransition) {\r
- blockTransitions(node, false);\r
- }\r
-\r
- applyBlocking(maxDuration);\r
-\r
- // TODO(matsko): for 1.5 change this code to have an animator object for better debugging\r
- return {\r
- $$willAnimate: true,\r
- end: endFn,\r
- start: function() {\r
- if (animationClosed) return;\r
-\r
- runnerHost = {\r
- end: endFn,\r
- cancel: cancelFn,\r
- resume: null, //this will be set during the start() phase\r
- pause: null\r
- };\r
-\r
- runner = new $$AnimateRunner(runnerHost);\r
-\r
- waitUntilQuiet(start);\r
-\r
- // we don't have access to pause/resume the animation\r
- // since it hasn't run yet. AnimateRunner will therefore\r
- // set noop functions for resume and pause and they will\r
- // later be overridden once the animation is triggered\r
- return runner;\r
- }\r
- };\r
-\r
- function endFn() {\r
- close();\r
- }\r
-\r
- function cancelFn() {\r
- close(true);\r
- }\r
-\r
- function close(rejected) { // jshint ignore:line\r
- // if the promise has been called already then we shouldn't close\r
- // the animation again\r
- if (animationClosed || (animationCompleted && animationPaused)) return;\r
- animationClosed = true;\r
- animationPaused = false;\r
-\r
- $$jqLite.removeClass(element, setupClasses);\r
- $$jqLite.removeClass(element, activeClasses);\r
-\r
- blockKeyframeAnimations(node, false);\r
- blockTransitions(node, false);\r
-\r
- forEach(temporaryStyles, function(entry) {\r
- // There is only one way to remove inline style properties entirely from elements.\r
- // By using `removeProperty` this works, but we need to convert camel-cased CSS\r
- // styles down to hyphenated values.\r
- node.style[entry[0]] = '';\r
- });\r
-\r
- applyAnimationClasses(element, options);\r
- applyAnimationStyles(element, options);\r
-\r
- // the reason why we have this option is to allow a synchronous closing callback\r
- // that is fired as SOON as the animation ends (when the CSS is removed) or if\r
- // the animation never takes off at all. A good example is a leave animation since\r
- // the element must be removed just after the animation is over or else the element\r
- // will appear on screen for one animation frame causing an overbearing flicker.\r
- if (options.onDone) {\r
- options.onDone();\r
- }\r
-\r
- // if the preparation function fails then the promise is not setup\r
- if (runner) {\r
- runner.complete(!rejected);\r
- }\r
- }\r
-\r
- function applyBlocking(duration) {\r
- if (flags.blockTransition) {\r
- blockTransitions(node, duration);\r
- }\r
-\r
- if (flags.blockKeyframeAnimation) {\r
- blockKeyframeAnimations(node, !!duration);\r
- }\r
- }\r
-\r
- function closeAndReturnNoopAnimator() {\r
- runner = new $$AnimateRunner({\r
- end: endFn,\r
- cancel: cancelFn\r
- });\r
-\r
- close();\r
-\r
- return {\r
- $$willAnimate: false,\r
- start: function() {\r
- return runner;\r
- },\r
- end: endFn\r
- };\r
- }\r
-\r
- function start() {\r
- if (animationClosed) return;\r
- if (!node.parentNode) {\r
- close();\r
- return;\r
- }\r
-\r
- var startTime, events = [];\r
-\r
- // even though we only pause keyframe animations here the pause flag\r
- // will still happen when transitions are used. Only the transition will\r
- // not be paused since that is not possible. If the animation ends when\r
- // paused then it will not complete until unpaused or cancelled.\r
- var playPause = function(playAnimation) {\r
- if (!animationCompleted) {\r
- animationPaused = !playAnimation;\r
- if (timings.animationDuration) {\r
- var value = blockKeyframeAnimations(node, animationPaused);\r
- animationPaused\r
- ? temporaryStyles.push(value)\r
- : removeFromArray(temporaryStyles, value);\r
- }\r
- } else if (animationPaused && playAnimation) {\r
- animationPaused = false;\r
- close();\r
- }\r
- };\r
-\r
- // checking the stagger duration prevents an accidently cascade of the CSS delay style\r
- // being inherited from the parent. If the transition duration is zero then we can safely\r
- // rely that the delay value is an intential stagger delay style.\r
- var maxStagger = itemIndex > 0\r
- && ((timings.transitionDuration && stagger.transitionDuration === 0) ||\r
- (timings.animationDuration && stagger.animationDuration === 0))\r
- && Math.max(stagger.animationDelay, stagger.transitionDelay);\r
- if (maxStagger) {\r
- $timeout(triggerAnimationStart,\r
- Math.floor(maxStagger * itemIndex * ONE_SECOND),\r
- false);\r
- } else {\r
- triggerAnimationStart();\r
- }\r
-\r
- // this will decorate the existing promise runner with pause/resume methods\r
- runnerHost.resume = function() {\r
- playPause(true);\r
- };\r
-\r
- runnerHost.pause = function() {\r
- playPause(false);\r
- };\r
-\r
- function triggerAnimationStart() {\r
- // just incase a stagger animation kicks in when the animation\r
- // itself was cancelled entirely\r
- if (animationClosed) return;\r
-\r
- applyBlocking(false);\r
-\r
- forEach(temporaryStyles, function(entry) {\r
- var key = entry[0];\r
- var value = entry[1];\r
- node.style[key] = value;\r
- });\r
-\r
- applyAnimationClasses(element, options);\r
- $$jqLite.addClass(element, activeClasses);\r
-\r
- if (flags.recalculateTimingStyles) {\r
- fullClassName = node.className + ' ' + setupClasses;\r
- cacheKey = gcsHashFn(node, fullClassName);\r
-\r
- timings = computeTimings(node, fullClassName, cacheKey);\r
- relativeDelay = timings.maxDelay;\r
- maxDelay = Math.max(relativeDelay, 0);\r
- maxDuration = timings.maxDuration;\r
-\r
- if (maxDuration === 0) {\r
- close();\r
- return;\r
- }\r
-\r
- flags.hasTransitions = timings.transitionDuration > 0;\r
- flags.hasAnimations = timings.animationDuration > 0;\r
- }\r
-\r
- if (flags.applyTransitionDelay || flags.applyAnimationDelay) {\r
- relativeDelay = typeof options.delay !== "boolean" && truthyTimingValue(options.delay)\r
- ? parseFloat(options.delay)\r
- : relativeDelay;\r
-\r
- maxDelay = Math.max(relativeDelay, 0);\r
-\r
- var delayStyle;\r
- if (flags.applyTransitionDelay) {\r
- timings.transitionDelay = relativeDelay;\r
- delayStyle = getCssDelayStyle(relativeDelay);\r
- temporaryStyles.push(delayStyle);\r
- node.style[delayStyle[0]] = delayStyle[1];\r
- }\r
-\r
- if (flags.applyAnimationDelay) {\r
- timings.animationDelay = relativeDelay;\r
- delayStyle = getCssDelayStyle(relativeDelay, true);\r
- temporaryStyles.push(delayStyle);\r
- node.style[delayStyle[0]] = delayStyle[1];\r
- }\r
- }\r
-\r
- maxDelayTime = maxDelay * ONE_SECOND;\r
- maxDurationTime = maxDuration * ONE_SECOND;\r
-\r
- if (options.easing) {\r
- var easeProp, easeVal = options.easing;\r
- if (flags.hasTransitions) {\r
- easeProp = TRANSITION_PROP + TIMING_KEY;\r
- temporaryStyles.push([easeProp, easeVal]);\r
- node.style[easeProp] = easeVal;\r
- }\r
- if (flags.hasAnimations) {\r
- easeProp = ANIMATION_PROP + TIMING_KEY;\r
- temporaryStyles.push([easeProp, easeVal]);\r
- node.style[easeProp] = easeVal;\r
- }\r
- }\r
-\r
- if (timings.transitionDuration) {\r
- events.push(TRANSITIONEND_EVENT);\r
- }\r
-\r
- if (timings.animationDuration) {\r
- events.push(ANIMATIONEND_EVENT);\r
- }\r
-\r
- startTime = Date.now();\r
- element.on(events.join(' '), onAnimationProgress);\r
- $timeout(onAnimationExpired, maxDelayTime + CLOSING_TIME_BUFFER * maxDurationTime);\r
-\r
- applyAnimationToStyles(element, options);\r
- }\r
-\r
- function onAnimationExpired() {\r
- // although an expired animation is a failed animation, getting to\r
- // this outcome is very easy if the CSS code screws up. Therefore we\r
- // should still continue normally as if the animation completed correctly.\r
- close();\r
- }\r
-\r
- function onAnimationProgress(event) {\r
- event.stopPropagation();\r
- var ev = event.originalEvent || event;\r
- var timeStamp = ev.$manualTimeStamp || ev.timeStamp || Date.now();\r
-\r
- /* Firefox (or possibly just Gecko) likes to not round values up\r
- * when a ms measurement is used for the animation */\r
- var elapsedTime = parseFloat(ev.elapsedTime.toFixed(ELAPSED_TIME_MAX_DECIMAL_PLACES));\r
-\r
- /* $manualTimeStamp is a mocked timeStamp value which is set\r
- * within browserTrigger(). This is only here so that tests can\r
- * mock animations properly. Real events fallback to event.timeStamp,\r
- * or, if they don't, then a timeStamp is automatically created for them.\r
- * We're checking to see if the timeStamp surpasses the expected delay,\r
- * but we're using elapsedTime instead of the timeStamp on the 2nd\r
- * pre-condition since animations sometimes close off early */\r
- if (Math.max(timeStamp - startTime, 0) >= maxDelayTime && elapsedTime >= maxDuration) {\r
- // we set this flag to ensure that if the transition is paused then, when resumed,\r
- // the animation will automatically close itself since transitions cannot be paused.\r
- animationCompleted = true;\r
- close();\r
- }\r
- }\r
- }\r
- }\r
- }];\r
-}];\r
-\r
-var $$AnimateCssDriverProvider = ['$$animationProvider', function($$animationProvider) {\r
- $$animationProvider.drivers.push('$$animateCssDriver');\r
-\r
- var NG_ANIMATE_SHIM_CLASS_NAME = 'ng-animate-shim';\r
- var NG_ANIMATE_ANCHOR_CLASS_NAME = 'ng-anchor';\r
-\r
- var NG_OUT_ANCHOR_CLASS_NAME = 'ng-anchor-out';\r
- var NG_IN_ANCHOR_CLASS_NAME = 'ng-anchor-in';\r
-\r
- this.$get = ['$animateCss', '$rootScope', '$$AnimateRunner', '$rootElement', '$document', '$sniffer',\r
- function($animateCss, $rootScope, $$AnimateRunner, $rootElement, $document, $sniffer) {\r
-\r
- // only browsers that support these properties can render animations\r
- if (!$sniffer.animations && !$sniffer.transitions) return noop;\r
-\r
- var bodyNode = getDomNode($document).body;\r
- var rootNode = getDomNode($rootElement);\r
-\r
- var rootBodyElement = jqLite(bodyNode.parentNode === rootNode ? bodyNode : rootNode);\r
-\r
- return function initDriverFn(animationDetails) {\r
- return animationDetails.from && animationDetails.to\r
- ? prepareFromToAnchorAnimation(animationDetails.from,\r
- animationDetails.to,\r
- animationDetails.classes,\r
- animationDetails.anchors)\r
- : prepareRegularAnimation(animationDetails);\r
- };\r
-\r
- function filterCssClasses(classes) {\r
- //remove all the `ng-` stuff\r
- return classes.replace(/\bng-\S+\b/g, '');\r
- }\r
-\r
- function getUniqueValues(a, b) {\r
- if (isString(a)) a = a.split(' ');\r
- if (isString(b)) b = b.split(' ');\r
- return a.filter(function(val) {\r
- return b.indexOf(val) === -1;\r
- }).join(' ');\r
- }\r
-\r
- function prepareAnchoredAnimation(classes, outAnchor, inAnchor) {\r
- var clone = jqLite(getDomNode(outAnchor).cloneNode(true));\r
- var startingClasses = filterCssClasses(getClassVal(clone));\r
-\r
- outAnchor.addClass(NG_ANIMATE_SHIM_CLASS_NAME);\r
- inAnchor.addClass(NG_ANIMATE_SHIM_CLASS_NAME);\r
-\r
- clone.addClass(NG_ANIMATE_ANCHOR_CLASS_NAME);\r
-\r
- rootBodyElement.append(clone);\r
-\r
- var animatorIn, animatorOut = prepareOutAnimation();\r
-\r
- // the user may not end up using the `out` animation and\r
- // only making use of the `in` animation or vice-versa.\r
- // In either case we should allow this and not assume the\r
- // animation is over unless both animations are not used.\r
- if (!animatorOut) {\r
- animatorIn = prepareInAnimation();\r
- if (!animatorIn) {\r
- return end();\r
- }\r
- }\r
-\r
- var startingAnimator = animatorOut || animatorIn;\r
-\r
- return {\r
- start: function() {\r
- var runner;\r
-\r
- var currentAnimation = startingAnimator.start();\r
- currentAnimation.done(function() {\r
- currentAnimation = null;\r
- if (!animatorIn) {\r
- animatorIn = prepareInAnimation();\r
- if (animatorIn) {\r
- currentAnimation = animatorIn.start();\r
- currentAnimation.done(function() {\r
- currentAnimation = null;\r
- end();\r
- runner.complete();\r
- });\r
- return currentAnimation;\r
- }\r
- }\r
- // in the event that there is no `in` animation\r
- end();\r
- runner.complete();\r
- });\r
-\r
- runner = new $$AnimateRunner({\r
- end: endFn,\r
- cancel: endFn\r
- });\r
-\r
- return runner;\r
-\r
- function endFn() {\r
- if (currentAnimation) {\r
- currentAnimation.end();\r
- }\r
- }\r
- }\r
- };\r
-\r
- function calculateAnchorStyles(anchor) {\r
- var styles = {};\r
-\r
- var coords = getDomNode(anchor).getBoundingClientRect();\r
-\r
- // we iterate directly since safari messes up and doesn't return\r
- // all the keys for the coods object when iterated\r
- forEach(['width','height','top','left'], function(key) {\r
- var value = coords[key];\r
- switch (key) {\r
- case 'top':\r
- value += bodyNode.scrollTop;\r
- break;\r
- case 'left':\r
- value += bodyNode.scrollLeft;\r
- break;\r
- }\r
- styles[key] = Math.floor(value) + 'px';\r
- });\r
- return styles;\r
- }\r
-\r
- function prepareOutAnimation() {\r
- var animator = $animateCss(clone, {\r
- addClass: NG_OUT_ANCHOR_CLASS_NAME,\r
- delay: true,\r
- from: calculateAnchorStyles(outAnchor)\r
- });\r
-\r
- // read the comment within `prepareRegularAnimation` to understand\r
- // why this check is necessary\r
- return animator.$$willAnimate ? animator : null;\r
- }\r
-\r
- function getClassVal(element) {\r
- return element.attr('class') || '';\r
- }\r
-\r
- function prepareInAnimation() {\r
- var endingClasses = filterCssClasses(getClassVal(inAnchor));\r
- var toAdd = getUniqueValues(endingClasses, startingClasses);\r
- var toRemove = getUniqueValues(startingClasses, endingClasses);\r
-\r
- var animator = $animateCss(clone, {\r
- to: calculateAnchorStyles(inAnchor),\r
- addClass: NG_IN_ANCHOR_CLASS_NAME + ' ' + toAdd,\r
- removeClass: NG_OUT_ANCHOR_CLASS_NAME + ' ' + toRemove,\r
- delay: true\r
- });\r
-\r
- // read the comment within `prepareRegularAnimation` to understand\r
- // why this check is necessary\r
- return animator.$$willAnimate ? animator : null;\r
- }\r
-\r
- function end() {\r
- clone.remove();\r
- outAnchor.removeClass(NG_ANIMATE_SHIM_CLASS_NAME);\r
- inAnchor.removeClass(NG_ANIMATE_SHIM_CLASS_NAME);\r
- }\r
- }\r
-\r
- function prepareFromToAnchorAnimation(from, to, classes, anchors) {\r
- var fromAnimation = prepareRegularAnimation(from);\r
- var toAnimation = prepareRegularAnimation(to);\r
-\r
- var anchorAnimations = [];\r
- forEach(anchors, function(anchor) {\r
- var outElement = anchor['out'];\r
- var inElement = anchor['in'];\r
- var animator = prepareAnchoredAnimation(classes, outElement, inElement);\r
- if (animator) {\r
- anchorAnimations.push(animator);\r
- }\r
- });\r
-\r
- // no point in doing anything when there are no elements to animate\r
- if (!fromAnimation && !toAnimation && anchorAnimations.length === 0) return;\r
-\r
- return {\r
- start: function() {\r
- var animationRunners = [];\r
-\r
- if (fromAnimation) {\r
- animationRunners.push(fromAnimation.start());\r
- }\r
-\r
- if (toAnimation) {\r
- animationRunners.push(toAnimation.start());\r
- }\r
-\r
- forEach(anchorAnimations, function(animation) {\r
- animationRunners.push(animation.start());\r
- });\r
-\r
- var runner = new $$AnimateRunner({\r
- end: endFn,\r
- cancel: endFn // CSS-driven animations cannot be cancelled, only ended\r
- });\r
-\r
- $$AnimateRunner.all(animationRunners, function(status) {\r
- runner.complete(status);\r
- });\r
-\r
- return runner;\r
-\r
- function endFn() {\r
- forEach(animationRunners, function(runner) {\r
- runner.end();\r
- });\r
- }\r
- }\r
- };\r
- }\r
-\r
- function prepareRegularAnimation(animationDetails) {\r
- var element = animationDetails.element;\r
- var options = animationDetails.options || {};\r
-\r
- if (animationDetails.structural) {\r
- // structural animations ensure that the CSS classes are always applied\r
- // before the detection starts.\r
- options.structural = options.applyClassesEarly = true;\r
-\r
- // we special case the leave animation since we want to ensure that\r
- // the element is removed as soon as the animation is over. Otherwise\r
- // a flicker might appear or the element may not be removed at all\r
- options.event = animationDetails.event;\r
- if (options.event === 'leave') {\r
- options.onDone = options.domOperation;\r
- }\r
- } else {\r
- options.event = null;\r
- }\r
-\r
- var animator = $animateCss(element, options);\r
-\r
- // the driver lookup code inside of $$animation attempts to spawn a\r
- // driver one by one until a driver returns a.$$willAnimate animator object.\r
- // $animateCss will always return an object, however, it will pass in\r
- // a flag as a hint as to whether an animation was detected or not\r
- return animator.$$willAnimate ? animator : null;\r
- }\r
- }];\r
-}];\r
-\r
-// TODO(matsko): use caching here to speed things up for detection\r
-// TODO(matsko): add documentation\r
-// by the time...\r
-\r
-var $$AnimateJsProvider = ['$animateProvider', function($animateProvider) {\r
- this.$get = ['$injector', '$$AnimateRunner', '$$rAFMutex', '$$jqLite',\r
- function($injector, $$AnimateRunner, $$rAFMutex, $$jqLite) {\r
-\r
- var applyAnimationClasses = applyAnimationClassesFactory($$jqLite);\r
- // $animateJs(element, 'enter');\r
- return function(element, event, classes, options) {\r
- // the `classes` argument is optional and if it is not used\r
- // then the classes will be resolved from the element's className\r
- // property as well as options.addClass/options.removeClass.\r
- if (arguments.length === 3 && isObject(classes)) {\r
- options = classes;\r
- classes = null;\r
- }\r
-\r
- options = prepareAnimationOptions(options);\r
- if (!classes) {\r
- classes = element.attr('class') || '';\r
- if (options.addClass) {\r
- classes += ' ' + options.addClass;\r
- }\r
- if (options.removeClass) {\r
- classes += ' ' + options.removeClass;\r
- }\r
- }\r
-\r
- var classesToAdd = options.addClass;\r
- var classesToRemove = options.removeClass;\r
-\r
- // the lookupAnimations function returns a series of animation objects that are\r
- // matched up with one or more of the CSS classes. These animation objects are\r
- // defined via the module.animation factory function. If nothing is detected then\r
- // we don't return anything which then makes $animation query the next driver.\r
- var animations = lookupAnimations(classes);\r
- var before, after;\r
- if (animations.length) {\r
- var afterFn, beforeFn;\r
- if (event == 'leave') {\r
- beforeFn = 'leave';\r
- afterFn = 'afterLeave'; // TODO(matsko): get rid of this\r
- } else {\r
- beforeFn = 'before' + event.charAt(0).toUpperCase() + event.substr(1);\r
- afterFn = event;\r
- }\r
-\r
- if (event !== 'enter' && event !== 'move') {\r
- before = packageAnimations(element, event, options, animations, beforeFn);\r
- }\r
- after = packageAnimations(element, event, options, animations, afterFn);\r
- }\r
-\r
- // no matching animations\r
- if (!before && !after) return;\r
-\r
- function applyOptions() {\r
- options.domOperation();\r
- applyAnimationClasses(element, options);\r
- }\r
-\r
- return {\r
- start: function() {\r
- var closeActiveAnimations;\r
- var chain = [];\r
-\r
- if (before) {\r
- chain.push(function(fn) {\r
- closeActiveAnimations = before(fn);\r
- });\r
- }\r
-\r
- if (chain.length) {\r
- chain.push(function(fn) {\r
- applyOptions();\r
- fn(true);\r
- });\r
- } else {\r
- applyOptions();\r
- }\r
-\r
- if (after) {\r
- chain.push(function(fn) {\r
- closeActiveAnimations = after(fn);\r
- });\r
- }\r
-\r
- var animationClosed = false;\r
- var runner = new $$AnimateRunner({\r
- end: function() {\r
- endAnimations();\r
- },\r
- cancel: function() {\r
- endAnimations(true);\r
- }\r
- });\r
-\r
- $$AnimateRunner.chain(chain, onComplete);\r
- return runner;\r
-\r
- function onComplete(success) {\r
- animationClosed = true;\r
- applyOptions();\r
- applyAnimationStyles(element, options);\r
- runner.complete(success);\r
- }\r
-\r
- function endAnimations(cancelled) {\r
- if (!animationClosed) {\r
- (closeActiveAnimations || noop)(cancelled);\r
- onComplete(cancelled);\r
- }\r
- }\r
- }\r
- };\r
-\r
- function executeAnimationFn(fn, element, event, options, onDone) {\r
- var args;\r
- switch (event) {\r
- case 'animate':\r
- args = [element, options.from, options.to, onDone];\r
- break;\r
-\r
- case 'setClass':\r
- args = [element, classesToAdd, classesToRemove, onDone];\r
- break;\r
-\r
- case 'addClass':\r
- args = [element, classesToAdd, onDone];\r
- break;\r
-\r
- case 'removeClass':\r
- args = [element, classesToRemove, onDone];\r
- break;\r
-\r
- default:\r
- args = [element, onDone];\r
- break;\r
- }\r
-\r
- args.push(options);\r
-\r
- var value = fn.apply(fn, args);\r
- if (value) {\r
- if (isFunction(value.start)) {\r
- value = value.start();\r
- }\r
-\r
- if (value instanceof $$AnimateRunner) {\r
- value.done(onDone);\r
- } else if (isFunction(value)) {\r
- // optional onEnd / onCancel callback\r
- return value;\r
- }\r
- }\r
-\r
- return noop;\r
- }\r
-\r
- function groupEventedAnimations(element, event, options, animations, fnName) {\r
- var operations = [];\r
- forEach(animations, function(ani) {\r
- var animation = ani[fnName];\r
- if (!animation) return;\r
-\r
- // note that all of these animations will run in parallel\r
- operations.push(function() {\r
- var runner;\r
- var endProgressCb;\r
-\r
- var resolved = false;\r
- var onAnimationComplete = function(rejected) {\r
- if (!resolved) {\r
- resolved = true;\r
- (endProgressCb || noop)(rejected);\r
- runner.complete(!rejected);\r
- }\r
- };\r
-\r
- runner = new $$AnimateRunner({\r
- end: function() {\r
- onAnimationComplete();\r
- },\r
- cancel: function() {\r
- onAnimationComplete(true);\r
- }\r
- });\r
-\r
- endProgressCb = executeAnimationFn(animation, element, event, options, function(result) {\r
- var cancelled = result === false;\r
- onAnimationComplete(cancelled);\r
- });\r
-\r
- return runner;\r
- });\r
- });\r
-\r
- return operations;\r
- }\r
-\r
- function packageAnimations(element, event, options, animations, fnName) {\r
- var operations = groupEventedAnimations(element, event, options, animations, fnName);\r
- if (operations.length === 0) {\r
- var a,b;\r
- if (fnName === 'beforeSetClass') {\r
- a = groupEventedAnimations(element, 'removeClass', options, animations, 'beforeRemoveClass');\r
- b = groupEventedAnimations(element, 'addClass', options, animations, 'beforeAddClass');\r
- } else if (fnName === 'setClass') {\r
- a = groupEventedAnimations(element, 'removeClass', options, animations, 'removeClass');\r
- b = groupEventedAnimations(element, 'addClass', options, animations, 'addClass');\r
- }\r
-\r
- if (a) {\r
- operations = operations.concat(a);\r
- }\r
- if (b) {\r
- operations = operations.concat(b);\r
- }\r
- }\r
-\r
- if (operations.length === 0) return;\r
-\r
- // TODO(matsko): add documentation\r
- return function startAnimation(callback) {\r
- var runners = [];\r
- if (operations.length) {\r
- forEach(operations, function(animateFn) {\r
- runners.push(animateFn());\r
- });\r
- }\r
-\r
- runners.length ? $$AnimateRunner.all(runners, callback) : callback();\r
-\r
- return function endFn(reject) {\r
- forEach(runners, function(runner) {\r
- reject ? runner.cancel() : runner.end();\r
- });\r
- };\r
- };\r
- }\r
- };\r
-\r
- function lookupAnimations(classes) {\r
- classes = isArray(classes) ? classes : classes.split(' ');\r
- var matches = [], flagMap = {};\r
- for (var i=0; i < classes.length; i++) {\r
- var klass = classes[i],\r
- animationFactory = $animateProvider.$$registeredAnimations[klass];\r
- if (animationFactory && !flagMap[klass]) {\r
- matches.push($injector.get(animationFactory));\r
- flagMap[klass] = true;\r
- }\r
- }\r
- return matches;\r
- }\r
- }];\r
-}];\r
-\r
-var $$AnimateJsDriverProvider = ['$$animationProvider', function($$animationProvider) {\r
- $$animationProvider.drivers.push('$$animateJsDriver');\r
- this.$get = ['$$animateJs', '$$AnimateRunner', function($$animateJs, $$AnimateRunner) {\r
- return function initDriverFn(animationDetails) {\r
- if (animationDetails.from && animationDetails.to) {\r
- var fromAnimation = prepareAnimation(animationDetails.from);\r
- var toAnimation = prepareAnimation(animationDetails.to);\r
- if (!fromAnimation && !toAnimation) return;\r
-\r
- return {\r
- start: function() {\r
- var animationRunners = [];\r
-\r
- if (fromAnimation) {\r
- animationRunners.push(fromAnimation.start());\r
- }\r
-\r
- if (toAnimation) {\r
- animationRunners.push(toAnimation.start());\r
- }\r
-\r
- $$AnimateRunner.all(animationRunners, done);\r
-\r
- var runner = new $$AnimateRunner({\r
- end: endFnFactory(),\r
- cancel: endFnFactory()\r
- });\r
-\r
- return runner;\r
-\r
- function endFnFactory() {\r
- return function() {\r
- forEach(animationRunners, function(runner) {\r
- // at this point we cannot cancel animations for groups just yet. 1.5+\r
- runner.end();\r
- });\r
- };\r
- }\r
-\r
- function done(status) {\r
- runner.complete(status);\r
- }\r
- }\r
- };\r
- } else {\r
- return prepareAnimation(animationDetails);\r
- }\r
- };\r
-\r
- function prepareAnimation(animationDetails) {\r
- // TODO(matsko): make sure to check for grouped animations and delegate down to normal animations\r
- var element = animationDetails.element;\r
- var event = animationDetails.event;\r
- var options = animationDetails.options;\r
- var classes = animationDetails.classes;\r
- return $$animateJs(element, event, classes, options);\r
- }\r
- }];\r
-}];\r
-\r
-var NG_ANIMATE_ATTR_NAME = 'data-ng-animate';\r
-var NG_ANIMATE_PIN_DATA = '$ngAnimatePin';\r
-var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {\r
- var PRE_DIGEST_STATE = 1;\r
- var RUNNING_STATE = 2;\r
-\r
- var rules = this.rules = {\r
- skip: [],\r
- cancel: [],\r
- join: []\r
- };\r
-\r
- function isAllowed(ruleType, element, currentAnimation, previousAnimation) {\r
- return rules[ruleType].some(function(fn) {\r
- return fn(element, currentAnimation, previousAnimation);\r
- });\r
- }\r
-\r
- function hasAnimationClasses(options, and) {\r
- options = options || {};\r
- var a = (options.addClass || '').length > 0;\r
- var b = (options.removeClass || '').length > 0;\r
- return and ? a && b : a || b;\r
- }\r
-\r
- rules.join.push(function(element, newAnimation, currentAnimation) {\r
- // if the new animation is class-based then we can just tack that on\r
- return !newAnimation.structural && hasAnimationClasses(newAnimation.options);\r
- });\r
-\r
- rules.skip.push(function(element, newAnimation, currentAnimation) {\r
- // there is no need to animate anything if no classes are being added and\r
- // there is no structural animation that will be triggered\r
- return !newAnimation.structural && !hasAnimationClasses(newAnimation.options);\r
- });\r
-\r
- rules.skip.push(function(element, newAnimation, currentAnimation) {\r
- // why should we trigger a new structural animation if the element will\r
- // be removed from the DOM anyway?\r
- return currentAnimation.event == 'leave' && newAnimation.structural;\r
- });\r
-\r
- rules.skip.push(function(element, newAnimation, currentAnimation) {\r
- // if there is a current animation then skip the class-based animation\r
- return currentAnimation.structural && !newAnimation.structural;\r
- });\r
-\r
- rules.cancel.push(function(element, newAnimation, currentAnimation) {\r
- // there can never be two structural animations running at the same time\r
- return currentAnimation.structural && newAnimation.structural;\r
- });\r
-\r
- rules.cancel.push(function(element, newAnimation, currentAnimation) {\r
- // if the previous animation is already running, but the new animation will\r
- // be triggered, but the new animation is structural\r
- return currentAnimation.state === RUNNING_STATE && newAnimation.structural;\r
- });\r
-\r
- rules.cancel.push(function(element, newAnimation, currentAnimation) {\r
- var nO = newAnimation.options;\r
- var cO = currentAnimation.options;\r
-\r
- // if the exact same CSS class is added/removed then it's safe to cancel it\r
- return (nO.addClass && nO.addClass === cO.removeClass) || (nO.removeClass && nO.removeClass === cO.addClass);\r
- });\r
-\r
- this.$get = ['$$rAF', '$rootScope', '$rootElement', '$document', '$$HashMap',\r
- '$$animation', '$$AnimateRunner', '$templateRequest', '$$jqLite',\r
- function($$rAF, $rootScope, $rootElement, $document, $$HashMap,\r
- $$animation, $$AnimateRunner, $templateRequest, $$jqLite) {\r
-\r
- var activeAnimationsLookup = new $$HashMap();\r
- var disabledElementsLookup = new $$HashMap();\r
-\r
- var animationsEnabled = null;\r
-\r
- // Wait until all directive and route-related templates are downloaded and\r
- // compiled. The $templateRequest.totalPendingRequests variable keeps track of\r
- // all of the remote templates being currently downloaded. If there are no\r
- // templates currently downloading then the watcher will still fire anyway.\r
- var deregisterWatch = $rootScope.$watch(\r
- function() { return $templateRequest.totalPendingRequests === 0; },\r
- function(isEmpty) {\r
- if (!isEmpty) return;\r
- deregisterWatch();\r
-\r
- // Now that all templates have been downloaded, $animate will wait until\r
- // the post digest queue is empty before enabling animations. By having two\r
- // calls to $postDigest calls we can ensure that the flag is enabled at the\r
- // very end of the post digest queue. Since all of the animations in $animate\r
- // use $postDigest, it's important that the code below executes at the end.\r
- // This basically means that the page is fully downloaded and compiled before\r
- // any animations are triggered.\r
- $rootScope.$$postDigest(function() {\r
- $rootScope.$$postDigest(function() {\r
- // we check for null directly in the event that the application already called\r
- // .enabled() with whatever arguments that it provided it with\r
- if (animationsEnabled === null) {\r
- animationsEnabled = true;\r
- }\r
- });\r
- });\r
- }\r
- );\r
-\r
- var bodyElement = jqLite($document[0].body);\r
-\r
- var callbackRegistry = {};\r
-\r
- // remember that the classNameFilter is set during the provider/config\r
- // stage therefore we can optimize here and setup a helper function\r
- var classNameFilter = $animateProvider.classNameFilter();\r
- var isAnimatableClassName = !classNameFilter\r
- ? function() { return true; }\r
- : function(className) {\r
- return classNameFilter.test(className);\r
- };\r
-\r
- var applyAnimationClasses = applyAnimationClassesFactory($$jqLite);\r
-\r
- function normalizeAnimationOptions(element, options) {\r
- return mergeAnimationOptions(element, options, {});\r
- }\r
-\r
- function findCallbacks(element, event) {\r
- var targetNode = getDomNode(element);\r
-\r
- var matches = [];\r
- var entries = callbackRegistry[event];\r
- if (entries) {\r
- forEach(entries, function(entry) {\r
- if (entry.node.contains(targetNode)) {\r
- matches.push(entry.callback);\r
- }\r
- });\r
- }\r
-\r
- return matches;\r
- }\r
-\r
- function triggerCallback(event, element, phase, data) {\r
- $$rAF(function() {\r
- forEach(findCallbacks(element, event), function(callback) {\r
- callback(element, phase, data);\r
- });\r
- });\r
- }\r
-\r
- return {\r
- on: function(event, container, callback) {\r
- var node = extractElementNode(container);\r
- callbackRegistry[event] = callbackRegistry[event] || [];\r
- callbackRegistry[event].push({\r
- node: node,\r
- callback: callback\r
- });\r
- },\r
-\r
- off: function(event, container, callback) {\r
- var entries = callbackRegistry[event];\r
- if (!entries) return;\r
-\r
- callbackRegistry[event] = arguments.length === 1\r
- ? null\r
- : filterFromRegistry(entries, container, callback);\r
-\r
- function filterFromRegistry(list, matchContainer, matchCallback) {\r
- var containerNode = extractElementNode(matchContainer);\r
- return list.filter(function(entry) {\r
- var isMatch = entry.node === containerNode &&\r
- (!matchCallback || entry.callback === matchCallback);\r
- return !isMatch;\r
- });\r
- }\r
- },\r
-\r
- pin: function(element, parentElement) {\r
- assertArg(isElement(element), 'element', 'not an element');\r
- assertArg(isElement(parentElement), 'parentElement', 'not an element');\r
- element.data(NG_ANIMATE_PIN_DATA, parentElement);\r
- },\r
-\r
- push: function(element, event, options, domOperation) {\r
- options = options || {};\r
- options.domOperation = domOperation;\r
- return queueAnimation(element, event, options);\r
- },\r
-\r
- // this method has four signatures:\r
- // () - global getter\r
- // (bool) - global setter\r
- // (element) - element getter\r
- // (element, bool) - element setter<F37>\r
- enabled: function(element, bool) {\r
- var argCount = arguments.length;\r
-\r
- if (argCount === 0) {\r
- // () - Global getter\r
- bool = !!animationsEnabled;\r
- } else {\r
- var hasElement = isElement(element);\r
-\r
- if (!hasElement) {\r
- // (bool) - Global setter\r
- bool = animationsEnabled = !!element;\r
- } else {\r
- var node = getDomNode(element);\r
- var recordExists = disabledElementsLookup.get(node);\r
-\r
- if (argCount === 1) {\r
- // (element) - Element getter\r
- bool = !recordExists;\r
- } else {\r
- // (element, bool) - Element setter\r
- bool = !!bool;\r
- if (!bool) {\r
- disabledElementsLookup.put(node, true);\r
- } else if (recordExists) {\r
- disabledElementsLookup.remove(node);\r
- }\r
- }\r
- }\r
- }\r
-\r
- return bool;\r
- }\r
- };\r
-\r
- function queueAnimation(element, event, options) {\r
- var node, parent;\r
- element = stripCommentsFromElement(element);\r
- if (element) {\r
- node = getDomNode(element);\r
- parent = element.parent();\r
- }\r
-\r
- options = prepareAnimationOptions(options);\r
-\r
- // we create a fake runner with a working promise.\r
- // These methods will become available after the digest has passed\r
- var runner = new $$AnimateRunner();\r
-\r
- // there are situations where a directive issues an animation for\r
- // a jqLite wrapper that contains only comment nodes... If this\r
- // happens then there is no way we can perform an animation\r
- if (!node) {\r
- close();\r
- return runner;\r
- }\r
-\r
- if (isArray(options.addClass)) {\r
- options.addClass = options.addClass.join(' ');\r
- }\r
-\r
- if (isArray(options.removeClass)) {\r
- options.removeClass = options.removeClass.join(' ');\r
- }\r
-\r
- if (options.from && !isObject(options.from)) {\r
- options.from = null;\r
- }\r
-\r
- if (options.to && !isObject(options.to)) {\r
- options.to = null;\r
- }\r
-\r
- var className = [node.className, options.addClass, options.removeClass].join(' ');\r
- if (!isAnimatableClassName(className)) {\r
- close();\r
- return runner;\r
- }\r
-\r
- var isStructural = ['enter', 'move', 'leave'].indexOf(event) >= 0;\r
-\r
- // this is a hard disable of all animations for the application or on\r
- // the element itself, therefore there is no need to continue further\r
- // past this point if not enabled\r
- var skipAnimations = !animationsEnabled || disabledElementsLookup.get(node);\r
- var existingAnimation = (!skipAnimations && activeAnimationsLookup.get(node)) || {};\r
- var hasExistingAnimation = !!existingAnimation.state;\r
-\r
- // there is no point in traversing the same collection of parent ancestors if a followup\r
- // animation will be run on the same element that already did all that checking work\r
- if (!skipAnimations && (!hasExistingAnimation || existingAnimation.state != PRE_DIGEST_STATE)) {\r
- skipAnimations = !areAnimationsAllowed(element, parent, event);\r
- }\r
-\r
- if (skipAnimations) {\r
- close();\r
- return runner;\r
- }\r
-\r
- if (isStructural) {\r
- closeChildAnimations(element);\r
- }\r
-\r
- var newAnimation = {\r
- structural: isStructural,\r
- element: element,\r
- event: event,\r
- close: close,\r
- options: options,\r
- runner: runner\r
- };\r
-\r
- if (hasExistingAnimation) {\r
- var skipAnimationFlag = isAllowed('skip', element, newAnimation, existingAnimation);\r
- if (skipAnimationFlag) {\r
- if (existingAnimation.state === RUNNING_STATE) {\r
- close();\r
- return runner;\r
- } else {\r
- mergeAnimationOptions(element, existingAnimation.options, options);\r
- return existingAnimation.runner;\r
- }\r
- }\r
-\r
- var cancelAnimationFlag = isAllowed('cancel', element, newAnimation, existingAnimation);\r
- if (cancelAnimationFlag) {\r
- if (existingAnimation.state === RUNNING_STATE) {\r
- // this will end the animation right away and it is safe\r
- // to do so since the animation is already running and the\r
- // runner callback code will run in async\r
- existingAnimation.runner.end();\r
- } else if (existingAnimation.structural) {\r
- // this means that the animation is queued into a digest, but\r
- // hasn't started yet. Therefore it is safe to run the close\r
- // method which will call the runner methods in async.\r
- existingAnimation.close();\r
- } else {\r
- // this will merge the existing animation options into this new follow-up animation\r
- mergeAnimationOptions(element, newAnimation.options, existingAnimation.options);\r
- }\r
- } else {\r
- // a joined animation means that this animation will take over the existing one\r
- // so an example would involve a leave animation taking over an enter. Then when\r
- // the postDigest kicks in the enter will be ignored.\r
- var joinAnimationFlag = isAllowed('join', element, newAnimation, existingAnimation);\r
- if (joinAnimationFlag) {\r
- if (existingAnimation.state === RUNNING_STATE) {\r
- normalizeAnimationOptions(element, options);\r
- } else {\r
- event = newAnimation.event = existingAnimation.event;\r
- options = mergeAnimationOptions(element, existingAnimation.options, newAnimation.options);\r
- return runner;\r
- }\r
- }\r
- }\r
- } else {\r
- // normalization in this case means that it removes redundant CSS classes that\r
- // already exist (addClass) or do not exist (removeClass) on the element\r
- normalizeAnimationOptions(element, options);\r
- }\r
-\r
- // when the options are merged and cleaned up we may end up not having to do\r
- // an animation at all, therefore we should check this before issuing a post\r
- // digest callback. Structural animations will always run no matter what.\r
- var isValidAnimation = newAnimation.structural;\r
- if (!isValidAnimation) {\r
- // animate (from/to) can be quickly checked first, otherwise we check if any classes are present\r
- isValidAnimation = (newAnimation.event === 'animate' && Object.keys(newAnimation.options.to || {}).length > 0)\r
- || hasAnimationClasses(newAnimation.options);\r
- }\r
-\r
- if (!isValidAnimation) {\r
- close();\r
- clearElementAnimationState(element);\r
- return runner;\r
- }\r
-\r
- if (isStructural) {\r
- closeParentClassBasedAnimations(parent);\r
- }\r
-\r
- // the counter keeps track of cancelled animations\r
- var counter = (existingAnimation.counter || 0) + 1;\r
- newAnimation.counter = counter;\r
-\r
- markElementAnimationState(element, PRE_DIGEST_STATE, newAnimation);\r
-\r
- $rootScope.$$postDigest(function() {\r
- var animationDetails = activeAnimationsLookup.get(node);\r
- var animationCancelled = !animationDetails;\r
- animationDetails = animationDetails || {};\r
-\r
- // if addClass/removeClass is called before something like enter then the\r
- // registered parent element may not be present. The code below will ensure\r
- // that a final value for parent element is obtained\r
- var parentElement = element.parent() || [];\r
-\r
- // animate/structural/class-based animations all have requirements. Otherwise there\r
- // is no point in performing an animation. The parent node must also be set.\r
- var isValidAnimation = parentElement.length > 0\r
- && (animationDetails.event === 'animate'\r
- || animationDetails.structural\r
- || hasAnimationClasses(animationDetails.options));\r
-\r
- // this means that the previous animation was cancelled\r
- // even if the follow-up animation is the same event\r
- if (animationCancelled || animationDetails.counter !== counter || !isValidAnimation) {\r
- // if another animation did not take over then we need\r
- // to make sure that the domOperation and options are\r
- // handled accordingly\r
- if (animationCancelled) {\r
- applyAnimationClasses(element, options);\r
- applyAnimationStyles(element, options);\r
- }\r
-\r
- // if the event changed from something like enter to leave then we do\r
- // it, otherwise if it's the same then the end result will be the same too\r
- if (animationCancelled || (isStructural && animationDetails.event !== event)) {\r
- options.domOperation();\r
- runner.end();\r
- }\r
-\r
- // in the event that the element animation was not cancelled or a follow-up animation\r
- // isn't allowed to animate from here then we need to clear the state of the element\r
- // so that any future animations won't read the expired animation data.\r
- if (!isValidAnimation) {\r
- clearElementAnimationState(element);\r
- }\r
-\r
- return;\r
- }\r
-\r
- // this combined multiple class to addClass / removeClass into a setClass event\r
- // so long as a structural event did not take over the animation\r
- event = !animationDetails.structural && hasAnimationClasses(animationDetails.options, true)\r
- ? 'setClass'\r
- : animationDetails.event;\r
-\r
- if (animationDetails.structural) {\r
- closeParentClassBasedAnimations(parentElement);\r
- }\r
-\r
- markElementAnimationState(element, RUNNING_STATE);\r
- var realRunner = $$animation(element, event, animationDetails.options);\r
- realRunner.done(function(status) {\r
- close(!status);\r
- var animationDetails = activeAnimationsLookup.get(node);\r
- if (animationDetails && animationDetails.counter === counter) {\r
- clearElementAnimationState(getDomNode(element));\r
- }\r
- notifyProgress(runner, event, 'close', {});\r
- });\r
-\r
- // this will update the runner's flow-control events based on\r
- // the `realRunner` object.\r
- runner.setHost(realRunner);\r
- notifyProgress(runner, event, 'start', {});\r
- });\r
-\r
- return runner;\r
-\r
- function notifyProgress(runner, event, phase, data) {\r
- triggerCallback(event, element, phase, data);\r
- runner.progress(event, phase, data);\r
- }\r
-\r
- function close(reject) { // jshint ignore:line\r
- applyAnimationClasses(element, options);\r
- applyAnimationStyles(element, options);\r
- options.domOperation();\r
- runner.complete(!reject);\r
- }\r
- }\r
-\r
- function closeChildAnimations(element) {\r
- var node = getDomNode(element);\r
- var children = node.querySelectorAll('[' + NG_ANIMATE_ATTR_NAME + ']');\r
- forEach(children, function(child) {\r
- var state = parseInt(child.getAttribute(NG_ANIMATE_ATTR_NAME));\r
- var animationDetails = activeAnimationsLookup.get(child);\r
- switch (state) {\r
- case RUNNING_STATE:\r
- animationDetails.runner.end();\r
- /* falls through */\r
- case PRE_DIGEST_STATE:\r
- if (animationDetails) {\r
- activeAnimationsLookup.remove(child);\r
- }\r
- break;\r
- }\r
- });\r
- }\r
-\r
- function clearElementAnimationState(element) {\r
- var node = getDomNode(element);\r
- node.removeAttribute(NG_ANIMATE_ATTR_NAME);\r
- activeAnimationsLookup.remove(node);\r
- }\r
-\r
- function isMatchingElement(nodeOrElmA, nodeOrElmB) {\r
- return getDomNode(nodeOrElmA) === getDomNode(nodeOrElmB);\r
- }\r
-\r
- function closeParentClassBasedAnimations(startingElement) {\r
- var parentNode = getDomNode(startingElement);\r
- do {\r
- if (!parentNode || parentNode.nodeType !== ELEMENT_NODE) break;\r
-\r
- var animationDetails = activeAnimationsLookup.get(parentNode);\r
- if (animationDetails) {\r
- examineParentAnimation(parentNode, animationDetails);\r
- }\r
-\r
- parentNode = parentNode.parentNode;\r
- } while (true);\r
-\r
- // since animations are detected from CSS classes, we need to flush all parent\r
- // class-based animations so that the parent classes are all present for child\r
- // animations to properly function (otherwise any CSS selectors may not work)\r
- function examineParentAnimation(node, animationDetails) {\r
- // enter/leave/move always have priority\r
- if (animationDetails.structural || !hasAnimationClasses(animationDetails.options)) return;\r
-\r
- if (animationDetails.state === RUNNING_STATE) {\r
- animationDetails.runner.end();\r
- }\r
- clearElementAnimationState(node);\r
- }\r
- }\r
-\r
- function areAnimationsAllowed(element, parentElement, event) {\r
- var bodyElementDetected = false;\r
- var rootElementDetected = false;\r
- var parentAnimationDetected = false;\r
- var animateChildren;\r
-\r
- var parentHost = element.data(NG_ANIMATE_PIN_DATA);\r
- if (parentHost) {\r
- parentElement = parentHost;\r
- }\r
-\r
- while (parentElement && parentElement.length) {\r
- if (!rootElementDetected) {\r
- // angular doesn't want to attempt to animate elements outside of the application\r
- // therefore we need to ensure that the rootElement is an ancestor of the current element\r
- rootElementDetected = isMatchingElement(parentElement, $rootElement);\r
- }\r
-\r
- var parentNode = parentElement[0];\r
- if (parentNode.nodeType !== ELEMENT_NODE) {\r
- // no point in inspecting the #document element\r
- break;\r
- }\r
-\r
- var details = activeAnimationsLookup.get(parentNode) || {};\r
- // either an enter, leave or move animation will commence\r
- // therefore we can't allow any animations to take place\r
- // but if a parent animation is class-based then that's ok\r
- if (!parentAnimationDetected) {\r
- parentAnimationDetected = details.structural || disabledElementsLookup.get(parentNode);\r
- }\r
-\r
- if (isUndefined(animateChildren) || animateChildren === true) {\r
- var value = parentElement.data(NG_ANIMATE_CHILDREN_DATA);\r
- if (isDefined(value)) {\r
- animateChildren = value;\r
- }\r
- }\r
-\r
- // there is no need to continue traversing at this point\r
- if (parentAnimationDetected && animateChildren === false) break;\r
-\r
- if (!rootElementDetected) {\r
- // angular doesn't want to attempt to animate elements outside of the application\r
- // therefore we need to ensure that the rootElement is an ancestor of the current element\r
- rootElementDetected = isMatchingElement(parentElement, $rootElement);\r
- if (!rootElementDetected) {\r
- parentHost = parentElement.data(NG_ANIMATE_PIN_DATA);\r
- if (parentHost) {\r
- parentElement = parentHost;\r
- }\r
- }\r
- }\r
-\r
- if (!bodyElementDetected) {\r
- // we also need to ensure that the element is or will be apart of the body element\r
- // otherwise it is pointless to even issue an animation to be rendered\r
- bodyElementDetected = isMatchingElement(parentElement, bodyElement);\r
- }\r
-\r
- parentElement = parentElement.parent();\r
- }\r
-\r
- var allowAnimation = !parentAnimationDetected || animateChildren;\r
- return allowAnimation && rootElementDetected && bodyElementDetected;\r
- }\r
-\r
- function markElementAnimationState(element, state, details) {\r
- details = details || {};\r
- details.state = state;\r
-\r
- var node = getDomNode(element);\r
- node.setAttribute(NG_ANIMATE_ATTR_NAME, state);\r
-\r
- var oldValue = activeAnimationsLookup.get(node);\r
- var newValue = oldValue\r
- ? extend(oldValue, details)\r
- : details;\r
- activeAnimationsLookup.put(node, newValue);\r
- }\r
- }];\r
-}];\r
-\r
-var $$rAFMutexFactory = ['$$rAF', function($$rAF) {\r
- return function() {\r
- var passed = false;\r
- $$rAF(function() {\r
- passed = true;\r
- });\r
- return function(fn) {\r
- passed ? fn() : $$rAF(fn);\r
- };\r
- };\r
-}];\r
-\r
-var $$AnimateRunnerFactory = ['$q', '$$rAFMutex', function($q, $$rAFMutex) {\r
- var INITIAL_STATE = 0;\r
- var DONE_PENDING_STATE = 1;\r
- var DONE_COMPLETE_STATE = 2;\r
-\r
- AnimateRunner.chain = function(chain, callback) {\r
- var index = 0;\r
-\r
- next();\r
- function next() {\r
- if (index === chain.length) {\r
- callback(true);\r
- return;\r
- }\r
-\r
- chain[index](function(response) {\r
- if (response === false) {\r
- callback(false);\r
- return;\r
- }\r
- index++;\r
- next();\r
- });\r
- }\r
- };\r
-\r
- AnimateRunner.all = function(runners, callback) {\r
- var count = 0;\r
- var status = true;\r
- forEach(runners, function(runner) {\r
- runner.done(onProgress);\r
- });\r
-\r
- function onProgress(response) {\r
- status = status && response;\r
- if (++count === runners.length) {\r
- callback(status);\r
- }\r
- }\r
- };\r
-\r
- function AnimateRunner(host) {\r
- this.setHost(host);\r
-\r
- this._doneCallbacks = [];\r
- this._runInAnimationFrame = $$rAFMutex();\r
- this._state = 0;\r
- }\r
-\r
- AnimateRunner.prototype = {\r
- setHost: function(host) {\r
- this.host = host || {};\r
- },\r
-\r
- done: function(fn) {\r
- if (this._state === DONE_COMPLETE_STATE) {\r
- fn();\r
- } else {\r
- this._doneCallbacks.push(fn);\r
- }\r
- },\r
-\r
- progress: noop,\r
-\r
- getPromise: function() {\r
- if (!this.promise) {\r
- var self = this;\r
- this.promise = $q(function(resolve, reject) {\r
- self.done(function(status) {\r
- status === false ? reject() : resolve();\r
- });\r
- });\r
- }\r
- return this.promise;\r
- },\r
-\r
- then: function(resolveHandler, rejectHandler) {\r
- return this.getPromise().then(resolveHandler, rejectHandler);\r
- },\r
-\r
- 'catch': function(handler) {\r
- return this.getPromise()['catch'](handler);\r
- },\r
-\r
- 'finally': function(handler) {\r
- return this.getPromise()['finally'](handler);\r
- },\r
-\r
- pause: function() {\r
- if (this.host.pause) {\r
- this.host.pause();\r
- }\r
- },\r
-\r
- resume: function() {\r
- if (this.host.resume) {\r
- this.host.resume();\r
- }\r
- },\r
-\r
- end: function() {\r
- if (this.host.end) {\r
- this.host.end();\r
- }\r
- this._resolve(true);\r
- },\r
-\r
- cancel: function() {\r
- if (this.host.cancel) {\r
- this.host.cancel();\r
- }\r
- this._resolve(false);\r
- },\r
-\r
- complete: function(response) {\r
- var self = this;\r
- if (self._state === INITIAL_STATE) {\r
- self._state = DONE_PENDING_STATE;\r
- self._runInAnimationFrame(function() {\r
- self._resolve(response);\r
- });\r
- }\r
- },\r
-\r
- _resolve: function(response) {\r
- if (this._state !== DONE_COMPLETE_STATE) {\r
- forEach(this._doneCallbacks, function(fn) {\r
- fn(response);\r
- });\r
- this._doneCallbacks.length = 0;\r
- this._state = DONE_COMPLETE_STATE;\r
- }\r
- }\r
- };\r
-\r
- return AnimateRunner;\r
-}];\r
-\r
-var $$AnimationProvider = ['$animateProvider', function($animateProvider) {\r
- var NG_ANIMATE_REF_ATTR = 'ng-animate-ref';\r
-\r
- var drivers = this.drivers = [];\r
-\r
- var RUNNER_STORAGE_KEY = '$$animationRunner';\r
-\r
- function setRunner(element, runner) {\r
- element.data(RUNNER_STORAGE_KEY, runner);\r
- }\r
-\r
- function removeRunner(element) {\r
- element.removeData(RUNNER_STORAGE_KEY);\r
- }\r
-\r
- function getRunner(element) {\r
- return element.data(RUNNER_STORAGE_KEY);\r
- }\r
-\r
- this.$get = ['$$jqLite', '$rootScope', '$injector', '$$AnimateRunner', '$$rAFScheduler',\r
- function($$jqLite, $rootScope, $injector, $$AnimateRunner, $$rAFScheduler) {\r
-\r
- var animationQueue = [];\r
- var applyAnimationClasses = applyAnimationClassesFactory($$jqLite);\r
-\r
- var totalPendingClassBasedAnimations = 0;\r
- var totalActiveClassBasedAnimations = 0;\r
- var classBasedAnimationsQueue = [];\r
-\r
- // TODO(matsko): document the signature in a better way\r
- return function(element, event, options) {\r
- options = prepareAnimationOptions(options);\r
- var isStructural = ['enter', 'move', 'leave'].indexOf(event) >= 0;\r
-\r
- // there is no animation at the current moment, however\r
- // these runner methods will get later updated with the\r
- // methods leading into the driver's end/cancel methods\r
- // for now they just stop the animation from starting\r
- var runner = new $$AnimateRunner({\r
- end: function() { close(); },\r
- cancel: function() { close(true); }\r
- });\r
-\r
- if (!drivers.length) {\r
- close();\r
- return runner;\r
- }\r
-\r
- setRunner(element, runner);\r
-\r
- var classes = mergeClasses(element.attr('class'), mergeClasses(options.addClass, options.removeClass));\r
- var tempClasses = options.tempClasses;\r
- if (tempClasses) {\r
- classes += ' ' + tempClasses;\r
- options.tempClasses = null;\r
- }\r
-\r
- var classBasedIndex;\r
- if (!isStructural) {\r
- classBasedIndex = totalPendingClassBasedAnimations;\r
- totalPendingClassBasedAnimations += 1;\r
- }\r
-\r
- animationQueue.push({\r
- // this data is used by the postDigest code and passed into\r
- // the driver step function\r
- element: element,\r
- classes: classes,\r
- event: event,\r
- classBasedIndex: classBasedIndex,\r
- structural: isStructural,\r
- options: options,\r
- beforeStart: beforeStart,\r
- close: close\r
- });\r
-\r
- element.on('$destroy', handleDestroyedElement);\r
-\r
- // we only want there to be one function called within the post digest\r
- // block. This way we can group animations for all the animations that\r
- // were apart of the same postDigest flush call.\r
- if (animationQueue.length > 1) return runner;\r
-\r
- $rootScope.$$postDigest(function() {\r
- totalActiveClassBasedAnimations = totalPendingClassBasedAnimations;\r
- totalPendingClassBasedAnimations = 0;\r
- classBasedAnimationsQueue.length = 0;\r
-\r
- var animations = [];\r
- forEach(animationQueue, function(entry) {\r
- // the element was destroyed early on which removed the runner\r
- // form its storage. This means we can't animate this element\r
- // at all and it already has been closed due to destruction.\r
- if (getRunner(entry.element)) {\r
- animations.push(entry);\r
- }\r
- });\r
-\r
- // now any future animations will be in another postDigest\r
- animationQueue.length = 0;\r
-\r
- forEach(groupAnimations(animations), function(animationEntry) {\r
- if (animationEntry.structural) {\r
- triggerAnimationStart();\r
- } else {\r
- classBasedAnimationsQueue.push({\r
- node: getDomNode(animationEntry.element),\r
- fn: triggerAnimationStart\r
- });\r
-\r
- if (animationEntry.classBasedIndex === totalActiveClassBasedAnimations - 1) {\r
- // we need to sort each of the animations in order of parent to child\r
- // relationships. This ensures that the child classes are applied at the\r
- // right time.\r
- classBasedAnimationsQueue = classBasedAnimationsQueue.sort(function(a,b) {\r
- return b.node.contains(a.node);\r
- }).map(function(entry) {\r
- return entry.fn;\r
- });\r
-\r
- $$rAFScheduler(classBasedAnimationsQueue);\r
- }\r
- }\r
-\r
- function triggerAnimationStart() {\r
- // it's important that we apply the `ng-animate` CSS class and the\r
- // temporary classes before we do any driver invoking since these\r
- // CSS classes may be required for proper CSS detection.\r
- animationEntry.beforeStart();\r
-\r
- var startAnimationFn, closeFn = animationEntry.close;\r
-\r
- // in the event that the element was removed before the digest runs or\r
- // during the RAF sequencing then we should not trigger the animation.\r
- var targetElement = animationEntry.anchors\r
- ? (animationEntry.from.element || animationEntry.to.element)\r
- : animationEntry.element;\r
-\r
- if (getRunner(targetElement) && getDomNode(targetElement).parentNode) {\r
- var operation = invokeFirstDriver(animationEntry);\r
- if (operation) {\r
- startAnimationFn = operation.start;\r
- }\r
- }\r
-\r
- if (!startAnimationFn) {\r
- closeFn();\r
- } else {\r
- var animationRunner = startAnimationFn();\r
- animationRunner.done(function(status) {\r
- closeFn(!status);\r
- });\r
- updateAnimationRunners(animationEntry, animationRunner);\r
- }\r
- }\r
- });\r
- });\r
-\r
- return runner;\r
-\r
- // TODO(matsko): change to reference nodes\r
- function getAnchorNodes(node) {\r
- var SELECTOR = '[' + NG_ANIMATE_REF_ATTR + ']';\r
- var items = node.hasAttribute(NG_ANIMATE_REF_ATTR)\r
- ? [node]\r
- : node.querySelectorAll(SELECTOR);\r
- var anchors = [];\r
- forEach(items, function(node) {\r
- var attr = node.getAttribute(NG_ANIMATE_REF_ATTR);\r
- if (attr && attr.length) {\r
- anchors.push(node);\r
- }\r
- });\r
- return anchors;\r
- }\r
-\r
- function groupAnimations(animations) {\r
- var preparedAnimations = [];\r
- var refLookup = {};\r
- forEach(animations, function(animation, index) {\r
- var element = animation.element;\r
- var node = getDomNode(element);\r
- var event = animation.event;\r
- var enterOrMove = ['enter', 'move'].indexOf(event) >= 0;\r
- var anchorNodes = animation.structural ? getAnchorNodes(node) : [];\r
-\r
- if (anchorNodes.length) {\r
- var direction = enterOrMove ? 'to' : 'from';\r
-\r
- forEach(anchorNodes, function(anchor) {\r
- var key = anchor.getAttribute(NG_ANIMATE_REF_ATTR);\r
- refLookup[key] = refLookup[key] || {};\r
- refLookup[key][direction] = {\r
- animationID: index,\r
- element: jqLite(anchor)\r
- };\r
- });\r
- } else {\r
- preparedAnimations.push(animation);\r
- }\r
- });\r
-\r
- var usedIndicesLookup = {};\r
- var anchorGroups = {};\r
- forEach(refLookup, function(operations, key) {\r
- var from = operations.from;\r
- var to = operations.to;\r
-\r
- if (!from || !to) {\r
- // only one of these is set therefore we can't have an\r
- // anchor animation since all three pieces are required\r
- var index = from ? from.animationID : to.animationID;\r
- var indexKey = index.toString();\r
- if (!usedIndicesLookup[indexKey]) {\r
- usedIndicesLookup[indexKey] = true;\r
- preparedAnimations.push(animations[index]);\r
- }\r
- return;\r
- }\r
-\r
- var fromAnimation = animations[from.animationID];\r
- var toAnimation = animations[to.animationID];\r
- var lookupKey = from.animationID.toString();\r
- if (!anchorGroups[lookupKey]) {\r
- var group = anchorGroups[lookupKey] = {\r
- structural: true,\r
- beforeStart: function() {\r
- fromAnimation.beforeStart();\r
- toAnimation.beforeStart();\r
- },\r
- close: function() {\r
- fromAnimation.close();\r
- toAnimation.close();\r
- },\r
- classes: cssClassesIntersection(fromAnimation.classes, toAnimation.classes),\r
- from: fromAnimation,\r
- to: toAnimation,\r
- anchors: [] // TODO(matsko): change to reference nodes\r
- };\r
-\r
- // the anchor animations require that the from and to elements both have at least\r
- // one shared CSS class which effictively marries the two elements together to use\r
- // the same animation driver and to properly sequence the anchor animation.\r
- if (group.classes.length) {\r
- preparedAnimations.push(group);\r
- } else {\r
- preparedAnimations.push(fromAnimation);\r
- preparedAnimations.push(toAnimation);\r
- }\r
- }\r
-\r
- anchorGroups[lookupKey].anchors.push({\r
- 'out': from.element, 'in': to.element\r
- });\r
- });\r
-\r
- return preparedAnimations;\r
- }\r
-\r
- function cssClassesIntersection(a,b) {\r
- a = a.split(' ');\r
- b = b.split(' ');\r
- var matches = [];\r
-\r
- for (var i = 0; i < a.length; i++) {\r
- var aa = a[i];\r
- if (aa.substring(0,3) === 'ng-') continue;\r
-\r
- for (var j = 0; j < b.length; j++) {\r
- if (aa === b[j]) {\r
- matches.push(aa);\r
- break;\r
- }\r
- }\r
- }\r
-\r
- return matches.join(' ');\r
- }\r
-\r
- function invokeFirstDriver(animationDetails) {\r
- // we loop in reverse order since the more general drivers (like CSS and JS)\r
- // may attempt more elements, but custom drivers are more particular\r
- for (var i = drivers.length - 1; i >= 0; i--) {\r
- var driverName = drivers[i];\r
- if (!$injector.has(driverName)) continue; // TODO(matsko): remove this check\r
-\r
- var factory = $injector.get(driverName);\r
- var driver = factory(animationDetails);\r
- if (driver) {\r
- return driver;\r
- }\r
- }\r
- }\r
-\r
- function beforeStart() {\r
- element.addClass(NG_ANIMATE_CLASSNAME);\r
- if (tempClasses) {\r
- $$jqLite.addClass(element, tempClasses);\r
- }\r
- }\r
-\r
- function updateAnimationRunners(animation, newRunner) {\r
- if (animation.from && animation.to) {\r
- update(animation.from.element);\r
- update(animation.to.element);\r
- } else {\r
- update(animation.element);\r
- }\r
-\r
- function update(element) {\r
- getRunner(element).setHost(newRunner);\r
- }\r
- }\r
-\r
- function handleDestroyedElement() {\r
- var runner = getRunner(element);\r
- if (runner && (event !== 'leave' || !options.$$domOperationFired)) {\r
- runner.end();\r
- }\r
- }\r
-\r
- function close(rejected) { // jshint ignore:line\r
- element.off('$destroy', handleDestroyedElement);\r
- removeRunner(element);\r
-\r
- applyAnimationClasses(element, options);\r
- applyAnimationStyles(element, options);\r
- options.domOperation();\r
-\r
- if (tempClasses) {\r
- $$jqLite.removeClass(element, tempClasses);\r
- }\r
-\r
- element.removeClass(NG_ANIMATE_CLASSNAME);\r
- runner.complete(!rejected);\r
- }\r
- };\r
- }];\r
-}];\r
-\r
-/* global angularAnimateModule: true,\r
-\r
- $$rAFMutexFactory,\r
- $$rAFSchedulerFactory,\r
- $$AnimateChildrenDirective,\r
- $$AnimateRunnerFactory,\r
- $$AnimateQueueProvider,\r
- $$AnimationProvider,\r
- $AnimateCssProvider,\r
- $$AnimateCssDriverProvider,\r
- $$AnimateJsProvider,\r
- $$AnimateJsDriverProvider,\r
-*/\r
-\r
-/**\r
- * @ngdoc module\r
- * @name ngAnimate\r
- * @description\r
- *\r
- * The `ngAnimate` module provides support for CSS-based animations (keyframes and transitions) as well as JavaScript-based animations via\r
- * callback hooks. Animations are not enabled by default, however, by including `ngAnimate` then the animation hooks are enabled for an Angular app.\r
- *\r
- * <div doc-module-components="ngAnimate"></div>\r
- *\r
- * # Usage\r
- * Simply put, there are two ways to make use of animations when ngAnimate is used: by using **CSS** and **JavaScript**. The former works purely based\r
- * using CSS (by using matching CSS selectors/styles) and the latter triggers animations that are registered via `module.animation()`. For\r
- * both CSS and JS animations the sole requirement is to have a matching `CSS class` that exists both in the registered animation and within\r
- * the HTML element that the animation will be triggered on.\r
- *\r
- * ## Directive Support\r
- * The following directives are "animation aware":\r
- *\r
- * | Directive | Supported Animations |\r
- * |----------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------|\r
- * | {@link ng.directive:ngRepeat#animations ngRepeat} | enter, leave and move |\r
- * | {@link ngRoute.directive:ngView#animations ngView} | enter and leave |\r
- * | {@link ng.directive:ngInclude#animations ngInclude} | enter and leave |\r
- * | {@link ng.directive:ngSwitch#animations ngSwitch} | enter and leave |\r
- * | {@link ng.directive:ngIf#animations ngIf} | enter and leave |\r
- * | {@link ng.directive:ngClass#animations ngClass} | add and remove (the CSS class(es) present) |\r
- * | {@link ng.directive:ngShow#animations ngShow} & {@link ng.directive:ngHide#animations ngHide} | add and remove (the ng-hide class value) |\r
- * | {@link ng.directive:form#animation-hooks form} & {@link ng.directive:ngModel#animation-hooks ngModel} | add and remove (dirty, pristine, valid, invalid & all other validations) |\r
- * | {@link module:ngMessages#animations ngMessages} | add and remove (ng-active & ng-inactive) |\r
- * | {@link module:ngMessages#animations ngMessage} | enter and leave |\r
- *\r
- * (More information can be found by visiting each the documentation associated with each directive.)\r
- *\r
- * ## CSS-based Animations\r
- *\r
- * CSS-based animations with ngAnimate are unique since they require no JavaScript code at all. By using a CSS class that we reference between our HTML\r
- * and CSS code we can create an animation that will be picked up by Angular when an the underlying directive performs an operation.\r
- *\r
- * The example below shows how an `enter` animation can be made possible on a element using `ng-if`:\r
- *\r
- * ```html\r
- * <div ng-if="bool" class="fade">\r
- * Fade me in out\r
- * </div>\r
- * <button ng-click="bool=true">Fade In!</button>\r
- * <button ng-click="bool=false">Fade Out!</button>\r
- * ```\r
- *\r
- * Notice the CSS class **fade**? We can now create the CSS transition code that references this class:\r
- *\r
- * ```css\r
- * /* The starting CSS styles for the enter animation */\r
- * .fade.ng-enter {\r
- * transition:0.5s linear all;\r
- * opacity:0;\r
- * }\r
- *\r
- * /* The finishing CSS styles for the enter animation */\r
- * .fade.ng-enter.ng-enter-active {\r
- * opacity:1;\r
- * }\r
- * ```\r
- *\r
- * The key thing to remember here is that, depending on the animation event (which each of the directives above trigger depending on what's going on) two\r
- * generated CSS classes will be applied to the element; in the example above we have `.ng-enter` and `.ng-enter-active`. For CSS transitions, the transition\r
- * code **must** be defined within the starting CSS class (in this case `.ng-enter`). The destination class is what the transition will animate towards.\r
- *\r
- * If for example we wanted to create animations for `leave` and `move` (ngRepeat triggers move) then we can do so using the same CSS naming conventions:\r
- *\r
- * ```css\r
- * /* now the element will fade out before it is removed from the DOM */\r
- * .fade.ng-leave {\r
- * transition:0.5s linear all;\r
- * opacity:1;\r
- * }\r
- * .fade.ng-leave.ng-leave-active {\r
- * opacity:0;\r
- * }\r
- * ```\r
- *\r
- * We can also make use of **CSS Keyframes** by referencing the keyframe animation within the starting CSS class:\r
- *\r
- * ```css\r
- * /* there is no need to define anything inside of the destination\r
- * CSS class since the keyframe will take charge of the animation */\r
- * .fade.ng-leave {\r
- * animation: my_fade_animation 0.5s linear;\r
- * -webkit-animation: my_fade_animation 0.5s linear;\r
- * }\r
- *\r
- * @keyframes my_fade_animation {\r
- * from { opacity:1; }\r
- * to { opacity:0; }\r
- * }\r
- *\r
- * @-webkit-keyframes my_fade_animation {\r
- * from { opacity:1; }\r
- * to { opacity:0; }\r
- * }\r
- * ```\r
- *\r
- * Feel free also mix transitions and keyframes together as well as any other CSS classes on the same element.\r
- *\r
- * ### CSS Class-based Animations\r
- *\r
- * Class-based animations (animations that are triggered via `ngClass`, `ngShow`, `ngHide` and some other directives) have a slightly different\r
- * naming convention. Class-based animations are basic enough that a standard transition or keyframe can be referenced on the class being added\r
- * and removed.\r
- *\r
- * For example if we wanted to do a CSS animation for `ngHide` then we place an animation on the `.ng-hide` CSS class:\r
- *\r
- * ```html\r
- * <div ng-show="bool" class="fade">\r
- * Show and hide me\r
- * </div>\r
- * <button ng-click="bool=true">Toggle</button>\r
- *\r
- * <style>\r
- * .fade.ng-hide {\r
- * transition:0.5s linear all;\r
- * opacity:0;\r
- * }\r
- * </style>\r
- * ```\r
- *\r
- * All that is going on here with ngShow/ngHide behind the scenes is the `.ng-hide` class is added/removed (when the hidden state is valid). Since\r
- * ngShow and ngHide are animation aware then we can match up a transition and ngAnimate handles the rest.\r
- *\r
- * In addition the addition and removal of the CSS class, ngAnimate also provides two helper methods that we can use to further decorate the animation\r
- * with CSS styles.\r
- *\r
- * ```html\r
- * <div ng-class="{on:onOff}" class="highlight">\r
- * Highlight this box\r
- * </div>\r
- * <button ng-click="onOff=!onOff">Toggle</button>\r
- *\r
- * <style>\r
- * .highlight {\r
- * transition:0.5s linear all;\r
- * }\r
- * .highlight.on-add {\r
- * background:white;\r
- * }\r
- * .highlight.on {\r
- * background:yellow;\r
- * }\r
- * .highlight.on-remove {\r
- * background:black;\r
- * }\r
- * </style>\r
- * ```\r
- *\r
- * We can also make use of CSS keyframes by placing them within the CSS classes.\r
- *\r
- *\r
- * ### CSS Staggering Animations\r
- * A Staggering animation is a collection of animations that are issued with a slight delay in between each successive operation resulting in a\r
- * curtain-like effect. The ngAnimate module (versions >=1.2) supports staggering animations and the stagger effect can be\r
- * performed by creating a **ng-EVENT-stagger** CSS class and attaching that class to the base CSS class used for\r
- * the animation. The style property expected within the stagger class can either be a **transition-delay** or an\r
- * **animation-delay** property (or both if your animation contains both transitions and keyframe animations).\r
- *\r
- * ```css\r
- * .my-animation.ng-enter {\r
- * /* standard transition code */\r
- * transition: 1s linear all;\r
- * opacity:0;\r
- * }\r
- * .my-animation.ng-enter-stagger {\r
- * /* this will have a 100ms delay between each successive leave animation */\r
- * transition-delay: 0.1s;\r
- *\r
- * /* in case the stagger doesn't work then the duration value\r
- * must be set to 0 to avoid an accidental CSS inheritance */\r
- * transition-duration: 0s;\r
- * }\r
- * .my-animation.ng-enter.ng-enter-active {\r
- * /* standard transition styles */\r
- * opacity:1;\r
- * }\r
- * ```\r
- *\r
- * Staggering animations work by default in ngRepeat (so long as the CSS class is defined). Outside of ngRepeat, to use staggering animations\r
- * on your own, they can be triggered by firing multiple calls to the same event on $animate. However, the restrictions surrounding this\r
- * are that each of the elements must have the same CSS className value as well as the same parent element. A stagger operation\r
- * will also be reset if one or more animation frames have passed since the multiple calls to `$animate` were fired.\r
- *\r
- * The following code will issue the **ng-leave-stagger** event on the element provided:\r
- *\r
- * ```js\r
- * var kids = parent.children();\r
- *\r
- * $animate.leave(kids[0]); //stagger index=0\r
- * $animate.leave(kids[1]); //stagger index=1\r
- * $animate.leave(kids[2]); //stagger index=2\r
- * $animate.leave(kids[3]); //stagger index=3\r
- * $animate.leave(kids[4]); //stagger index=4\r
- *\r
- * window.requestAnimationFrame(function() {\r
- * //stagger has reset itself\r
- * $animate.leave(kids[5]); //stagger index=0\r
- * $animate.leave(kids[6]); //stagger index=1\r
- *\r
- * $scope.$digest();\r
- * });\r
- * ```\r
- *\r
- * Stagger animations are currently only supported within CSS-defined animations.\r
- *\r
- * ### The `ng-animate` CSS class\r
- *\r
- * When ngAnimate is animating an element it will apply the `ng-animate` CSS class to the element for the duration of the animation.\r
- * This is a temporary CSS class and it will be removed once the animation is over (for both JavaScript and CSS-based animations).\r
- *\r
- * Therefore, animations can be applied to an element using this temporary class directly via CSS.\r
- *\r
- * ```css\r
- * .zipper.ng-animate {\r
- * transition:0.5s linear all;\r
- * }\r
- * .zipper.ng-enter {\r
- * opacity:0;\r
- * }\r
- * .zipper.ng-enter.ng-enter-active {\r
- * opacity:1;\r
- * }\r
- * .zipper.ng-leave {\r
- * opacity:1;\r
- * }\r
- * .zipper.ng-leave.ng-leave-active {\r
- * opacity:0;\r
- * }\r
- * ```\r
- *\r
- * (Note that the `ng-animate` CSS class is reserved and it cannot be applied on an element directly since ngAnimate will always remove\r
- * the CSS class once an animation has completed.)\r
- *\r
- *\r
- * ## JavaScript-based Animations\r
- *\r
- * ngAnimate also allows for animations to be consumed by JavaScript code. The approach is similar to CSS-based animations (where there is a shared\r
- * 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\r
- * `module.animation()` module function we can register the ainmation.\r
- *\r
- * Let's see an example of a enter/leave animation using `ngRepeat`:\r
- *\r
- * ```html\r
- * <div ng-repeat="item in items" class="slide">\r
- * {{ item }}\r
- * </div>\r
- * ```\r
- *\r
- * See the **slide** CSS class? Let's use that class to define an animation that we'll structure in our module code by using `module.animation`:\r
- *\r
- * ```js\r
- * myModule.animation('.slide', [function() {\r
- * return {\r
- * // make note that other events (like addClass/removeClass)\r
- * // have different function input parameters\r
- * enter: function(element, doneFn) {\r
- * jQuery(element).fadeIn(1000, doneFn);\r
- *\r
- * // remember to call doneFn so that angular\r
- * // knows that the animation has concluded\r
- * },\r
- *\r
- * move: function(element, doneFn) {\r
- * jQuery(element).fadeIn(1000, doneFn);\r
- * },\r
- *\r
- * leave: function(element, doneFn) {\r
- * jQuery(element).fadeOut(1000, doneFn);\r
- * }\r
- * }\r
- * }]\r
- * ```\r
- *\r
- * The nice thing about JS-based animations is that we can inject other services and make use of advanced animation libraries such as\r
- * greensock.js and velocity.js.\r
- *\r
- * If our animation code class-based (meaning that something like `ngClass`, `ngHide` and `ngShow` triggers it) then we can still define\r
- * our animations inside of the same registered animation, however, the function input arguments are a bit different:\r
- *\r
- * ```html\r
- * <div ng-class="color" class="colorful">\r
- * this box is moody\r
- * </div>\r
- * <button ng-click="color='red'">Change to red</button>\r
- * <button ng-click="color='blue'">Change to blue</button>\r
- * <button ng-click="color='green'">Change to green</button>\r
- * ```\r
- *\r
- * ```js\r
- * myModule.animation('.colorful', [function() {\r
- * return {\r
- * addClass: function(element, className, doneFn) {\r
- * // do some cool animation and call the doneFn\r
- * },\r
- * removeClass: function(element, className, doneFn) {\r
- * // do some cool animation and call the doneFn\r
- * },\r
- * setClass: function(element, addedClass, removedClass, doneFn) {\r
- * // do some cool animation and call the doneFn\r
- * }\r
- * }\r
- * }]\r
- * ```\r
- *\r
- * ## CSS + JS Animations Together\r
- *\r
- * AngularJS 1.4 and higher has taken steps to make the amalgamation of CSS and JS animations more flexible. However, unlike earlier versions of Angular,\r
- * defining CSS and JS animations to work off of the same CSS class will not work anymore. Therefore the example below will only result in **JS animations taking\r
- * charge of the animation**:\r
- *\r
- * ```html\r
- * <div ng-if="bool" class="slide">\r
- * Slide in and out\r
- * </div>\r
- * ```\r
- *\r
- * ```js\r
- * myModule.animation('.slide', [function() {\r
- * return {\r
- * enter: function(element, doneFn) {\r
- * jQuery(element).slideIn(1000, doneFn);\r
- * }\r
- * }\r
- * }]\r
- * ```\r
- *\r
- * ```css\r
- * .slide.ng-enter {\r
- * transition:0.5s linear all;\r
- * transform:translateY(-100px);\r
- * }\r
- * .slide.ng-enter.ng-enter-active {\r
- * transform:translateY(0);\r
- * }\r
- * ```\r
- *\r
- * Does this mean that CSS and JS animations cannot be used together? Do JS-based animations always have higher priority? We can make up for the\r
- * lack of CSS animations by using the `$animateCss` service to trigger our own tweaked-out, CSS-based animations directly from\r
- * our own JS-based animation code:\r
- *\r
- * ```js\r
- * myModule.animation('.slide', ['$animateCss', function($animateCss) {\r
- * return {\r
- * enter: function(element, doneFn) {\r
-* // this will trigger `.slide.ng-enter` and `.slide.ng-enter-active`.\r
- * var runner = $animateCss(element, {\r
- * event: 'enter',\r
- * structural: true\r
- * }).start();\r
-* runner.done(doneFn);\r
- * }\r
- * }\r
- * }]\r
- * ```\r
- *\r
- * 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.\r
- *\r
- * The `$animateCss` service is very powerful since we can feed in all kinds of extra properties that will be evaluated and fed into a CSS transition or\r
- * keyframe animation. For example if we wanted to animate the height of an element while adding and removing classes then we can do so by providing that\r
- * data into `$animateCss` directly:\r
- *\r
- * ```js\r
- * myModule.animation('.slide', ['$animateCss', function($animateCss) {\r
- * return {\r
- * enter: function(element, doneFn) {\r
- * var runner = $animateCss(element, {\r
- * event: 'enter',\r
- * addClass: 'maroon-setting',\r
- * from: { height:0 },\r
- * to: { height: 200 }\r
- * }).start();\r
- *\r
- * runner.done(doneFn);\r
- * }\r
- * }\r
- * }]\r
- * ```\r
- *\r
- * Now we can fill in the rest via our transition CSS code:\r
- *\r
- * ```css\r
- * /* the transition tells ngAnimate to make the animation happen */\r
- * .slide.ng-enter { transition:0.5s linear all; }\r
- *\r
- * /* this extra CSS class will be absorbed into the transition\r
- * since the $animateCss code is adding the class */\r
- * .maroon-setting { background:red; }\r
- * ```\r
- *\r
- * And `$animateCss` will figure out the rest. Just make sure to have the `done()` callback fire the `doneFn` function to signal when the animation is over.\r
- *\r
- * To learn more about what's possible be sure to visit the {@link ngAnimate.$animateCss $animateCss service}.\r
- *\r
- * ## Animation Anchoring (via `ng-animate-ref`)\r
- *\r
- * ngAnimate in AngularJS 1.4 comes packed with the ability to cross-animate elements between\r
- * structural areas of an application (like views) by pairing up elements using an attribute\r
- * called `ng-animate-ref`.\r
- *\r
- * Let's say for example we have two views that are managed by `ng-view` and we want to show\r
- * that there is a relationship between two components situated in within these views. By using the\r
- * `ng-animate-ref` attribute we can identify that the two components are paired together and we\r
- * can then attach an animation, which is triggered when the view changes.\r
- *\r
- * Say for example we have the following template code:\r
- *\r
- * ```html\r
- * <!-- index.html -->\r
- * <div ng-view class="view-animation">\r
- * </div>\r
- *\r
- * <!-- home.html -->\r
- * <a href="#/banner-page">\r
- * <img src="./banner.jpg" class="banner" ng-animate-ref="banner">\r
- * </a>\r
- *\r
- * <!-- banner-page.html -->\r
- * <img src="./banner.jpg" class="banner" ng-animate-ref="banner">\r
- * ```\r
- *\r
- * Now, when the view changes (once the link is clicked), ngAnimate will examine the\r
- * HTML contents to see if there is a match reference between any components in the view\r
- * that is leaving and the view that is entering. It will scan both the view which is being\r
- * removed (leave) and inserted (enter) to see if there are any paired DOM elements that\r
- * contain a matching ref value.\r
- *\r
- * The two images match since they share the same ref value. ngAnimate will now create a\r
- * transport element (which is a clone of the first image element) and it will then attempt\r
- * to animate to the position of the second image element in the next view. For the animation to\r
- * work a special CSS class called `ng-anchor` will be added to the transported element.\r
- *\r
- * We can now attach a transition onto the `.banner.ng-anchor` CSS class and then\r
- * ngAnimate will handle the entire transition for us as well as the addition and removal of\r
- * any changes of CSS classes between the elements:\r
- *\r
- * ```css\r
- * .banner.ng-anchor {\r
- * /* this animation will last for 1 second since there are\r
- * two phases to the animation (an `in` and an `out` phase) */\r
- * transition:0.5s linear all;\r
- * }\r
- * ```\r
- *\r
- * We also **must** include animations for the views that are being entered and removed\r
- * (otherwise anchoring wouldn't be possible since the new view would be inserted right away).\r
- *\r
- * ```css\r
- * .view-animation.ng-enter, .view-animation.ng-leave {\r
- * transition:0.5s linear all;\r
- * position:fixed;\r
- * left:0;\r
- * top:0;\r
- * width:100%;\r
- * }\r
- * .view-animation.ng-enter {\r
- * transform:translateX(100%);\r
- * }\r
- * .view-animation.ng-leave,\r
- * .view-animation.ng-enter.ng-enter-active {\r
- * transform:translateX(0%);\r
- * }\r
- * .view-animation.ng-leave.ng-leave-active {\r
- * transform:translateX(-100%);\r
- * }\r
- * ```\r
- *\r
- * Now we can jump back to the anchor animation. When the animation happens, there are two stages that occur:\r
- * an `out` and an `in` stage. The `out` stage happens first and that is when the element is animated away\r
- * from its origin. Once that animation is over then the `in` stage occurs which animates the\r
- * element to its destination. The reason why there are two animations is to give enough time\r
- * for the enter animation on the new element to be ready.\r
- *\r
- * The example above sets up a transition for both the in and out phases, but we can also target the out or\r
- * in phases directly via `ng-anchor-out` and `ng-anchor-in`.\r
- *\r
- * ```css\r
- * .banner.ng-anchor-out {\r
- * transition: 0.5s linear all;\r
- *\r
- * /* the scale will be applied during the out animation,\r
- * but will be animated away when the in animation runs */\r
- * transform: scale(1.2);\r
- * }\r
- *\r
- * .banner.ng-anchor-in {\r
- * transition: 1s linear all;\r
- * }\r
- * ```\r
- *\r
- *\r
- *\r
- *\r
- * ### Anchoring Demo\r
- *\r
- <example module="anchoringExample"\r
- name="anchoringExample"\r
- id="anchoringExample"\r
- deps="angular-animate.js;angular-route.js"\r
- animations="true">\r
- <file name="index.html">\r
- <a href="#/">Home</a>\r
- <hr />\r
- <div class="view-container">\r
- <div ng-view class="view"></div>\r
- </div>\r
- </file>\r
- <file name="script.js">\r
- angular.module('anchoringExample', ['ngAnimate', 'ngRoute'])\r
- .config(['$routeProvider', function($routeProvider) {\r
- $routeProvider.when('/', {\r
- templateUrl: 'home.html',\r
- controller: 'HomeController as home'\r
- });\r
- $routeProvider.when('/profile/:id', {\r
- templateUrl: 'profile.html',\r
- controller: 'ProfileController as profile'\r
- });\r
- }])\r
- .run(['$rootScope', function($rootScope) {\r
- $rootScope.records = [\r
- { id:1, title: "Miss Beulah Roob" },\r
- { id:2, title: "Trent Morissette" },\r
- { id:3, title: "Miss Ava Pouros" },\r
- { id:4, title: "Rod Pouros" },\r
- { id:5, title: "Abdul Rice" },\r
- { id:6, title: "Laurie Rutherford Sr." },\r
- { id:7, title: "Nakia McLaughlin" },\r
- { id:8, title: "Jordon Blanda DVM" },\r
- { id:9, title: "Rhoda Hand" },\r
- { id:10, title: "Alexandrea Sauer" }\r
- ];\r
- }])\r
- .controller('HomeController', [function() {\r
- //empty\r
- }])\r
- .controller('ProfileController', ['$rootScope', '$routeParams', function($rootScope, $routeParams) {\r
- var index = parseInt($routeParams.id, 10);\r
- var record = $rootScope.records[index - 1];\r
-\r
- this.title = record.title;\r
- this.id = record.id;\r
- }]);\r
- </file>\r
- <file name="home.html">\r
- <h2>Welcome to the home page</h1>\r
- <p>Please click on an element</p>\r
- <a class="record"\r
- ng-href="#/profile/{{ record.id }}"\r
- ng-animate-ref="{{ record.id }}"\r
- ng-repeat="record in records">\r
- {{ record.title }}\r
- </a>\r
- </file>\r
- <file name="profile.html">\r
- <div class="profile record" ng-animate-ref="{{ profile.id }}">\r
- {{ profile.title }}\r
- </div>\r
- </file>\r
- <file name="animations.css">\r
- .record {\r
- display:block;\r
- font-size:20px;\r
- }\r
- .profile {\r
- background:black;\r
- color:white;\r
- font-size:100px;\r
- }\r
- .view-container {\r
- position:relative;\r
- }\r
- .view-container > .view.ng-animate {\r
- position:absolute;\r
- top:0;\r
- left:0;\r
- width:100%;\r
- min-height:500px;\r
- }\r
- .view.ng-enter, .view.ng-leave,\r
- .record.ng-anchor {\r
- transition:0.5s linear all;\r
- }\r
- .view.ng-enter {\r
- transform:translateX(100%);\r
- }\r
- .view.ng-enter.ng-enter-active, .view.ng-leave {\r
- transform:translateX(0%);\r
- }\r
- .view.ng-leave.ng-leave-active {\r
- transform:translateX(-100%);\r
- }\r
- .record.ng-anchor-out {\r
- background:red;\r
- }\r
- </file>\r
- </example>\r
- *\r
- * ### How is the element transported?\r
- *\r
- * When an anchor animation occurs, ngAnimate will clone the starting element and position it exactly where the starting\r
- * element is located on screen via absolute positioning. The cloned element will be placed inside of the root element\r
- * of the application (where ng-app was defined) and all of the CSS classes of the starting element will be applied. The\r
- * element will then animate into the `out` and `in` animations and will eventually reach the coordinates and match\r
- * the dimensions of the destination element. During the entire animation a CSS class of `.ng-animate-shim` will be applied\r
- * to both the starting and destination elements in order to hide them from being visible (the CSS styling for the class\r
- * is: `visibility:hidden`). Once the anchor reaches its destination then it will be removed and the destination element\r
- * will become visible since the shim class will be removed.\r
- *\r
- * ### How is the morphing handled?\r
- *\r
- * CSS Anchoring relies on transitions and keyframes and the internal code is intelligent enough to figure out\r
- * what CSS classes differ between the starting element and the destination element. These different CSS classes\r
- * will be added/removed on the anchor element and a transition will be applied (the transition that is provided\r
- * in the anchor class). Long story short, ngAnimate will figure out what classes to add and remove which will\r
- * make the transition of the element as smooth and automatic as possible. Be sure to use simple CSS classes that\r
- * do not rely on DOM nesting structure so that the anchor element appears the same as the starting element (since\r
- * the cloned element is placed inside of root element which is likely close to the body element).\r
- *\r
- * Note that if the root element is on the `<html>` element then the cloned node will be placed inside of body.\r
- *\r
- *\r
- * ## Using $animate in your directive code\r
- *\r
- * So far we've explored how to feed in animations into an Angular application, but how do we trigger animations within our own directives in our application?\r
- * By injecting the `$animate` service into our directive code, we can trigger structural and class-based hooks which can then be consumed by animations. Let's\r
- * imagine we have a greeting box that shows and hides itself when the data changes\r
- *\r
- * ```html\r
- * <greeting-box active="onOrOff">Hi there</greeting-box>\r
- * ```\r
- *\r
- * ```js\r
- * ngModule.directive('greetingBox', ['$animate', function($animate) {\r
- * return function(scope, element, attrs) {\r
- * attrs.$observe('active', function(value) {\r
- * value ? $animate.addClass(element, 'on') : $animate.removeClass(element, 'on');\r
- * });\r
- * });\r
- * }]);\r
- * ```\r
- *\r
- * Now the `on` CSS class is added and removed on the greeting box component. Now if we add a CSS class on top of the greeting box element\r
- * in our HTML code then we can trigger a CSS or JS animation to happen.\r
- *\r
- * ```css\r
- * /* normally we would create a CSS class to reference on the element */\r
- * greeting-box.on { transition:0.5s linear all; background:green; color:white; }\r
- * ```\r
- *\r
- * The `$animate` service contains a variety of other methods like `enter`, `leave`, `animate` and `setClass`. To learn more about what's\r
- * possible be sure to visit the {@link ng.$animate $animate service API page}.\r
- *\r
- *\r
- * ### Preventing Collisions With Third Party Libraries\r
- *\r
- * Some third-party frameworks place animation duration defaults across many element or className\r
- * selectors in order to make their code small and reuseable. This can lead to issues with ngAnimate, which\r
- * is expecting actual animations on these elements and has to wait for their completion.\r
- *\r
- * You can prevent this unwanted behavior by using a prefix on all your animation classes:\r
- *\r
- * ```css\r
- * /* prefixed with animate- */\r
- * .animate-fade-add.animate-fade-add-active {\r
- * transition:1s linear all;\r
- * opacity:0;\r
- * }\r
- * ```\r
- *\r
- * You then configure `$animate` to enforce this prefix:\r
- *\r
- * ```js\r
- * $animateProvider.classNameFilter(/animate-/);\r
- * ```\r
- *\r
- * This also may provide your application with a speed boost since only specific elements containing CSS class prefix\r
- * will be evaluated for animation when any DOM changes occur in the application.\r
- *\r
- * ## Callbacks and Promises\r
- *\r
- * When `$animate` is called it returns a promise that can be used to capture when the animation has ended. Therefore if we were to trigger\r
- * an animation (within our directive code) then we can continue performing directive and scope related activities after the animation has\r
- * ended by chaining onto the returned promise that animation method returns.\r
- *\r
- * ```js\r
- * // somewhere within the depths of the directive\r
- * $animate.enter(element, parent).then(function() {\r
- * //the animation has completed\r
- * });\r
- * ```\r
- *\r
- * (Note that earlier versions of Angular prior to v1.4 required the promise code to be wrapped using `$scope.$apply(...)`. This is not the case\r
- * anymore.)\r
- *\r
- * In addition to the animation promise, we can also make use of animation-related callbacks within our directives and controller code by registering\r
- * an event listener using the `$animate` service. Let's say for example that an animation was triggered on our view\r
- * routing controller to hook into that:\r
- *\r
- * ```js\r
- * ngModule.controller('HomePageController', ['$animate', function($animate) {\r
- * $animate.on('enter', ngViewElement, function(element) {\r
- * // the animation for this route has completed\r
- * }]);\r
- * }])\r
- * ```\r
- *\r
- * (Note that you will need to trigger a digest within the callback to get angular to notice any scope-related changes.)\r
- */\r
-\r
-/**\r
- * @ngdoc service\r
- * @name $animate\r
- * @kind object\r
- *\r
- * @description\r
- * The ngAnimate `$animate` service documentation is the same for the core `$animate` service.\r
- *\r
- * Click here {@link ng.$animate $animate to learn more about animations with `$animate`}.\r
- */\r
-angular.module('ngAnimate', [])\r
- .directive('ngAnimateChildren', $$AnimateChildrenDirective)\r
-\r
- .factory('$$rAFMutex', $$rAFMutexFactory)\r
- .factory('$$rAFScheduler', $$rAFSchedulerFactory)\r
-\r
- .factory('$$AnimateRunner', $$AnimateRunnerFactory)\r
-\r
- .provider('$$animateQueue', $$AnimateQueueProvider)\r
- .provider('$$animation', $$AnimationProvider)\r
-\r
- .provider('$animateCss', $AnimateCssProvider)\r
- .provider('$$animateCssDriver', $$AnimateCssDriverProvider)\r
-\r
- .provider('$$animateJs', $$AnimateJsProvider)\r
- .provider('$$animateJsDriver', $$AnimateJsDriverProvider);\r
-\r
-\r
-})(window, window.angular);\r