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

Welcome to the home page

-

Please click on an element

- - {{ record.title }} - -
- -
- {{ profile.title }} -
-
- - .record { - display:block; - font-size:20px; - } - .profile { - background:black; - color:white; - font-size:100px; - } - .view-container { - position:relative; - } - .view-container > .view.ng-animate { - position:absolute; - top:0; - left:0; - width:100%; - min-height:500px; - } - .view.ng-enter, .view.ng-leave, - .record.ng-anchor { - transition:0.5s linear all; - } - .view.ng-enter { - transform:translateX(100%); - } - .view.ng-enter.ng-enter-active, .view.ng-leave { - transform:translateX(0%); - } - .view.ng-leave.ng-leave-active { - transform:translateX(-100%); - } - .record.ng-anchor-out { - background:red; - } - -
- * - * ### How is the element transported? - * - * When an anchor animation occurs, ngAnimate will clone the starting element and position it exactly where the starting - * element is located on screen via absolute positioning. The cloned element will be placed inside of the root element - * of the application (where ng-app was defined) and all of the CSS classes of the starting element will be applied. The - * element will then animate into the `out` and `in` animations and will eventually reach the coordinates and match - * the dimensions of the destination element. During the entire animation a CSS class of `.ng-animate-shim` will be applied - * to both the starting and destination elements in order to hide them from being visible (the CSS styling for the class - * is: `visibility:hidden`). Once the anchor reaches its destination then it will be removed and the destination element - * will become visible since the shim class will be removed. - * - * ### How is the morphing handled? - * - * CSS Anchoring relies on transitions and keyframes and the internal code is intelligent enough to figure out - * what CSS classes differ between the starting element and the destination element. These different CSS classes - * will be added/removed on the anchor element and a transition will be applied (the transition that is provided - * in the anchor class). Long story short, ngAnimate will figure out what classes to add and remove which will - * make the transition of the element as smooth and automatic as possible. Be sure to use simple CSS classes that - * do not rely on DOM nesting structure so that the anchor element appears the same as the starting element (since - * the cloned element is placed inside of root element which is likely close to the body element). - * - * Note that if the root element is on the `` element then the cloned node will be placed inside of body. - * - * - * ## Using $animate in your directive code - * - * So far we've explored how to feed in animations into an Angular application, but how do we trigger animations within our own directives in our application? - * By injecting the `$animate` service into our directive code, we can trigger structural and class-based hooks which can then be consumed by animations. Let's - * imagine we have a greeting box that shows and hides itself when the data changes - * - * ```html - * Hi there - * ``` - * - * ```js - * ngModule.directive('greetingBox', ['$animate', function($animate) { - * return function(scope, element, attrs) { - * attrs.$observe('active', function(value) { - * value ? $animate.addClass(element, 'on') : $animate.removeClass(element, 'on'); - * }); - * }); - * }]); - * ``` - * - * 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 - * in our HTML code then we can trigger a CSS or JS animation to happen. - * - * ```css - * /* normally we would create a CSS class to reference on the element */ - * greeting-box.on { transition:0.5s linear all; background:green; color:white; } - * ``` - * - * The `$animate` service contains a variety of other methods like `enter`, `leave`, `animate` and `setClass`. To learn more about what's - * possible be sure to visit the {@link ng.$animate $animate service API page}. - * - * - * ### Preventing Collisions With Third Party Libraries - * - * Some third-party frameworks place animation duration defaults across many element or className - * selectors in order to make their code small and reuseable. This can lead to issues with ngAnimate, which - * is expecting actual animations on these elements and has to wait for their completion. - * - * You can prevent this unwanted behavior by using a prefix on all your animation classes: - * - * ```css - * /* prefixed with animate- */ - * .animate-fade-add.animate-fade-add-active { - * transition:1s linear all; - * opacity:0; - * } - * ``` - * - * You then configure `$animate` to enforce this prefix: - * - * ```js - * $animateProvider.classNameFilter(/animate-/); - * ``` - * - * This also may provide your application with a speed boost since only specific elements containing CSS class prefix - * will be evaluated for animation when any DOM changes occur in the application. - * - * ## Callbacks and Promises - * - * 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 - * an animation (within our directive code) then we can continue performing directive and scope related activities after the animation has - * ended by chaining onto the returned promise that animation method returns. - * - * ```js - * // somewhere within the depths of the directive - * $animate.enter(element, parent).then(function() { - * //the animation has completed - * }); - * ``` - * - * (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 - * anymore.) - * - * In addition to the animation promise, we can also make use of animation-related callbacks within our directives and controller code by registering - * an event listener using the `$animate` service. Let's say for example that an animation was triggered on our view - * routing controller to hook into that: - * - * ```js - * ngModule.controller('HomePageController', ['$animate', function($animate) { - * $animate.on('enter', ngViewElement, function(element) { - * // the animation for this route has completed - * }]); - * }]) - * ``` - * - * (Note that you will need to trigger a digest within the callback to get angular to notice any scope-related changes.) - */ - -/** - * @ngdoc service - * @name $animate - * @kind object - * - * @description - * The ngAnimate `$animate` service documentation is the same for the core `$animate` service. - * - * Click here {@link ng.$animate $animate to learn more about animations with `$animate`}. - */ -angular.module('ngAnimate', []) - .directive('ngAnimateChildren', $$AnimateChildrenDirective) - - .factory('$$rAFMutex', $$rAFMutexFactory) - .factory('$$rAFScheduler', $$rAFSchedulerFactory) - - .factory('$$AnimateRunner', $$AnimateRunnerFactory) - - .provider('$$animateQueue', $$AnimateQueueProvider) - .provider('$$animation', $$AnimationProvider) - - .provider('$animateCss', $AnimateCssProvider) - .provider('$$animateCssDriver', $$AnimateCssDriverProvider) - - .provider('$$animateJs', $$AnimateJsProvider) - .provider('$$animateJsDriver', $$AnimateJsDriverProvider); - - -})(window, window.angular); diff --git a/epsdk-app-onap/src/main/webapp/app/fusion/external/ebz/angular_js/angular-touch.js b/epsdk-app-onap/src/main/webapp/app/fusion/external/ebz/angular_js/angular-touch.js deleted file mode 100755 index 8934ff6b3..000000000 --- a/epsdk-app-onap/src/main/webapp/app/fusion/external/ebz/angular_js/angular-touch.js +++ /dev/null @@ -1,628 +0,0 @@ -/** - * @license AngularJS v1.4.3 - * (c) 2010-2015 Google, Inc. http://angularjs.org - * License: MIT - */ -(function(window, angular, undefined) {'use strict'; - -/** - * @ngdoc module - * @name ngTouch - * @description - * - * # ngTouch - * - * The `ngTouch` module provides touch events and other helpers for touch-enabled devices. - * The implementation is based on jQuery Mobile touch event handling - * ([jquerymobile.com](http://jquerymobile.com/)). - * - * - * See {@link ngTouch.$swipe `$swipe`} for usage. - * - *
- * - */ - -// define ngTouch module -/* global -ngTouch */ -var ngTouch = angular.module('ngTouch', []); - -function nodeName_(element) { - return angular.lowercase(element.nodeName || (element[0] && element[0].nodeName)); -} - -/* global ngTouch: false */ - - /** - * @ngdoc service - * @name $swipe - * - * @description - * The `$swipe` service is a service that abstracts the messier details of hold-and-drag swipe - * behavior, to make implementing swipe-related directives more convenient. - * - * Requires the {@link ngTouch `ngTouch`} module to be installed. - * - * `$swipe` is used by the `ngSwipeLeft` and `ngSwipeRight` directives in `ngTouch`, and by - * `ngCarousel` in a separate component. - * - * # Usage - * The `$swipe` service is an object with a single method: `bind`. `bind` takes an element - * which is to be watched for swipes, and an object with four handler functions. See the - * documentation for `bind` below. - */ - -ngTouch.factory('$swipe', [function() { - // The total distance in any direction before we make the call on swipe vs. scroll. - var MOVE_BUFFER_RADIUS = 10; - - var POINTER_EVENTS = { - 'mouse': { - start: 'mousedown', - move: 'mousemove', - end: 'mouseup' - }, - 'touch': { - start: 'touchstart', - move: 'touchmove', - end: 'touchend', - cancel: 'touchcancel' - } - }; - - function getCoordinates(event) { - var originalEvent = event.originalEvent || event; - var touches = originalEvent.touches && originalEvent.touches.length ? originalEvent.touches : [originalEvent]; - var e = (originalEvent.changedTouches && originalEvent.changedTouches[0]) || touches[0]; - - return { - x: e.clientX, - y: e.clientY - }; - } - - function getEvents(pointerTypes, eventType) { - var res = []; - angular.forEach(pointerTypes, function(pointerType) { - var eventName = POINTER_EVENTS[pointerType][eventType]; - if (eventName) { - res.push(eventName); - } - }); - return res.join(' '); - } - - return { - /** - * @ngdoc method - * @name $swipe#bind - * - * @description - * The main method of `$swipe`. It takes an element to be watched for swipe motions, and an - * object containing event handlers. - * The pointer types that should be used can be specified via the optional - * third argument, which is an array of strings `'mouse'` and `'touch'`. By default, - * `$swipe` will listen for `mouse` and `touch` events. - * - * The four events are `start`, `move`, `end`, and `cancel`. `start`, `move`, and `end` - * receive as a parameter a coordinates object of the form `{ x: 150, y: 310 }` and the raw - * `event`. `cancel` receives the raw `event` as its single parameter. - * - * `start` is called on either `mousedown` or `touchstart`. After this event, `$swipe` is - * watching for `touchmove` or `mousemove` events. These events are ignored until the total - * distance moved in either dimension exceeds a small threshold. - * - * Once this threshold is exceeded, either the horizontal or vertical delta is greater. - * - If the horizontal distance is greater, this is a swipe and `move` and `end` events follow. - * - If the vertical distance is greater, this is a scroll, and we let the browser take over. - * A `cancel` event is sent. - * - * `move` is called on `mousemove` and `touchmove` after the above logic has determined that - * a swipe is in progress. - * - * `end` is called when a swipe is successfully completed with a `touchend` or `mouseup`. - * - * `cancel` is called either on a `touchcancel` from the browser, or when we begin scrolling - * as described above. - * - */ - bind: function(element, eventHandlers, pointerTypes) { - // Absolute total movement, used to control swipe vs. scroll. - var totalX, totalY; - // Coordinates of the start position. - var startCoords; - // Last event's position. - var lastPos; - // Whether a swipe is active. - var active = false; - - pointerTypes = pointerTypes || ['mouse', 'touch']; - element.on(getEvents(pointerTypes, 'start'), function(event) { - startCoords = getCoordinates(event); - active = true; - totalX = 0; - totalY = 0; - lastPos = startCoords; - eventHandlers['start'] && eventHandlers['start'](startCoords, event); - }); - var events = getEvents(pointerTypes, 'cancel'); - if (events) { - element.on(events, function(event) { - active = false; - eventHandlers['cancel'] && eventHandlers['cancel'](event); - }); - } - - element.on(getEvents(pointerTypes, 'move'), function(event) { - if (!active) return; - - // Android will send a touchcancel if it thinks we're starting to scroll. - // So when the total distance (+ or - or both) exceeds 10px in either direction, - // we either: - // - On totalX > totalY, we send preventDefault() and treat this as a swipe. - // - On totalY > totalX, we let the browser handle it as a scroll. - - if (!startCoords) return; - var coords = getCoordinates(event); - - totalX += Math.abs(coords.x - lastPos.x); - totalY += Math.abs(coords.y - lastPos.y); - - lastPos = coords; - - if (totalX < MOVE_BUFFER_RADIUS && totalY < MOVE_BUFFER_RADIUS) { - return; - } - - // One of totalX or totalY has exceeded the buffer, so decide on swipe vs. scroll. - if (totalY > totalX) { - // Allow native scrolling to take over. - active = false; - eventHandlers['cancel'] && eventHandlers['cancel'](event); - return; - } else { - // Prevent the browser from scrolling. - event.preventDefault(); - eventHandlers['move'] && eventHandlers['move'](coords, event); - } - }); - - element.on(getEvents(pointerTypes, 'end'), function(event) { - if (!active) return; - active = false; - eventHandlers['end'] && eventHandlers['end'](getCoordinates(event), event); - }); - } - }; -}]); - -/* global ngTouch: false, - nodeName_: false -*/ - -/** - * @ngdoc directive - * @name ngClick - * - * @description - * A more powerful replacement for the default ngClick designed to be used on touchscreen - * devices. Most mobile browsers wait about 300ms after a tap-and-release before sending - * the click event. This version handles them immediately, and then prevents the - * following click event from propagating. - * - * Requires the {@link ngTouch `ngTouch`} module to be installed. - * - * This directive can fall back to using an ordinary click event, and so works on desktop - * browsers as well as mobile. - * - * This directive also sets the CSS class `ng-click-active` while the element is being held - * down (by a mouse click or touch) so you can restyle the depressed element if you wish. - * - * @element ANY - * @param {expression} ngClick {@link guide/expression Expression} to evaluate - * upon tap. (Event object is available as `$event`) - * - * @example - - - - count: {{ count }} - - - angular.module('ngClickExample', ['ngTouch']); - - - */ - -ngTouch.config(['$provide', function($provide) { - $provide.decorator('ngClickDirective', ['$delegate', function($delegate) { - // drop the default ngClick directive - $delegate.shift(); - return $delegate; - }]); -}]); - -ngTouch.directive('ngClick', ['$parse', '$timeout', '$rootElement', - function($parse, $timeout, $rootElement) { - var TAP_DURATION = 750; // Shorter than 750ms is a tap, longer is a taphold or drag. - var MOVE_TOLERANCE = 12; // 12px seems to work in most mobile browsers. - var PREVENT_DURATION = 2500; // 2.5 seconds maximum from preventGhostClick call to click - var CLICKBUSTER_THRESHOLD = 25; // 25 pixels in any dimension is the limit for busting clicks. - - var ACTIVE_CLASS_NAME = 'ng-click-active'; - var lastPreventedTime; - var touchCoordinates; - var lastLabelClickCoordinates; - - - // TAP EVENTS AND GHOST CLICKS - // - // Why tap events? - // Mobile browsers detect a tap, then wait a moment (usually ~300ms) to see if you're - // double-tapping, and then fire a click event. - // - // This delay sucks and makes mobile apps feel unresponsive. - // So we detect touchstart, touchcancel and touchend ourselves and determine when - // the user has tapped on something. - // - // What happens when the browser then generates a click event? - // The browser, of course, also detects the tap and fires a click after a delay. This results in - // tapping/clicking twice. We do "clickbusting" to prevent it. - // - // How does it work? - // We attach global touchstart and click handlers, that run during the capture (early) phase. - // So the sequence for a tap is: - // - global touchstart: Sets an "allowable region" at the point touched. - // - element's touchstart: Starts a touch - // (- touchcancel ends the touch, no click follows) - // - element's touchend: Determines if the tap is valid (didn't move too far away, didn't hold - // too long) and fires the user's tap handler. The touchend also calls preventGhostClick(). - // - preventGhostClick() removes the allowable region the global touchstart created. - // - The browser generates a click event. - // - The global click handler catches the click, and checks whether it was in an allowable region. - // - If preventGhostClick was called, the region will have been removed, the click is busted. - // - If the region is still there, the click proceeds normally. Therefore clicks on links and - // other elements without ngTap on them work normally. - // - // This is an ugly, terrible hack! - // Yeah, tell me about it. The alternatives are using the slow click events, or making our users - // deal with the ghost clicks, so I consider this the least of evils. Fortunately Angular - // encapsulates this ugly logic away from the user. - // - // Why not just put click handlers on the element? - // We do that too, just to be sure. If the tap event caused the DOM to change, - // it is possible another element is now in that position. To take account for these possibly - // distinct elements, the handlers are global and care only about coordinates. - - // Checks if the coordinates are close enough to be within the region. - function hit(x1, y1, x2, y2) { - return Math.abs(x1 - x2) < CLICKBUSTER_THRESHOLD && Math.abs(y1 - y2) < CLICKBUSTER_THRESHOLD; - } - - // Checks a list of allowable regions against a click location. - // Returns true if the click should be allowed. - // Splices out the allowable region from the list after it has been used. - function checkAllowableRegions(touchCoordinates, x, y) { - for (var i = 0; i < touchCoordinates.length; i += 2) { - if (hit(touchCoordinates[i], touchCoordinates[i + 1], x, y)) { - touchCoordinates.splice(i, i + 2); - return true; // allowable region - } - } - return false; // No allowable region; bust it. - } - - // Global click handler that prevents the click if it's in a bustable zone and preventGhostClick - // was called recently. - function onClick(event) { - if (Date.now() - lastPreventedTime > PREVENT_DURATION) { - return; // Too old. - } - - var touches = event.touches && event.touches.length ? event.touches : [event]; - var x = touches[0].clientX; - var y = touches[0].clientY; - // Work around desktop Webkit quirk where clicking a label will fire two clicks (on the label - // and on the input element). Depending on the exact browser, this second click we don't want - // to bust has either (0,0), negative coordinates, or coordinates equal to triggering label - // click event - if (x < 1 && y < 1) { - return; // offscreen - } - if (lastLabelClickCoordinates && - lastLabelClickCoordinates[0] === x && lastLabelClickCoordinates[1] === y) { - return; // input click triggered by label click - } - // reset label click coordinates on first subsequent click - if (lastLabelClickCoordinates) { - lastLabelClickCoordinates = null; - } - // remember label click coordinates to prevent click busting of trigger click event on input - if (nodeName_(event.target) === 'label') { - lastLabelClickCoordinates = [x, y]; - } - - // Look for an allowable region containing this click. - // If we find one, that means it was created by touchstart and not removed by - // preventGhostClick, so we don't bust it. - if (checkAllowableRegions(touchCoordinates, x, y)) { - return; - } - - // If we didn't find an allowable region, bust the click. - event.stopPropagation(); - event.preventDefault(); - - // Blur focused form elements - event.target && event.target.blur && event.target.blur(); - } - - - // Global touchstart handler that creates an allowable region for a click event. - // This allowable region can be removed by preventGhostClick if we want to bust it. - function onTouchStart(event) { - var touches = event.touches && event.touches.length ? event.touches : [event]; - var x = touches[0].clientX; - var y = touches[0].clientY; - touchCoordinates.push(x, y); - - $timeout(function() { - // Remove the allowable region. - for (var i = 0; i < touchCoordinates.length; i += 2) { - if (touchCoordinates[i] == x && touchCoordinates[i + 1] == y) { - touchCoordinates.splice(i, i + 2); - return; - } - } - }, PREVENT_DURATION, false); - } - - // On the first call, attaches some event handlers. Then whenever it gets called, it creates a - // zone around the touchstart where clicks will get busted. - function preventGhostClick(x, y) { - if (!touchCoordinates) { - $rootElement[0].addEventListener('click', onClick, true); - $rootElement[0].addEventListener('touchstart', onTouchStart, true); - touchCoordinates = []; - } - - lastPreventedTime = Date.now(); - - checkAllowableRegions(touchCoordinates, x, y); - } - - // Actual linking function. - return function(scope, element, attr) { - var clickHandler = $parse(attr.ngClick), - tapping = false, - tapElement, // Used to blur the element after a tap. - startTime, // Used to check if the tap was held too long. - touchStartX, - touchStartY; - - function resetState() { - tapping = false; - element.removeClass(ACTIVE_CLASS_NAME); - } - - element.on('touchstart', function(event) { - tapping = true; - tapElement = event.target ? event.target : event.srcElement; // IE uses srcElement. - // Hack for Safari, which can target text nodes instead of containers. - if (tapElement.nodeType == 3) { - tapElement = tapElement.parentNode; - } - - element.addClass(ACTIVE_CLASS_NAME); - - startTime = Date.now(); - - // Use jQuery originalEvent - var originalEvent = event.originalEvent || event; - var touches = originalEvent.touches && originalEvent.touches.length ? originalEvent.touches : [originalEvent]; - var e = touches[0]; - touchStartX = e.clientX; - touchStartY = e.clientY; - }); - - element.on('touchcancel', function(event) { - resetState(); - }); - - element.on('touchend', function(event) { - var diff = Date.now() - startTime; - - // Use jQuery originalEvent - var originalEvent = event.originalEvent || event; - var touches = (originalEvent.changedTouches && originalEvent.changedTouches.length) ? - originalEvent.changedTouches : - ((originalEvent.touches && originalEvent.touches.length) ? originalEvent.touches : [originalEvent]); - var e = touches[0]; - var x = e.clientX; - var y = e.clientY; - var dist = Math.sqrt(Math.pow(x - touchStartX, 2) + Math.pow(y - touchStartY, 2)); - - if (tapping && diff < TAP_DURATION && dist < MOVE_TOLERANCE) { - // Call preventGhostClick so the clickbuster will catch the corresponding click. - preventGhostClick(x, y); - - // Blur the focused element (the button, probably) before firing the callback. - // This doesn't work perfectly on Android Chrome, but seems to work elsewhere. - // I couldn't get anything to work reliably on Android Chrome. - if (tapElement) { - tapElement.blur(); - } - - if (!angular.isDefined(attr.disabled) || attr.disabled === false) { - element.triggerHandler('click', [event]); - } - } - - resetState(); - }); - - // Hack for iOS Safari's benefit. It goes searching for onclick handlers and is liable to click - // something else nearby. - element.onclick = function(event) { }; - - // Actual click handler. - // There are three different kinds of clicks, only two of which reach this point. - // - On desktop browsers without touch events, their clicks will always come here. - // - On mobile browsers, the simulated "fast" click will call this. - // - But the browser's follow-up slow click will be "busted" before it reaches this handler. - // Therefore it's safe to use this directive on both mobile and desktop. - element.on('click', function(event, touchend) { - scope.$apply(function() { - clickHandler(scope, {$event: (touchend || event)}); - }); - }); - - element.on('mousedown', function(event) { - element.addClass(ACTIVE_CLASS_NAME); - }); - - element.on('mousemove mouseup', function(event) { - element.removeClass(ACTIVE_CLASS_NAME); - }); - - }; -}]); - -/* global ngTouch: false */ - -/** - * @ngdoc directive - * @name ngSwipeLeft - * - * @description - * Specify custom behavior when an element is swiped to the left on a touchscreen device. - * A leftward swipe is a quick, right-to-left slide of the finger. - * Though ngSwipeLeft is designed for touch-based devices, it will work with a mouse click and drag - * too. - * - * To disable the mouse click and drag functionality, add `ng-swipe-disable-mouse` to - * the `ng-swipe-left` or `ng-swipe-right` DOM Element. - * - * Requires the {@link ngTouch `ngTouch`} module to be installed. - * - * @element ANY - * @param {expression} ngSwipeLeft {@link guide/expression Expression} to evaluate - * upon left swipe. (Event object is available as `$event`) - * - * @example - - -
- Some list content, like an email in the inbox -
-
- - -
-
- - angular.module('ngSwipeLeftExample', ['ngTouch']); - -
- */ - -/** - * @ngdoc directive - * @name ngSwipeRight - * - * @description - * Specify custom behavior when an element is swiped to the right on a touchscreen device. - * A rightward swipe is a quick, left-to-right slide of the finger. - * Though ngSwipeRight is designed for touch-based devices, it will work with a mouse click and drag - * too. - * - * Requires the {@link ngTouch `ngTouch`} module to be installed. - * - * @element ANY - * @param {expression} ngSwipeRight {@link guide/expression Expression} to evaluate - * upon right swipe. (Event object is available as `$event`) - * - * @example - - -
- Some list content, like an email in the inbox -
-
- - -
-
- - angular.module('ngSwipeRightExample', ['ngTouch']); - -
- */ - -function makeSwipeDirective(directiveName, direction, eventName) { - ngTouch.directive(directiveName, ['$parse', '$swipe', function($parse, $swipe) { - // The maximum vertical delta for a swipe should be less than 75px. - var MAX_VERTICAL_DISTANCE = 75; - // Vertical distance should not be more than a fraction of the horizontal distance. - var MAX_VERTICAL_RATIO = 0.3; - // At least a 30px lateral motion is necessary for a swipe. - var MIN_HORIZONTAL_DISTANCE = 30; - - return function(scope, element, attr) { - var swipeHandler = $parse(attr[directiveName]); - - var startCoords, valid; - - function validSwipe(coords) { - // Check that it's within the coordinates. - // Absolute vertical distance must be within tolerances. - // Horizontal distance, we take the current X - the starting X. - // This is negative for leftward swipes and positive for rightward swipes. - // After multiplying by the direction (-1 for left, +1 for right), legal swipes - // (ie. same direction as the directive wants) will have a positive delta and - // illegal ones a negative delta. - // Therefore this delta must be positive, and larger than the minimum. - if (!startCoords) return false; - var deltaY = Math.abs(coords.y - startCoords.y); - var deltaX = (coords.x - startCoords.x) * direction; - return valid && // Short circuit for already-invalidated swipes. - deltaY < MAX_VERTICAL_DISTANCE && - deltaX > 0 && - deltaX > MIN_HORIZONTAL_DISTANCE && - deltaY / deltaX < MAX_VERTICAL_RATIO; - } - - var pointerTypes = ['touch']; - if (!angular.isDefined(attr['ngSwipeDisableMouse'])) { - pointerTypes.push('mouse'); - } - $swipe.bind(element, { - 'start': function(coords, event) { - startCoords = coords; - valid = true; - }, - 'cancel': function(event) { - valid = false; - }, - 'end': function(coords, event) { - if (validSwipe(coords)) { - scope.$apply(function() { - element.triggerHandler(eventName); - swipeHandler(scope, {$event: event}); - }); - } - } - }, pointerTypes); - }; - }]); -} - -// Left is negative X-coordinate, right is positive. -makeSwipeDirective('ngSwipeLeft', -1, 'swipeleft'); -makeSwipeDirective('ngSwipeRight', 1, 'swiperight'); - - - -})(window, window.angular); -- cgit 1.2.3-korg