1/** 2 * @license AngularJS v1.5.0-rc.1 3 * (c) 2010-2016 Google, Inc. http://angularjs.org 4 * License: MIT 5 */ 6(function(window, angular, undefined) {'use strict'; 7 8/* jshint ignore:start */ 9var noop = angular.noop; 10var copy = angular.copy; 11var extend = angular.extend; 12var jqLite = angular.element; 13var forEach = angular.forEach; 14var isArray = angular.isArray; 15var isString = angular.isString; 16var isObject = angular.isObject; 17var isUndefined = angular.isUndefined; 18var isDefined = angular.isDefined; 19var isFunction = angular.isFunction; 20var isElement = angular.isElement; 21 22var ELEMENT_NODE = 1; 23var COMMENT_NODE = 8; 24 25var ADD_CLASS_SUFFIX = '-add'; 26var REMOVE_CLASS_SUFFIX = '-remove'; 27var EVENT_CLASS_PREFIX = 'ng-'; 28var ACTIVE_CLASS_SUFFIX = '-active'; 29var PREPARE_CLASS_SUFFIX = '-prepare'; 30 31var NG_ANIMATE_CLASSNAME = 'ng-animate'; 32var NG_ANIMATE_CHILDREN_DATA = '$$ngAnimateChildren'; 33 34// Detect proper transitionend/animationend event names. 35var CSS_PREFIX = '', TRANSITION_PROP, TRANSITIONEND_EVENT, ANIMATION_PROP, ANIMATIONEND_EVENT; 36 37// If unprefixed events are not supported but webkit-prefixed are, use the latter.
vendor: 12,888 bytes, lines 38-465
38// Otherwise, just use W3C names, browsers not supporting them at all will just ignore them. 39// Note: Chrome implements `window.onwebkitanimationend` and doesn't implement `window.onanimationend` 40// but at the same time dispatches the `animationend` event and not `webkitAnimationEnd`. 41// Register both events in case `window.onanimationend` is not supported because of that, 42// do the same for `transitionend` as Safari is likely to exhibit similar behavior. 43// Also, the only modern browser that uses vendor prefixes for transitions/keyframes is webkit 44// therefore there is no reason to test anymore for other vendor prefixes: 45// http://caniuse.com/#search=transition 46if (isUndefined(window.ontransitionend) && isDefined(window.onwebkittransitionend)) { 47 CSS_PREFIX = '-webkit-'; 48 TRANSITION_PROP = 'WebkitTransition'; 49 TRANSITIONEND_EVENT = 'webkitTransitionEnd transitionend'; 50} else { 51 TRANSITION_PROP = 'transition'; 52 TRANSITIONEND_EVENT = 'transitionend'; 53} 54 55if (isUndefined(window.onanimationend) && isDefined(window.onwebkitanimationend)) { 56 CSS_PREFIX = '-webkit-'; 57 ANIMATION_PROP = 'WebkitAnimation'; 58 ANIMATIONEND_EVENT = 'webkitAnimationEnd animationend'; 59} else { 60 ANIMATION_PROP = 'animation'; 61 ANIMATIONEND_EVENT = 'animationend'; 62} 63 64var DURATION_KEY = 'Duration'; 65var PROPERTY_KEY = 'Property'; 66var DELAY_KEY = 'Delay'; 67var TIMING_KEY = 'TimingFunction'; 68var ANIMATION_ITERATION_COUNT_KEY = 'IterationCount'; 69var ANIMATION_PLAYSTATE_KEY = 'PlayState'; 70var SAFE_FAST_FORWARD_DURATION_VALUE = 9999; 71 72var ANIMATION_DELAY_PROP = ANIMATION_PROP + DELAY_KEY; 73var ANIMATION_DURATION_PROP = ANIMATION_PROP + DURATION_KEY; 74var TRANSITION_DELAY_PROP = TRANSITION_PROP + DELAY_KEY; 75var TRANSITION_DURATION_PROP = TRANSITION_PROP + DURATION_KEY; 76 77var isPromiseLike = function(p) { 78 return p && p.then ? true : false; 79}; 80 81var ngMinErr = angular.$$minErr('ng'); 82function assertArg(arg, name, reason) { 83 if (!arg) { 84 throw ngMinErr('areq', "Argument '{0}' is {1}", (name || '?'), (reason || "required")); 85 } 86 return arg; 87} 88 89function mergeClasses(a,b) { 90 if (!a && !b) return ''; 91 if (!a) return b; 92 if (!b) return a; 93 if (isArray(a)) a = a.join(' '); 94 if (isArray(b)) b = b.join(' '); 95 return a + ' ' + b; 96} 97 98function packageStyles(options) { 99 var styles = {}; 100 if (options && (options.to || options.from)) { 101 styles.to = options.to; 102 styles.from = options.from; 103 } 104 return styles; 105} 106 107function pendClasses(classes, fix, isPrefix) { 108 var className = ''; 109 classes = isArray(classes) 110 ? classes 111 : classes && isString(classes) && classes.length 112 ? classes.split(/\s+/) 113 : []; 114 forEach(classes, function(klass, i) { 115 if (klass && klass.length > 0) { 116 className += (i > 0) ? ' ' : ''; 117 className += isPrefix ? fix + klass 118 : klass + fix; 119 } 120 }); 121 return className; 122} 123 124function removeFromArray(arr, val) { 125 var index = arr.indexOf(val); 126 if (val >= 0) { 127 arr.splice(index, 1); 128 } 129} 130 131function stripCommentsFromElement(element) { 132 if (element instanceof jqLite) { 133 switch (element.length) { 134 case 0: 135 return []; 136 break; 137 138 case 1: 139 // there is no point of stripping anything if the element 140 // is the only element within the jqLite wrapper. 141 // (it's important that we retain the element instance.) 142 if (element[0].nodeType === ELEMENT_NODE) { 143 return element; 144 } 145 break; 146 147 default: 148 return jqLite(extractElementNode(element)); 149 break; 150 } 151 } 152 153 if (element.nodeType === ELEMENT_NODE) { 154 return jqLite(element); 155 } 156} 157 158function extractElementNode(element) { 159 if (!element[0]) return element; 160 for (var i = 0; i < element.length; i++) { 161 var elm = element[i]; 162 if (elm.nodeType == ELEMENT_NODE) { 163 return elm; 164 } 165 } 166} 167 168function $$addClass($$jqLite, element, className) { 169 forEach(element, function(elm) { 170 $$jqLite.addClass(elm, className); 171 }); 172} 173 174function $$removeClass($$jqLite, element, className) { 175 forEach(element, function(elm) { 176 $$jqLite.removeClass(elm, className); 177 }); 178} 179 180function applyAnimationClassesFactory($$jqLite) { 181 return function(element, options) { 182 if (options.addClass) { 183 $$addClass($$jqLite, element, options.addClass); 184 options.addClass = null; 185 } 186 if (options.removeClass) { 187 $$removeClass($$jqLite, element, options.removeClass); 188 options.removeClass = null; 189 } 190 } 191} 192 193function prepareAnimationOptions(options) { 194 options = options || {}; 195 if (!options.$$prepared) { 196 var domOperation = options.domOperation || noop; 197 options.domOperation = function() { 198 options.$$domOperationFired = true; 199 domOperation(); 200 domOperation = noop; 201 }; 202 options.$$prepared = true; 203 } 204 return options; 205} 206 207function applyAnimationStyles(element, options) { 208 applyAnimationFromStyles(element, options); 209 applyAnimationToStyles(element, options); 210} 211 212function applyAnimationFromStyles(element, options) { 213 if (options.from) { 214 element.css(options.from); 215 options.from = null; 216 } 217} 218 219function applyAnimationToStyles(element, options) { 220 if (options.to) { 221 element.css(options.to); 222 options.to = null; 223 } 224} 225 226function mergeAnimationOptions(element, target, newOptions) { 227 var toAdd = (target.addClass || '') + ' ' + (newOptions.addClass || ''); 228 var toRemove = (target.removeClass || '') + ' ' + (newOptions.removeClass || ''); 229 var classes = resolveElementClasses(element.attr('class'), toAdd, toRemove); 230 231 if (newOptions.preparationClasses) { 232 target.preparationClasses = concatWithSpace(newOptions.preparationClasses, target.preparationClasses); 233 delete newOptions.preparationClasses; 234 } 235 236 // noop is basically when there is no callback; otherwise something has been set 237 var realDomOperation = target.domOperation !== noop ? target.domOperation : null; 238 239 extend(target, newOptions); 240 241 // TODO(matsko or sreeramu): proper fix is to maintain all animation callback in array and call at last,but now only leave has the callback so no issue with this. 242 if (realDomOperation) { 243 target.domOperation = realDomOperation; 244 } 245 246 if (classes.addClass) { 247 target.addClass = classes.addClass; 248 } else { 249 target.addClass = null; 250 } 251 252 if (classes.removeClass) { 253 target.removeClass = classes.removeClass; 254 } else { 255 target.removeClass = null; 256 } 257 258 return target; 259} 260 261function resolveElementClasses(existing, toAdd, toRemove) { 262 var ADD_CLASS = 1; 263 var REMOVE_CLASS = -1; 264 265 var flags = {}; 266 existing = splitClassesToLookup(existing); 267 268 toAdd = splitClassesToLookup(toAdd); 269 forEach(toAdd, function(value, key) { 270 flags[key] = ADD_CLASS; 271 }); 272 273 toRemove = splitClassesToLookup(toRemove); 274 forEach(toRemove, function(value, key) { 275 flags[key] = flags[key] === ADD_CLASS ? null : REMOVE_CLASS; 276 }); 277 278 var classes = { 279 addClass: '', 280 removeClass: '' 281 }; 282 283 forEach(flags, function(val, klass) { 284 var prop, allow; 285 if (val === ADD_CLASS) { 286 prop = 'addClass'; 287 allow = !existing[klass]; 288 } else if (val === REMOVE_CLASS) { 289 prop = 'removeClass'; 290 allow = existing[klass]; 291 } 292 if (allow) { 293 if (classes[prop].length) { 294 classes[prop] += ' '; 295 } 296 classes[prop] += klass; 297 } 298 }); 299 300 function splitClassesToLookup(classes) { 301 if (isString(classes)) { 302 classes = classes.split(' '); 303 } 304 305 var obj = {}; 306 forEach(classes, function(klass) { 307 // sometimes the split leaves empty string values 308 // incase extra spaces were applied to the options 309 if (klass.length) { 310 obj[klass] = true; 311 } 312 }); 313 return obj; 314 } 315 316 return classes; 317} 318 319function getDomNode(element) { 320 return (element instanceof angular.element) ? element[0] : element; 321} 322 323function applyGeneratedPreparationClasses(element, event, options) { 324 var classes = ''; 325 if (event) { 326 classes = pendClasses(event, EVENT_CLASS_PREFIX, true); 327 } 328 if (options.addClass) { 329 classes = concatWithSpace(classes, pendClasses(options.addClass, ADD_CLASS_SUFFIX)); 330 } 331 if (options.removeClass) { 332 classes = concatWithSpace(classes, pendClasses(options.removeClass, REMOVE_CLASS_SUFFIX)); 333 } 334 if (classes.length) { 335 options.preparationClasses = classes; 336 element.addClass(classes); 337 } 338} 339 340function clearGeneratedClasses(element, options) { 341 if (options.preparationClasses) { 342 element.removeClass(options.preparationClasses); 343 options.preparationClasses = null; 344 } 345 if (options.activeClasses) { 346 element.removeClass(options.activeClasses); 347 options.activeClasses = null; 348 } 349} 350 351function blockTransitions(node, duration) { 352 // we use a negative delay value since it performs blocking 353 // yet it doesn't kill any existing transitions running on the 354 // same element which makes this safe for class-based animations 355 var value = duration ? '-' + duration + 's' : ''; 356 applyInlineStyle(node, [TRANSITION_DELAY_PROP, value]); 357 return [TRANSITION_DELAY_PROP, value]; 358} 359 360function blockKeyframeAnimations(node, applyBlock) { 361 var value = applyBlock ? 'paused' : ''; 362 var key = ANIMATION_PROP + ANIMATION_PLAYSTATE_KEY; 363 applyInlineStyle(node, [key, value]); 364 return [key, value]; 365} 366 367function applyInlineStyle(node, styleTuple) { 368 var prop = styleTuple[0]; 369 var value = styleTuple[1]; 370 node.style[prop] = value; 371} 372 373function concatWithSpace(a,b) { 374 if (!a) return b; 375 if (!b) return a; 376 return a + ' ' + b; 377} 378 379var $$rAFSchedulerFactory = ['$$rAF', function($$rAF) { 380 var queue, cancelFn; 381 382 function scheduler(tasks) { 383 // we make a copy since RAFScheduler mutates the state 384 // of the passed in array variable and this would be difficult 385 // to track down on the outside code 386 queue = queue.concat(tasks); 387 nextTick(); 388 } 389 390 queue = scheduler.queue = []; 391 392 /* waitUntilQuiet does two things: 393 * 1. It will run the FINAL `fn` value only when an uncanceled RAF has passed through 394 * 2. It will delay the next wave of tasks from running until the quiet `fn` has run. 395 * 396 * The motivation here is that animation code can request more time from the scheduler 397 * before the next wave runs. This allows for certain DOM properties such as classes to 398 * be resolved in time for the next animation to run. 399 */ 400 scheduler.waitUntilQuiet = function(fn) { 401 if (cancelFn) cancelFn(); 402 403 cancelFn = $$rAF(function() { 404 cancelFn = null; 405 fn(); 406 nextTick(); 407 }); 408 }; 409 410 return scheduler; 411 412 function nextTick() { 413 if (!queue.length) return; 414 415 var items = queue.shift(); 416 for (var i = 0; i < items.length; i++) { 417 items[i](); 418 } 419 420 if (!cancelFn) { 421 $$rAF(function() { 422 if (!cancelFn) nextTick(); 423 }); 424 } 425 } 426}]; 427 428var $$AnimateChildrenDirective = [function() { 429 return function(scope, element, attrs) { 430 var val = attrs.ngAnimateChildren; 431 if (angular.isString(val) && val.length === 0) { //empty attribute 432 element.data(NG_ANIMATE_CHILDREN_DATA, true); 433 } else { 434 attrs.$observe('ngAnimateChildren', function(value) { 435 value = value === 'on' || value === 'true'; 436 element.data(NG_ANIMATE_CHILDREN_DATA, value); 437 }); 438 } 439 }; 440}]; 441 442var ANIMATE_TIMER_KEY = '$$animateCss'; 443 444/** 445 * @ngdoc service 446 * @name $animateCss 447 * @kind object 448 * 449 * @description 450 * The `$animateCss` service is a useful utility to trigger customized CSS-based transitions/keyframes 451 * from a JavaScript-based animation or directly from a directive. The purpose of `$animateCss` is NOT 452 * to side-step how `$animate` and ngAnimate work, but the goal is to allow pre-existing animations or 453 * directives to create more complex animations that can be purely driven using CSS code. 454 * 455 * Note that only browsers that support CSS transitions and/or keyframe animations are capable of 456 * rendering animations triggered via `$animateCss` (bad news for IE9 and lower). 457 * 458 * ## Usage 459 * Once again, `$animateCss` is designed to be used inside of a registered JavaScript animation that 460 * is powered by ngAnimate. It is possible to use `$animateCss` directly inside of a directive, however, 461 * any automatic control over cancelling animations and/or preventing animations from being run on 462 * child elements will not be handled by Angular. For this to work as expected, please use `$animate` to 463 * trigger the animation and then setup a JavaScript animation that injects `$animateCss` to trigger 464 * the CSS animation. 465 *
466 * The example below shows how we can create a folding animation on an element using `ng-if`: 467 * 468 * ```html 469 * <!-- notice the `fold-animation` CSS class --> 470 * <div ng-if="onOff" class="fold-animation"> 471 * This element will go BOOM 472 * </div> 473 * <button ng-click="onOff=true">Fold In</button> 474 * ``` 475 * 476 * Now we create the **JavaScript animation** that will trigger the CSS transition: 477 * 478 * ```js 479 * ngModule.animation('.fold-animation', ['$animateCss', function($animateCss) { 480 * return { 481 * enter: function(element, doneFn) { 482 * var height = element[0].offsetHeight; 483 * return $animateCss(element, { 484 * from: { height:'0px' }, 485 * to: { height:height + 'px' }, 486 * duration: 1 // one second 487 * }); 488 * } 489 * } 490 * }]); 491 * ``` 492 * 493 * ## More Advanced Uses 494 * 495 * `$animateCss` is the underlying code that ngAnimate uses to power **CSS-based animations** behind the scenes. Therefore CSS hooks 496 * like `.ng-EVENT`, `.ng-EVENT-active`, `.ng-EVENT-stagger` are all features that can be triggered using `$animateCss` via JavaScript code. 497 * 498 * This also means that just about any combination of adding classes, removing classes, setting styles, dynamically setting a keyframe animation, 499 * applying a hardcoded duration or delay value, changing the animation easing or applying a stagger animation are all options that work with 500 * `$animateCss`. The service itself is smart enough to figure out the combination of options and examine the element styling properties in order 501 * to provide a working animation that will run in CSS. 502 * 503 * The example below showcases a more advanced version of the `.fold-animation` from the example above: 504 * 505 * ```js 506 * ngModule.animation('.fold-animation', ['$animateCss', function($animateCss) { 507 * return { 508 * enter: function(element, doneFn) { 509 * var height = element[0].offsetHeight; 510 * return $animateCss(element, { 511 * addClass: 'red large-text pulse-twice', 512 * easing: 'ease-out', 513 * from: { height:'0px' }, 514 * to: { height:height + 'px' }, 515 * duration: 1 // one second 516 * }); 517 * } 518 * } 519 * }]); 520 * ``` 521 * 522 * Since we're adding/removing CSS classes then the CSS transition will also pick those up: 523 * 524 * ```css 525 * /* since a hardcoded duration value of 1 was provided in the JavaScript animation code, 526 * the CSS classes below will be transitioned despite them being defined as regular CSS classes */ 527 * .red { background:red; } 528 * .large-text { font-size:20px; } 529 * 530 * /* we can also use a keyframe animation and $animateCss will make it work alongside the transition */ 531 * .pulse-twice { 532 * animation: 0.5s pulse linear 2; 533 * -webkit-animation: 0.5s pulse linear 2; 534 * } 535 * 536 * @keyframes pulse { 537 * from { transform: scale(0.5); } 538 * to { transform: scale(1.5); } 539 * } 540 * 541 * @-webkit-keyframes pulse { 542 * from { -webkit-transform: scale(0.5); } 543 * to { -webkit-transform: scale(1.5); } 544 * } 545 * ``` 546 * 547 * Given this complex combination of CSS classes, styles and options, `$animateCss` will figure everything out and make the animation happen. 548 * 549 * ## How the Options are handled 550 * 551 * `$animateCss` is very versatile and intelligent when it comes to figuring out what configurations to apply to the element to ensure the animation 552 * 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 553 * styles using the `from` and `to` properties. 554 * 555 * ```js 556 * var animator = $animateCss(element, { 557 * from: { background:'red' }, 558 * to: { background:'blue' } 559 * }); 560 * animator.start(); 561 * ``` 562 * 563 * ```css 564 * .rotating-animation { 565 * animation:0.5s rotate linear; 566 * -webkit-animation:0.5s rotate linear; 567 * } 568 * 569 * @keyframes rotate { 570 * from { transform: rotate(0deg); } 571 * to { transform: rotate(360deg); } 572 * } 573 * 574 * @-webkit-keyframes rotate { 575 * from { -webkit-transform: rotate(0deg); } 576 * to { -webkit-transform: rotate(360deg); } 577 * } 578 * ``` 579 * 580 * The missing pieces here are that we do not have a transition set
580(within the CSS code nor within the `$animateCss` options) and the duration of the animation is 581 * going to be detected from what the keyframe styles on the CSS class are. In this event, `$animateCss` will automatically create an inline transition 582 * 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 583 * 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 584 * and spread across the transition and keyframe animation. 585 * 586 * ## What is returned 587 * 588 * `$animateCss` works in two stages: a preparation phase and an animation phase. Therefore when `$animateCss` is first called it will NOT actually 589 * 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 590 * added and removed on the element). Once `$animateCss` is called it will return an object with the following properties: 591 * 592 * ```js 593 * var animator = $animateCss(element, { ... }); 594 * ``` 595 * 596 * Now what do the contents of our `animator` variable look like: 597 * 598 * ```js 599 * { 600 * // starts the animation 601 * start: Function, 602 * 603 * // ends (aborts) the animation 604 * end: Function 605 * } 606 * ``` 607 * 608 * 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. 609 * If we choose not to run the animation then we MUST run `animation.end()` to perform a cleanup on the element (since some CSS classes and styles may have been 610 * applied to the element during the preparation phase). Note that all other properties such as duration, delay, transitions and keyframes are just properties 611 * and that changing them will not reconfigure the parameters of the animation. 612 * 613 * ### runner.done() vs runner.then() 614 * 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 615 * runner called `.done(callbackFn)`. The done method works the same as `.finally(callbackFn)`, however, it does **not trigger a digest to occur**. 616 * Therefore, for performance reasons, it's always best to use `runner.done(callback)` instead of `runner.then()`, `runner.catch()` or `runner.finally()` 617 * unless you really need a digest to kick off afterwards. 618 * 619 * 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 620 * (so there is no need to call `runner.done(doneFn)` inside of your JavaScript animation code). 621 * Check the {@link ngAnimate.$animateCss#usage animation code above} to see how this works. 622 * 623 * @param {DOMElement} element the element that will be animated 624 * @param {object} options the animation-related options that will be applied during the animation 625 * 626 * * `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 627 * 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.) 628 * * `structural` - Indicates that the `ng-` prefix will be added to the event class. Setting to `false` or omitting will turn `ng-EVENT` and 629 * `ng-EVENT-active` in `EVENT` and `EVENT-active`. Unused if `event` is omitted. 630 * * `easing` - The CSS easing value that will be applied to the transition or keyframe animation (or both). 631 * * `transitionStyle` - The raw CSS transition style that will be used (e.g. `1s linear all`). 632 * * `keyframeStyle` - The raw CSS keyframe animation style that will be used (e.g. `1s my_animation linear`). 633 * * `from` - The starting CSS styles (a key/value object) that will be applied at the start of the animation. 634 * * `to` - The ending CSS styles (a key/value object) that will be applied across the animation via a CSS transition. 635 * * `addClass` - A space separated list of CSS classes that will be added to the element and spread across the animation. 636 * * `removeClass` - A space separated list of CSS classes that will be removed from the element and spread across the animation. 637 * * `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` 638 * is provided then the animation will be skipped entirely. 639 * * `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 640 * 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 641 * 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 642 * CSS delay value. 643 * * `stagger` - A numeric time value representing the delay between successively animated elements 644 * ({@link ngAnimate#css-staggering-animations Click here to learn how CSS-based staggering works in ngAnimate.}) 645 * * `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 646 * `stagger` option value of `0.1` is used then there will be a stagger delay of `600ms`) 647 * * `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
647in time. (Note that this will prevent any transitions from occurring on the classes being added and removed.) 648 * * `cleanupStyles` - Whether or not the provided `from` and `to` styles will be removed once 649 * the animation is closed. This is useful for when the styles are used purely for the sake of 650 * the animation and do not have a lasting visual effect on the element (e.g. a collapse and open animation). 651 * By default this value is set to `false`. 652 * 653 * @return {object} an object with start and end methods and details about the animation. 654 * 655 * * `start` - The method to start the animation. This will return a `Promise` when called. 656 * * `end` - This method will cancel the animation and remove all applied CSS classes and styles. 657 */ 658var ONE_SECOND = 1000; 659var BASE_TEN = 10; 660 661var ELAPSED_TIME_MAX_DECIMAL_PLACES = 3; 662var CLOSING_TIME_BUFFER = 1.5; 663 664var DETECT_CSS_PROPERTIES = { 665 transitionDuration: TRANSITION_DURATION_PROP, 666 transitionDelay: TRANSITION_DELAY_PROP, 667 transitionProperty: TRANSITION_PROP + PROPERTY_KEY, 668 animationDuration: ANIMATION_DURATION_PROP, 669 animationDelay: ANIMATION_DELAY_PROP, 670 animationIterationCount: ANIMATION_PROP + ANIMATION_ITERATION_COUNT_KEY 671}; 672 673var DETECT_STAGGER_CSS_PROPERTIES = { 674 transitionDuration: TRANSITION_DURATION_PROP, 675 transitionDelay: TRANSITION_DELAY_PROP, 676 animationDuration: ANIMATION_DURATION_PROP, 677 animationDelay: ANIMATION_DELAY_PROP 678}; 679 680function getCssKeyframeDurationStyle(duration) { 681 return [ANIMATION_DURATION_PROP, duration + 's']; 682} 683 684function getCssDelayStyle(delay, isKeyframeAnimation) { 685 var prop = isKeyframeAnimation ? ANIMATION_DELAY_PROP : TRANSITION_DELAY_PROP; 686 return [prop, delay + 's']; 687} 688 689function computeCssStyles($window, element, properties) { 690 var styles = Object.create(null); 691 var detectedStyles = $window.getComputedStyle(element) || {}; 692 forEach(properties, function(formalStyleName, actualStyleName) { 693 var val = detectedStyles[formalStyleName]; 694 if (val) { 695 var c = val.charAt(0);
696 697 // only numerical-based values have a negative sign or digit as the first value 698 if (c === '-' || c === '+' || c >= 0) { 699 val = parseMaxTime(val); 700 } 701 702 // by setting this to null in the event that the delay is not set or is set directly as 0 703 // then we can still allow for negative values to be used later on and not mistake this 704 // value for being greater than any other negative value. 705 if (val === 0) { 706 val = null; 707 } 708 styles[actualStyleName] = val; 709 } 710 }); 711 712 return styles; 713} 714 715function parseMaxTime(str) { 716 var maxValue = 0; 717 var values = str.split(/\s*,\s*/); 718 forEach(values, function(value) { 719 // it's always safe to consider only second values and omit `ms` values since 720 // getComputedStyle will always handle the conversion for us 721 if (value.charAt(value.length - 1) == 's') { 722 value = value.substring(0, value.length - 1); 723 } 724 value = parseFloat(value) || 0; 725 maxValue = maxValue ? Math.max(value, maxValue) : value; 726 }); 727 return maxValue; 728} 729 730function truthyTimingValue(val) { 731 return val === 0 || val != null; 732} 733 734function getCssTransitionDurationStyle(duration, applyOnlyDuration) { 735 var style = TRANSITION_PROP; 736 var value = duration + 's'; 737 if (applyOnlyDuration) { 738 style += DURATION_KEY; 739 } else { 740 value += ' linear all'; 741 } 742 return [style, value]; 743} 744 745function createLocalCacheLookup() { 746 var cache = Object.create(null); 747 return { 748 flush: function() { 749 cache = Object.create(null); 750 }, 751 752 count: function(key) { 753 var entry = cache[key]; 754 return entry ? entry.total : 0; 755 }, 756 757 get: function(key) { 758 var entry = cache[key]; 759 return entry && entry.value; 760 }, 761 762 put: function(key, value) { 763 if (!cache[key]) { 764 cache[key] = { total: 1, value: value }; 765 } else { 766 cache[key].total++; 767 } 768 } 769 }; 770} 771 772// we do not reassign an already present style value since 773// if we detect the style property value again we may be 774// detecting styles that were added via the `from` styles. 775// We make use of `isDefined` here since an empty string 776// or null value (which is what getPropertyValue will return 777// for a non-existing style) will still be marked as a valid 778// value for the style (a falsy value implies that the style 779// is to be removed at the end of the animation). If we had a simple 780// "OR" statement then it would not be enough to catch that. 781function registerRestorableStyles(backup, node, properties) { 782 forEach(properties, function(prop) { 783 backup[prop] = isDefined(backup[prop]) 784 ? backup[prop] 785 : node.style.getPropertyValue(prop); 786 }); 787} 788 789var $AnimateCssProvider = ['$animateProvider', function($animateProvider) { 790 var gcsLookup = createLocalCacheLookup(); 791 var gcsStaggerLookup = createLocalCacheLookup(); 792 793 this.$get = ['$window', '$$jqLite', '$$AnimateRunner', '$timeout', 794 '$$forceReflow', '$sniffer', '$$rAFScheduler', '$$animateQueue', 795 function($window, $$jqLite, $$AnimateRunner, $timeout, 796 $$forceReflow, $sniffer, $$rAFScheduler, $$animateQueue) { 797 798 var applyAnimationClasses = applyAnimationClassesFactory($$jqLite); 799 800 var parentCounter = 0; 801 function gcsHashFn(node, extraClasses) { 802 var KEY = "$$ngAnimateParentKey"; 803 var parentNode = node.parentNode; 804 var parentID = parentNode[KEY] || (parentNode[KEY] = ++parentCounter); 805 return parentID + '-' + node.getAttribute('class') + '-' + extraClasses; 806 } 807 808 function computeCachedCssStyles(node, className, cacheKey, properties) { 809 var timings = gcsLookup.get(cacheKey); 810 811 if (!timings) { 812 timings = computeCssStyles($window, node, properties); 813 if (timings.animationIterationCount === 'infinite') { 814 timings.animationIterationCount = 1; 815 } 816 } 817 818 // we keep putting this in multiple times even though the value and the cacheKey are the same 819 // because we're keeping an internal tally of how many duplicate animations are detected. 820 gcsLookup.put(cacheKey, timings); 821 return timings; 822 } 823 824 function computeCachedCssStaggerStyles(node, className, cacheKey, properties) { 825 var stagger; 826 827 // if we have one or more existing matches of matching elements 828 // containing the same parent + CSS styles (which is how cacheKey works) 829 // then staggering is possible 830 if (gcsLookup.count(cacheKey) > 0) { 831 stagger = gcsStaggerLookup.get(cacheKey); 832 833 if (!stagger) { 834 var staggerClassName = pendClasses(className, '-stagger'); 835 836 $$jqLite.addClass(node, staggerClassName); 837 838 stagger = computeCssStyles($window, node, properties); 839 840 // force the conversion of a null value to zero incase not set 841 stagger.animationDuration = Math.max(stagger.animationDuration, 0); 842 stagger.transitionDuration = Math.max(stagger.transitionDuration, 0); 843 844 $$jqLite.removeClass(node, staggerClassName); 845 846 gcsStaggerLookup.put(cacheKey, stagger); 847 } 848 } 849 850 return stagger || {}; 851 } 852 853 var cancelLastRAFRequest; 854 var rafWaitQueue = []; 855 function waitUntilQuiet(callback) { 856 rafWaitQueue.push(callback); 857 $$rAFScheduler.waitUntilQuiet(function() { 858 gcsLookup.flush(); 859 gcsStaggerLookup.flush(); 860 861 // DO NOT REMOVE THIS LINE OR REFACTOR OUT THE `pageWidth` variable. 862 // PLEASE EXAMINE THE `$$forceReflow` service to understand why. 863 var pageWidth = $$forceReflow(); 864 865 // we use a for loop to ensure that if the queue is changed 866 // during this looping then it will consider new requests 867 for (var i = 0; i < rafWaitQueue.length; i++) { 868 rafWaitQueue[i](pageWidth);
vendor: 53,717 bytes, lines 869-2366
869 } 870 rafWaitQueue.length = 0; 871 }); 872 } 873 874 function computeTimings(node, className, cacheKey) { 875 var timings = computeCachedCssStyles(node, className, cacheKey, DETECT_CSS_PROPERTIES); 876 var aD = timings.animationDelay; 877 var tD = timings.transitionDelay; 878 timings.maxDelay = aD && tD 879 ? Math.max(aD, tD) 880 : (aD || tD); 881 timings.maxDuration = Math.max( 882 timings.animationDuration * timings.animationIterationCount, 883 timings.transitionDuration); 884 885 return timings; 886 } 887 888 return function init(element, initialOptions) { 889 // all of the animation functions should create 890 // a copy of the options data, however, if a 891 // parent service has already created a copy then 892 // we should stick to using that 893 var options = initialOptions || {}; 894 if (!options.$$prepared) { 895 options = prepareAnimationOptions(copy(options)); 896 } 897 898 var restoreStyles = {}; 899 var node = getDomNode(element); 900 if (!node 901 || !node.parentNode 902 || !$$animateQueue.enabled()) { 903 return closeAndReturnNoopAnimator(); 904 } 905 906 var temporaryStyles = []; 907 var classes = element.attr('class'); 908 var styles = packageStyles(options); 909 var animationClosed; 910 var animationPaused; 911 var animationCompleted; 912 var runner; 913 var runnerHost; 914 var maxDelay; 915 var maxDelayTime; 916 var maxDuration; 917 var maxDurationTime; 918 var startTime; 919 var events = []; 920 921 if (options.duration === 0 || (!$sniffer.animations && !$sniffer.transitions)) { 922 return closeAndReturnNoopAnimator(); 923 } 924 925 var method = options.event && isArray(options.event) 926 ? options.event.join(' ') 927 : options.event; 928 929 var isStructural = method && options.structural; 930 var structuralClassName = ''; 931 var addRemoveClassName = ''; 932 933 if (isStructural) { 934 structuralClassName = pendClasses(method, EVENT_CLASS_PREFIX, true); 935 } else if (method) { 936 structuralClassName = method; 937 } 938 939 if (options.addClass) { 940 addRemoveClassName += pendClasses(options.addClass, ADD_CLASS_SUFFIX); 941 } 942 943 if (options.removeClass) { 944 if (addRemoveClassName.length) { 945 addRemoveClassName += ' '; 946 } 947 addRemoveClassName += pendClasses(options.removeClass, REMOVE_CLASS_SUFFIX); 948 } 949 950 // there may be a situation where a structural animation is combined together 951 // with CSS classes that need to resolve before the animation is computed. 952 // However this means that there is no explicit CSS code to block the animation 953 // from happening (by setting 0s none in the class name). If this is the case 954 // we need to apply the classes before the first rAF so we know to continue if 955 // there actually is a detected transition or keyframe animation 956 if (options.applyClassesEarly && addRemoveClassName.length) { 957 applyAnimationClasses(element, options); 958 } 959 960 var preparationClasses = [structuralClassName, addRemoveClassName].join(' ').trim(); 961 var fullClassName = classes + ' ' + preparationClasses; 962 var activeClasses = pendClasses(preparationClasses, ACTIVE_CLASS_SUFFIX); 963 var hasToStyles = styles.to && Object.keys(styles.to).length > 0; 964 var containsKeyframeAnimation = (options.keyframeStyle || '').length > 0; 965 966 // there is no way we can trigger an animation if no styles and 967 // no classes are being applied which would then trigger a transition, 968 // unless there a is raw keyframe value that is applied to the element. 969 if (!containsKeyframeAnimation 970 && !hasToStyles 971 && !preparationClasses) { 972 return closeAndReturnNoopAnimator(); 973 } 974 975 var cacheKey, stagger; 976 if (options.stagger > 0) { 977 var staggerVal = parseFloat(options.stagger); 978 stagger = { 979 transitionDelay: staggerVal, 980 animationDelay: staggerVal, 981 transitionDuration: 0, 982 animationDuration: 0 983 }; 984 } else { 985 cacheKey = gcsHashFn(node, fullClassName); 986 stagger = computeCachedCssStaggerStyles(node, preparationClasses, cacheKey, DETECT_STAGGER_CSS_PROPERTIES); 987 } 988 989 if (!options.$$skipPreparationClasses) { 990 $$jqLite.addClass(element, preparationClasses); 991 } 992 993 var applyOnlyDuration; 994 995 if (options.transitionStyle) { 996 var transitionStyle = [TRANSITION_PROP, options.transitionStyle]; 997 applyInlineStyle(node, transitionStyle); 998 temporaryStyles.push(transitionStyle); 999 } 1000 1001 if (options.duration >= 0) { 1002 applyOnlyDuration = node.style[TRANSITION_PROP].length > 0; 1003 var durationStyle = getCssTransitionDurationStyle(options.duration, applyOnlyDuration); 1004 1005 // we set the duration so that it will be picked up by getComputedStyle later 1006 applyInlineStyle(node, durationStyle); 1007 temporaryStyles.push(durationStyle); 1008 } 1009 1010 if (options.keyframeStyle) { 1011 var keyframeStyle = [ANIMATION_PROP, options.keyframeStyle]; 1012 applyInlineStyle(node, keyframeStyle); 1013 temporaryStyles.push(keyframeStyle); 1014 } 1015 1016 var itemIndex = stagger 1017 ? options.staggerIndex >= 0 1018 ? options.staggerIndex 1019 : gcsLookup.count(cacheKey) 1020 : 0; 1021 1022 var isFirst = itemIndex === 0; 1023 1024 // this is a pre-emptive way of forcing the setup classes to be added and applied INSTANTLY 1025 // without causing any combination of transitions to kick in. By adding a negative delay value 1026 // it forces the setup class' transition to end immediately. We later then remove the negative 1027 // transition delay to allow for the transition to naturally do it's thing. The beauty here is 1028 // that if there is no transition defined then nothing will happen and this will also allow 1029 // other transitions to be stacked on top of each other without any chopping them out. 1030 if (isFirst && !options.skipBlocking) { 1031 blockTransitions(node, SAFE_FAST_FORWARD_DURATION_VALUE); 1032 } 1033 1034 var timings = computeTimings(node, fullClassName, cacheKey); 1035 var relativeDelay = timings.maxDelay; 1036 maxDelay = Math.max(relativeDelay, 0); 1037 maxDuration = timings.maxDuration; 1038 1039 var flags = {}; 1040 flags.hasTransitions = timings.transitionDuration > 0; 1041 flags.hasAnimations = timings.animationDuration > 0; 1042 flags.hasTransitionAll = flags.hasTransitions && timings.transitionProperty == 'all'; 1043 flags.applyTransitionDuration = hasToStyles && ( 1044 (flags.hasTransitions && !flags.hasTransitionAll) 1045 || (flags.hasAnimations && !flags.hasTransitions)); 1046 flags.applyAnimationDuration = options.duration && flags.hasAnimations; 1047 flags.applyTransitionDelay = truthyTimingValue(options.delay) && (flags.applyTransitionDuration || flags.hasTransitions); 1048 flags.applyAnimationDelay = truthyTimingValue(options.delay) && flags.hasAnimations; 1049 flags.recalculateTimingStyles = addRemoveClassName.length > 0; 1050 1051 if (flags.applyTransitionDuration || flags.applyAnimationDuration) { 1052 maxDuration = options.duration ? parseFloat(options.duration) : maxDuration; 1053 1054 if (flags.applyTransitionDuration) { 1055 flags.hasTransitions = true; 1056 timings.transitionDuration = maxDuration; 1057 applyOnlyDuration = node.style[TRANSITION_PROP + PROPERTY_KEY].length > 0; 1058 temporaryStyles.push(getCssTransitionDurationStyle(maxDuration, applyOnlyDuration)); 1059 } 1060 1061 if (flags.applyAnimationDuration) { 1062 flags.hasAnimations = true; 1063 timings.animationDuration = maxDuration; 1064 temporaryStyles.push(getCssKeyframeDurationStyle(maxDuration)); 1065 } 1066 } 1067 1068 if (maxDuration === 0 && !flags.recalculateTimingStyles) { 1069 return closeAndReturnNoopAnimator(); 1070 } 1071 1072 if (options.delay != null) { 1073 var delayStyle; 1074 if (typeof options.delay !== "boolean") { 1075 delayStyle = parseFloat(options.delay); 1076 // number in options.delay means we have to recalculate the delay for the closing timeout 1077 maxDelay = Math.max(delayStyle, 0); 1078 } 1079 1080 if (flags.applyTransitionDelay) { 1081 temporaryStyles.push(getCssDelayStyle(delayStyle)); 1082 } 1083 1084 if (flags.applyAnimationDelay) { 1085 temporaryStyles.push(getCssDelayStyle(delayStyle, true)); 1086 } 1087 } 1088 1089 // we need to recalculate the delay value since we used a pre-emptive negative 1090 // delay value and the delay value is required for the final event checking. This 1091 // property will ensure that this will happen after the RAF phase has passed. 1092 if (options.duration == null && timings.transitionDuration > 0) { 1093 flags.recalculateTimingStyles = flags.recalculateTimingStyles || isFirst; 1094 } 1095 1096 maxDelayTime = maxDelay * ONE_SECOND; 1097 maxDurationTime = maxDuration * ONE_SECOND; 1098 if (!options.skipBlocking) { 1099 flags.blockTransition = timings.transitionDuration > 0; 1100 flags.blockKeyframeAnimation = timings.animationDuration > 0 && 1101 stagger.animationDelay > 0 && 1102 stagger.animationDuration === 0; 1103 } 1104 1105 if (options.from) { 1106 if (options.cleanupStyles) { 1107 registerRestorableStyles(restoreStyles, node, Object.keys(options.from)); 1108 } 1109 applyAnimationFromStyles(element, options); 1110 } 1111 1112 if (flags.blockTransition || flags.blockKeyframeAnimation) { 1113 applyBlocking(maxDuration); 1114 } else if (!options.skipBlocking) { 1115 blockTransitions(node, false); 1116 } 1117 1118 // TODO(matsko): for 1.5 change this code to have an animator object for better debugging 1119 return { 1120 $$willAnimate: true, 1121 end: endFn, 1122 start: function() { 1123 if (animationClosed) return; 1124 1125 runnerHost = { 1126 end: endFn, 1127 cancel: cancelFn, 1128 resume: null, //this will be set during the start() phase 1129 pause: null 1130 }; 1131 1132 runner = new $$AnimateRunner(runnerHost); 1133 1134 waitUntilQuiet(start); 1135 1136 // we don't have access to pause/resume the animation 1137 // since it hasn't run yet. AnimateRunner will therefore 1138 // set noop functions for resume and pause and they will 1139 // later be overridden once the animation is triggered 1140 return runner; 1141 } 1142 }; 1143 1144 function endFn() { 1145 close(); 1146 } 1147 1148 function cancelFn() { 1149 close(true); 1150 } 1151 1152 function close(rejected) { // jshint ignore:line 1153 // if the promise has been called already then we shouldn't close 1154 // the animation again 1155 if (animationClosed || (animationCompleted && animationPaused)) return; 1156 animationClosed = true; 1157 animationPaused = false; 1158 1159 if (!options.$$skipPreparationClasses) { 1160 $$jqLite.removeClass(element, preparationClasses); 1161 } 1162 $$jqLite.removeClass(element, activeClasses); 1163 1164 blockKeyframeAnimations(node, false); 1165 blockTransitions(node, false); 1166 1167 forEach(temporaryStyles, function(entry) { 1168 // There is only one way to remove inline style properties entirely from elements. 1169 // By using `removeProperty` this works, but we need to convert camel-cased CSS 1170 // styles down to hyphenated values. 1171 node.style[entry[0]] = ''; 1172 }); 1173 1174 applyAnimationClasses(element, options); 1175 applyAnimationStyles(element, options); 1176 1177 if (Object.keys(restoreStyles).length) { 1178 forEach(restoreStyles, function(value, prop) { 1179 value ? node.style.setProperty(prop, value) 1180 : node.style.removeProperty(prop); 1181 }); 1182 } 1183 1184 // the reason why we have this option is to allow a synchronous closing callback 1185 // that is fired as SOON as the animation ends (when the CSS is removed) or if 1186 // the animation never takes off at all. A good example is a leave animation since 1187 // the element must be removed just after the animation is over or else the element 1188 // will appear on screen for one animation frame causing an overbearing flicker. 1189 if (options.onDone) { 1190 options.onDone(); 1191 } 1192 1193 if (events && events.length) { 1194 // Remove the transitionend / animationend listener(s) 1195 element.off(events.join(' '), onAnimationProgress); 1196 } 1197 1198 // if the preparation function fails then the promise is not setup 1199 if (runner) { 1200 runner.complete(!rejected); 1201 } 1202 } 1203 1204 function applyBlocking(duration) { 1205 if (flags.blockTransition) { 1206 blockTransitions(node, duration); 1207 } 1208 1209 if (flags.blockKeyframeAnimation) { 1210 blockKeyframeAnimations(node, !!duration); 1211 } 1212 } 1213 1214 function closeAndReturnNoopAnimator() { 1215 runner = new $$AnimateRunner({ 1216 end: endFn, 1217 cancel: cancelFn 1218 }); 1219 1220 // should flush the cache animation 1221 waitUntilQuiet(noop); 1222 close(); 1223 1224 return { 1225 $$willAnimate: false, 1226 start: function() { 1227 return runner; 1228 }, 1229 end: endFn 1230 }; 1231 } 1232 1233 function onAnimationProgress(event) { 1234 event.stopPropagation(); 1235 var ev = event.originalEvent || event; 1236 1237 // we now always use `Date.now()` due to the recent changes with 1238 // event.timeStamp in Firefox, Webkit and Chrome (see #13494 for more info) 1239 var timeStamp = ev.$manualTimeStamp || Date.now(); 1240 1241 /* Firefox (or possibly just Gecko) likes to not round values up 1242 * when a ms measurement is used for the animation */ 1243 var elapsedTime = parseFloat(ev.elapsedTime.toFixed(ELAPSED_TIME_MAX_DECIMAL_PLACES)); 1244 1245 /* $manualTimeStamp is a mocked timeStamp value which is set 1246 * within browserTrigger(). This is only here so that tests can 1247 * mock animations properly. Real events fallback to event.timeStamp, 1248 * or, if they don't, then a timeStamp is automatically created for them. 1249 * We're checking to see if the timeStamp surpasses the expected delay, 1250 * but we're using elapsedTime instead of the timeStamp on the 2nd 1251 * pre-condition since animationPauseds sometimes close off early */ 1252 if (Math.max(timeStamp - startTime, 0) >= maxDelayTime && elapsedTime >= maxDuration) { 1253 // we set this flag to ensure that if the transition is paused then, when resumed, 1254 // the animation will automatically close itself since transitions cannot be paused. 1255 animationCompleted = true; 1256 close(); 1257 } 1258 } 1259 1260 function start() { 1261 if (animationClosed) return; 1262 if (!node.parentNode) { 1263 close(); 1264 return; 1265 } 1266 1267 // even though we only pause keyframe animations here the pause flag 1268 // will still happen when transitions are used. Only the transition will 1269 // not be paused since that is not possible. If the animation ends when 1270 // paused then it will not complete until unpaused or cancelled. 1271 var playPause = function(playAnimation) { 1272 if (!animationCompleted) { 1273 animationPaused = !playAnimation; 1274 if (timings.animationDuration) { 1275 var value = blockKeyframeAnimations(node, animationPaused); 1276 animationPaused 1277 ? temporaryStyles.push(value) 1278 : removeFromArray(temporaryStyles, value); 1279 } 1280 } else if (animationPaused && playAnimation) { 1281 animationPaused = false; 1282 close(); 1283 } 1284 }; 1285 1286 // checking the stagger duration prevents an accidentally cascade of the CSS delay style 1287 // being inherited from the parent. If the transition duration is zero then we can safely 1288 // rely that the delay value is an intentional stagger delay style. 1289 var maxStagger = itemIndex > 0 1290 && ((timings.transitionDuration && stagger.transitionDuration === 0) || 1291 (timings.animationDuration && stagger.animationDuration === 0)) 1292 && Math.max(stagger.animationDelay, stagger.transitionDelay); 1293 if (maxStagger) { 1294 $timeout(triggerAnimationStart, 1295 Math.floor(maxStagger * itemIndex * ONE_SECOND), 1296 false); 1297 } else { 1298 triggerAnimationStart(); 1299 } 1300 1301 // this will decorate the existing promise runner with pause/resume methods 1302 runnerHost.resume = function() { 1303 playPause(true); 1304 }; 1305 1306 runnerHost.pause = function() { 1307 playPause(false); 1308 }; 1309 1310 function triggerAnimationStart() { 1311 // just incase a stagger animation kicks in when the animation 1312 // itself was cancelled entirely 1313 if (animationClosed) return; 1314 1315 applyBlocking(false); 1316 1317 forEach(temporaryStyles, function(entry) { 1318 var key = entry[0]; 1319 var value = entry[1]; 1320 node.style[key] = value; 1321 }); 1322 1323 applyAnimationClasses(element, options); 1324 $$jqLite.addClass(element, activeClasses); 1325 1326 if (flags.recalculateTimingStyles) { 1327 fullClassName = node.className + ' ' + preparationClasses; 1328 cacheKey = gcsHashFn(node, fullClassName); 1329 1330 timings = computeTimings(node, fullClassName, cacheKey); 1331 relativeDelay = timings.maxDelay; 1332 maxDelay = Math.max(relativeDelay, 0); 1333 maxDuration = timings.maxDuration; 1334 1335 if (maxDuration === 0) { 1336 close(); 1337 return; 1338 } 1339 1340 flags.hasTransitions = timings.transitionDuration > 0; 1341 flags.hasAnimations = timings.animationDuration > 0; 1342 } 1343 1344 if (flags.applyAnimationDelay) { 1345 relativeDelay = typeof options.delay !== "boolean" && truthyTimingValue(options.delay) 1346 ? parseFloat(options.delay) 1347 : relativeDelay; 1348 1349 maxDelay = Math.max(relativeDelay, 0); 1350 timings.animationDelay = relativeDelay; 1351 delayStyle = getCssDelayStyle(relativeDelay, true); 1352 temporaryStyles.push(delayStyle); 1353 node.style[delayStyle[0]] = delayStyle[1]; 1354 } 1355 1356 maxDelayTime = maxDelay * ONE_SECOND; 1357 maxDurationTime = maxDuration * ONE_SECOND; 1358 1359 if (options.easing) { 1360 var easeProp, easeVal = options.easing; 1361 if (flags.hasTransitions) { 1362 easeProp = TRANSITION_PROP + TIMING_KEY; 1363 temporaryStyles.push([easeProp, easeVal]); 1364 node.style[easeProp] = easeVal; 1365 } 1366 if (flags.hasAnimations) { 1367 easeProp = ANIMATION_PROP + TIMING_KEY; 1368 temporaryStyles.push([easeProp, easeVal]); 1369 node.style[easeProp] = easeVal; 1370 } 1371 } 1372 1373 if (timings.transitionDuration) { 1374 events.push(TRANSITIONEND_EVENT); 1375 } 1376 1377 if (timings.animationDuration) { 1378 events.push(ANIMATIONEND_EVENT); 1379 } 1380 1381 startTime = Date.now(); 1382 var timerTime = maxDelayTime + CLOSING_TIME_BUFFER * maxDurationTime; 1383 var endTime = startTime + timerTime; 1384 1385 var animationsData = element.data(ANIMATE_TIMER_KEY) || []; 1386 var setupFallbackTimer = true; 1387 if (animationsData.length) { 1388 var currentTimerData = animationsData[0]; 1389 setupFallbackTimer = endTime > currentTimerData.expectedEndTime; 1390 if (setupFallbackTimer) { 1391 $timeout.cancel(currentTimerData.timer); 1392 } else { 1393 animationsData.push(close); 1394 } 1395 } 1396 1397 if (setupFallbackTimer) { 1398 var timer = $timeout(onAnimationExpired, timerTime, false); 1399 animationsData[0] = { 1400 timer: timer, 1401 expectedEndTime: endTime 1402 }; 1403 animationsData.push(close); 1404 element.data(ANIMATE_TIMER_KEY, animationsData); 1405 } 1406 1407 if (events.length) { 1408 element.on(events.join(' '), onAnimationProgress); 1409 } 1410 1411 if (options.to) { 1412 if (options.cleanupStyles) { 1413 registerRestorableStyles(restoreStyles, node, Object.keys(options.to)); 1414 } 1415 applyAnimationToStyles(element, options); 1416 } 1417 } 1418 1419 function onAnimationExpired() { 1420 var animationsData = element.data(ANIMATE_TIMER_KEY); 1421 1422 // this will be false in the event that the element was 1423 // removed from the DOM (via a leave animation or something 1424 // similar) 1425 if (animationsData) { 1426 for (var i = 1; i < animationsData.length; i++) { 1427 animationsData[i](); 1428 } 1429 element.removeData(ANIMATE_TIMER_KEY); 1430 } 1431 } 1432 } 1433 }; 1434 }]; 1435}]; 1436 1437var $$AnimateCssDriverProvider = ['$$animationProvider', function($$animationProvider) { 1438 $$animationProvider.drivers.push('$$animateCssDriver'); 1439 1440 var NG_ANIMATE_SHIM_CLASS_NAME = 'ng-animate-shim'; 1441 var NG_ANIMATE_ANCHOR_CLASS_NAME = 'ng-anchor'; 1442 1443 var NG_OUT_ANCHOR_CLASS_NAME = 'ng-anchor-out'; 1444 var NG_IN_ANCHOR_CLASS_NAME = 'ng-anchor-in'; 1445 1446 function isDocumentFragment(node) { 1447 return node.parentNode && node.parentNode.nodeType === 11; 1448 } 1449 1450 this.$get = ['$animateCss', '$rootScope', '$$AnimateRunner', '$rootElement', '$sniffer', '$$jqLite', '$document', 1451 function($animateCss, $rootScope, $$AnimateRunner, $rootElement, $sniffer, $$jqLite, $document) { 1452 1453 // only browsers that support these properties can render animations 1454 if (!$sniffer.animations && !$sniffer.transitions) return noop; 1455 1456 var bodyNode = $document[0].body; 1457 var rootNode = getDomNode($rootElement); 1458 1459 var rootBodyElement = jqLite( 1460 // this is to avoid using something that exists outside of the body 1461 // we also special case the doc fragment case because our unit test code 1462 // appends the $rootElement to the body after the app has been bootstrapped 1463 isDocumentFragment(rootNode) || bodyNode.contains(rootNode) ? rootNode : bodyNode 1464 ); 1465 1466 var applyAnimationClasses = applyAnimationClassesFactory($$jqLite); 1467 1468 return function initDriverFn(animationDetails) { 1469 return animationDetails.from && animationDetails.to 1470 ? prepareFromToAnchorAnimation(animationDetails.from, 1471 animationDetails.to, 1472 animationDetails.classes, 1473 animationDetails.anchors) 1474 : prepareRegularAnimation(animationDetails); 1475 }; 1476 1477 function filterCssClasses(classes) { 1478 //remove all the `ng-` stuff 1479 return classes.replace(/\bng-\S+\b/g, ''); 1480 } 1481 1482 function getUniqueValues(a, b) { 1483 if (isString(a)) a = a.split(' '); 1484 if (isString(b)) b = b.split(' '); 1485 return a.filter(function(val) { 1486 return b.indexOf(val) === -1; 1487 }).join(' '); 1488 } 1489 1490 function prepareAnchoredAnimation(classes, outAnchor, inAnchor) { 1491 var clone = jqLite(getDomNode(outAnchor).cloneNode(true)); 1492 var startingClasses = filterCssClasses(getClassVal(clone)); 1493 1494 outAnchor.addClass(NG_ANIMATE_SHIM_CLASS_NAME); 1495 inAnchor.addClass(NG_ANIMATE_SHIM_CLASS_NAME); 1496 1497 clone.addClass(NG_ANIMATE_ANCHOR_CLASS_NAME); 1498 1499 rootBodyElement.append(clone); 1500 1501 var animatorIn, animatorOut = prepareOutAnimation(); 1502 1503 // the user may not end up using the `out` animation and 1504 // only making use of the `in` animation or vice-versa. 1505 // In either case we should allow this and not assume the 1506 // animation is over unless both animations are not used. 1507 if (!animatorOut) { 1508 animatorIn = prepareInAnimation(); 1509 if (!animatorIn) { 1510 return end(); 1511 } 1512 } 1513 1514 var startingAnimator = animatorOut || animatorIn; 1515 1516 return { 1517 start: function() { 1518 var runner; 1519 1520 var currentAnimation = startingAnimator.start(); 1521 currentAnimation.done(function() { 1522 currentAnimation = null; 1523 if (!animatorIn) { 1524 animatorIn = prepareInAnimation(); 1525 if (animatorIn) { 1526 currentAnimation = animatorIn.start(); 1527 currentAnimation.done(function() { 1528 currentAnimation = null; 1529 end(); 1530 runner.complete(); 1531 }); 1532 return currentAnimation; 1533 } 1534 } 1535 // in the event that there is no `in` animation 1536 end(); 1537 runner.complete(); 1538 }); 1539 1540 runner = new $$AnimateRunner({ 1541 end: endFn, 1542 cancel: endFn 1543 }); 1544 1545 return runner; 1546 1547 function endFn() { 1548 if (currentAnimation) { 1549 currentAnimation.end(); 1550 } 1551 } 1552 } 1553 }; 1554 1555 function calculateAnchorStyles(anchor) { 1556 var styles = {}; 1557 1558 var coords = getDomNode(anchor).getBoundingClientRect(); 1559 1560 // we iterate directly since safari messes up and doesn't return 1561 // all the keys for the coords object when iterated 1562 forEach(['width','height','top','left'], function(key) { 1563 var value = coords[key]; 1564 switch (key) { 1565 case 'top': 1566 value += bodyNode.scrollTop; 1567 break; 1568 case 'left': 1569 value += bodyNode.scrollLeft; 1570 break; 1571 } 1572 styles[key] = Math.floor(value) + 'px'; 1573 }); 1574 return styles; 1575 } 1576 1577 function prepareOutAnimation() { 1578 var animator = $animateCss(clone, { 1579 addClass: NG_OUT_ANCHOR_CLASS_NAME, 1580 delay: true, 1581 from: calculateAnchorStyles(outAnchor) 1582 }); 1583 1584 // read the comment within `prepareRegularAnimation` to understand 1585 // why this check is necessary 1586 return animator.$$willAnimate ? animator : null; 1587 } 1588 1589 function getClassVal(element) { 1590 return element.attr('class') || ''; 1591 } 1592 1593 function prepareInAnimation() { 1594 var endingClasses = filterCssClasses(getClassVal(inAnchor)); 1595 var toAdd = getUniqueValues(endingClasses, startingClasses); 1596 var toRemove = getUniqueValues(startingClasses, endingClasses); 1597 1598 var animator = $animateCss(clone, { 1599 to: calculateAnchorStyles(inAnchor), 1600 addClass: NG_IN_ANCHOR_CLASS_NAME + ' ' + toAdd, 1601 removeClass: NG_OUT_ANCHOR_CLASS_NAME + ' ' + toRemove, 1602 delay: true 1603 }); 1604 1605 // read the comment within `prepareRegularAnimation` to understand 1606 // why this check is necessary 1607 return animator.$$willAnimate ? animator : null; 1608 } 1609 1610 function end() { 1611 clone.remove(); 1612 outAnchor.removeClass(NG_ANIMATE_SHIM_CLASS_NAME); 1613 inAnchor.removeClass(NG_ANIMATE_SHIM_CLASS_NAME); 1614 } 1615 } 1616 1617 function prepareFromToAnchorAnimation(from, to, classes, anchors) { 1618 var fromAnimation = prepareRegularAnimation(from, noop); 1619 var toAnimation = prepareRegularAnimation(to, noop); 1620 1621 var anchorAnimations = []; 1622 forEach(anchors, function(anchor) { 1623 var outElement = anchor['out']; 1624 var inElement = anchor['in']; 1625 var animator = prepareAnchoredAnimation(classes, outElement, inElement); 1626 if (animator) { 1627 anchorAnimations.push(animator); 1628 } 1629 }); 1630 1631 // no point in doing anything when there are no elements to animate 1632 if (!fromAnimation && !toAnimation && anchorAnimations.length === 0) return; 1633 1634 return { 1635 start: function() { 1636 var animationRunners = []; 1637 1638 if (fromAnimation) { 1639 animationRunners.push(fromAnimation.start()); 1640 } 1641 1642 if (toAnimation) { 1643 animationRunners.push(toAnimation.start()); 1644 } 1645 1646 forEach(anchorAnimations, function(animation) { 1647 animationRunners.push(animation.start()); 1648 }); 1649 1650 var runner = new $$AnimateRunner({ 1651 end: endFn, 1652 cancel: endFn // CSS-driven animations cannot be cancelled, only ended 1653 }); 1654 1655 $$AnimateRunner.all(animationRunners, function(status) { 1656 runner.complete(status); 1657 }); 1658 1659 return runner; 1660 1661 function endFn() { 1662 forEach(animationRunners, function(runner) { 1663 runner.end(); 1664 }); 1665 } 1666 } 1667 }; 1668 } 1669 1670 function prepareRegularAnimation(animationDetails) { 1671 var element = animationDetails.element; 1672 var options = animationDetails.options || {}; 1673 1674 if (animationDetails.structural) { 1675 options.event = animationDetails.event; 1676 options.structural = true; 1677 options.applyClassesEarly = true; 1678 1679 // we special case the leave animation since we want to ensure that 1680 // the element is removed as soon as the animation is over. Otherwise 1681 // a flicker might appear or the element may not be removed at all 1682 if (animationDetails.event === 'leave') { 1683 options.onDone = options.domOperation; 1684 } 1685 } 1686 1687 // We assign the preparationClasses as the actual animation event since 1688 // the internals of $animateCss will just suffix the event token values 1689 // with `-active` to trigger the animation. 1690 if (options.preparationClasses) { 1691 options.event = concatWithSpace(options.event, options.preparationClasses); 1692 } 1693 1694 var animator = $animateCss(element, options); 1695 1696 // the driver lookup code inside of $$animation attempts to spawn a 1697 // driver one by one until a driver returns a.$$willAnimate animator object. 1698 // $animateCss will always return an object, however, it will pass in 1699 // a flag as a hint as to whether an animation was detected or not 1700 return animator.$$willAnimate ? animator : null; 1701 } 1702 }]; 1703}]; 1704 1705// TODO(matsko): use caching here to speed things up for detection 1706// TODO(matsko): add documentation 1707// by the time... 1708 1709var $$AnimateJsProvider = ['$animateProvider', function($animateProvider) { 1710 this.$get = ['$injector', '$$AnimateRunner', '$$jqLite', 1711 function($injector, $$AnimateRunner, $$jqLite) { 1712 1713 var applyAnimationClasses = applyAnimationClassesFactory($$jqLite); 1714 // $animateJs(element, 'enter'); 1715 return function(element, event, classes, options) { 1716 var animationClosed = false; 1717 1718 // the `classes` argument is optional and if it is not used 1719 // then the classes will be resolved from the element's className 1720 // property as well as options.addClass/options.removeClass. 1721 if (arguments.length === 3 && isObject(classes)) { 1722 options = classes; 1723 classes = null; 1724 } 1725 1726 options = prepareAnimationOptions(options); 1727 if (!classes) { 1728 classes = element.attr('class') || ''; 1729 if (options.addClass) { 1730 classes += ' ' + options.addClass; 1731 } 1732 if (options.removeClass) { 1733 classes += ' ' + options.removeClass; 1734 } 1735 } 1736 1737 var classesToAdd = options.addClass; 1738 var classesToRemove = options.removeClass; 1739 1740 // the lookupAnimations function returns a series of animation objects that are 1741 // matched up with one or more of the CSS classes. These animation objects are 1742 // defined via the module.animation factory function. If nothing is detected then 1743 // we don't return anything which then makes $animation query the next driver. 1744 var animations = lookupAnimations(classes); 1745 var before, after; 1746 if (animations.length) { 1747 var afterFn, beforeFn; 1748 if (event == 'leave') { 1749 beforeFn = 'leave'; 1750 afterFn = 'afterLeave'; // TODO(matsko): get rid of this 1751 } else { 1752 beforeFn = 'before' + event.charAt(0).toUpperCase() + event.substr(1); 1753 afterFn = event; 1754 } 1755 1756 if (event !== 'enter' && event !== 'move') { 1757 before = packageAnimations(element, event, options, animations, beforeFn); 1758 } 1759 after = packageAnimations(element, event, options, animations, afterFn); 1760 } 1761 1762 // no matching animations 1763 if (!before && !after) return; 1764 1765 function applyOptions() { 1766 options.domOperation(); 1767 applyAnimationClasses(element, options); 1768 } 1769 1770 function close() { 1771 animationClosed = true; 1772 applyOptions(); 1773 applyAnimationStyles(element, options); 1774 } 1775 1776 var runner; 1777 1778 return { 1779 $$willAnimate: true, 1780 end: function() { 1781 if (runner) { 1782 runner.end(); 1783 } else { 1784 close(); 1785 runner = new $$AnimateRunner(); 1786 runner.complete(true); 1787 } 1788 return runner; 1789 }, 1790 start: function() { 1791 if (runner) { 1792 return runner; 1793 } 1794 1795 runner = new $$AnimateRunner(); 1796 var closeActiveAnimations; 1797 var chain = []; 1798 1799 if (before) { 1800 chain.push(function(fn) { 1801 closeActiveAnimations = before(fn); 1802 }); 1803 } 1804 1805 if (chain.length) { 1806 chain.push(function(fn) { 1807 applyOptions(); 1808 fn(true); 1809 }); 1810 } else { 1811 applyOptions(); 1812 } 1813 1814 if (after) { 1815 chain.push(function(fn) { 1816 closeActiveAnimations = after(fn); 1817 }); 1818 } 1819 1820 runner.setHost({ 1821 end: function() { 1822 endAnimations(); 1823 }, 1824 cancel: function() { 1825 endAnimations(true); 1826 } 1827 }); 1828 1829 $$AnimateRunner.chain(chain, onComplete); 1830 return runner; 1831 1832 function onComplete(success) { 1833 close(success); 1834 runner.complete(success); 1835 } 1836 1837 function endAnimations(cancelled) { 1838 if (!animationClosed) { 1839 (closeActiveAnimations || noop)(cancelled); 1840 onComplete(cancelled); 1841 } 1842 } 1843 } 1844 }; 1845 1846 function executeAnimationFn(fn, element, event, options, onDone) { 1847 var args; 1848 switch (event) { 1849 case 'animate': 1850 args = [element, options.from, options.to, onDone]; 1851 break; 1852 1853 case 'setClass': 1854 args = [element, classesToAdd, classesToRemove, onDone]; 1855 break; 1856 1857 case 'addClass': 1858 args = [element, classesToAdd, onDone]; 1859 break; 1860 1861 case 'removeClass': 1862 args = [element, classesToRemove, onDone]; 1863 break; 1864 1865 default: 1866 args = [element, onDone]; 1867 break; 1868 } 1869 1870 args.push(options); 1871 1872 var value = fn.apply(fn, args); 1873 if (value) { 1874 if (isFunction(value.start)) { 1875 value = value.start(); 1876 } 1877 1878 if (value instanceof $$AnimateRunner) { 1879 value.done(onDone); 1880 } else if (isFunction(value)) { 1881 // optional onEnd / onCancel callback 1882 return value; 1883 } 1884 } 1885 1886 return noop; 1887 } 1888 1889 function groupEventedAnimations(element, event, options, animations, fnName) { 1890 var operations = []; 1891 forEach(animations, function(ani) { 1892 var animation = ani[fnName]; 1893 if (!animation) return; 1894 1895 // note that all of these animations will run in parallel 1896 operations.push(function() { 1897 var runner; 1898 var endProgressCb; 1899 1900 var resolved = false; 1901 var onAnimationComplete = function(rejected) { 1902 if (!resolved) { 1903 resolved = true; 1904 (endProgressCb || noop)(rejected); 1905 runner.complete(!rejected); 1906 } 1907 }; 1908 1909 runner = new $$AnimateRunner({ 1910 end: function() { 1911 onAnimationComplete(); 1912 }, 1913 cancel: function() { 1914 onAnimationComplete(true); 1915 } 1916 }); 1917 1918 endProgressCb = executeAnimationFn(animation, element, event, options, function(result) { 1919 var cancelled = result === false; 1920 onAnimationComplete(cancelled); 1921 }); 1922 1923 return runner; 1924 }); 1925 }); 1926 1927 return operations; 1928 } 1929 1930 function packageAnimations(element, event, options, animations, fnName) { 1931 var operations = groupEventedAnimations(element, event, options, animations, fnName); 1932 if (operations.length === 0) { 1933 var a,b; 1934 if (fnName === 'beforeSetClass') { 1935 a = groupEventedAnimations(element, 'removeClass', options, animations, 'beforeRemoveClass'); 1936 b = groupEventedAnimations(element, 'addClass', options, animations, 'beforeAddClass'); 1937 } else if (fnName === 'setClass') { 1938 a = groupEventedAnimations(element, 'removeClass', options, animations, 'removeClass'); 1939 b = groupEventedAnimations(element, 'addClass', options, animations, 'addClass'); 1940 } 1941 1942 if (a) { 1943 operations = operations.concat(a); 1944 } 1945 if (b) { 1946 operations = operations.concat(b); 1947 } 1948 } 1949 1950 if (operations.length === 0) return; 1951 1952 // TODO(matsko): add documentation 1953 return function startAnimation(callback) { 1954 var runners = []; 1955 if (operations.length) { 1956 forEach(operations, function(animateFn) { 1957 runners.push(animateFn()); 1958 }); 1959 } 1960 1961 runners.length ? $$AnimateRunner.all(runners, callback) : callback(); 1962 1963 return function endFn(reject) { 1964 forEach(runners, function(runner) { 1965 reject ? runner.cancel() : runner.end(); 1966 }); 1967 }; 1968 }; 1969 } 1970 }; 1971 1972 function lookupAnimations(classes) { 1973 classes = isArray(classes) ? classes : classes.split(' '); 1974 var matches = [], flagMap = {}; 1975 for (var i=0; i < classes.length; i++) { 1976 var klass = classes[i], 1977 animationFactory = $animateProvider.$$registeredAnimations[klass]; 1978 if (animationFactory && !flagMap[klass]) { 1979 matches.push($injector.get(animationFactory)); 1980 flagMap[klass] = true; 1981 } 1982 } 1983 return matches; 1984 } 1985 }]; 1986}]; 1987 1988var $$AnimateJsDriverProvider = ['$$animationProvider', function($$animationProvider) { 1989 $$animationProvider.drivers.push('$$animateJsDriver'); 1990 this.$get = ['$$animateJs', '$$AnimateRunner', function($$animateJs, $$AnimateRunner) { 1991 return function initDriverFn(animationDetails) { 1992 if (animationDetails.from && animationDetails.to) { 1993 var fromAnimation = prepareAnimation(animationDetails.from); 1994 var toAnimation = prepareAnimation(animationDetails.to); 1995 if (!fromAnimation && !toAnimation) return; 1996 1997 return { 1998 start: function() { 1999 var animationRunners = []; 2000 2001 if (fromAnimation) { 2002 animationRunners.push(fromAnimation.start()); 2003 } 2004 2005 if (toAnimation) { 2006 animationRunners.push(toAnimation.start()); 2007 } 2008 2009 $$AnimateRunner.all(animationRunners, done); 2010 2011 var runner = new $$AnimateRunner({ 2012 end: endFnFactory(), 2013 cancel: endFnFactory() 2014 }); 2015 2016 return runner; 2017 2018 function endFnFactory() { 2019 return function() { 2020 forEach(animationRunners, function(runner) { 2021 // at this point we cannot cancel animations for groups just yet. 1.5+ 2022 runner.end(); 2023 }); 2024 }; 2025 } 2026 2027 function done(status) { 2028 runner.complete(status); 2029 } 2030 } 2031 }; 2032 } else { 2033 return prepareAnimation(animationDetails); 2034 } 2035 }; 2036 2037 function prepareAnimation(animationDetails) { 2038 // TODO(matsko): make sure to check for grouped animations and delegate down to normal animations 2039 var element = animationDetails.element; 2040 var event = animationDetails.event; 2041 var options = animationDetails.options; 2042 var classes = animationDetails.classes; 2043 return $$animateJs(element, event, classes, options); 2044 } 2045 }]; 2046}]; 2047 2048var NG_ANIMATE_ATTR_NAME = 'data-ng-animate'; 2049var NG_ANIMATE_PIN_DATA = '$ngAnimatePin'; 2050var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) { 2051 var PRE_DIGEST_STATE = 1; 2052 var RUNNING_STATE = 2; 2053 var ONE_SPACE = ' '; 2054 2055 var rules = this.rules = { 2056 skip: [], 2057 cancel: [], 2058 join: [] 2059 }; 2060 2061 function makeTruthyCssClassMap(classString) { 2062 if (!classString) { 2063 return null; 2064 } 2065 2066 var keys = classString.split(ONE_SPACE); 2067 var map = Object.create(null); 2068 2069 forEach(keys, function(key) { 2070 map[key] = true; 2071 }); 2072 return map; 2073 } 2074 2075 function hasMatchingClasses(newClassString, currentClassString) { 2076 if (newClassString && currentClassString) { 2077 var currentClassMap = makeTruthyCssClassMap(currentClassString); 2078 return newClassString.split(ONE_SPACE).some(function(className) { 2079 return currentClassMap[className]; 2080 }); 2081 } 2082 } 2083 2084 function isAllowed(ruleType, element, currentAnimation, previousAnimation) { 2085 return rules[ruleType].some(function(fn) { 2086 return fn(element, currentAnimation, previousAnimation); 2087 }); 2088 } 2089 2090 function hasAnimationClasses(options, and) { 2091 options = options || {}; 2092 var a = (options.addClass || '').length > 0; 2093 var b = (options.removeClass || '').length > 0; 2094 return and ? a && b : a || b; 2095 } 2096 2097 rules.join.push(function(element, newAnimation, currentAnimation) { 2098 // if the new animation is class-based then we can just tack that on 2099 return !newAnimation.structural && hasAnimationClasses(newAnimation.options); 2100 }); 2101 2102 rules.skip.push(function(element, newAnimation, currentAnimation) { 2103 // there is no need to animate anything if no classes are being added and 2104 // there is no structural animation that will be triggered 2105 return !newAnimation.structural && !hasAnimationClasses(newAnimation.options); 2106 }); 2107 2108 rules.skip.push(function(element, newAnimation, currentAnimation) { 2109 // why should we trigger a new structural animation if the element will 2110 // be removed from the DOM anyway? 2111 return currentAnimation.event == 'leave' && newAnimation.structural; 2112 }); 2113 2114 rules.skip.push(function(element, newAnimation, currentAnimation) { 2115 // if there is an ongoing current animation then don't even bother running the class-based animation 2116 return currentAnimation.structural && currentAnimation.state === RUNNING_STATE && !newAnimation.structural; 2117 }); 2118 2119 rules.cancel.push(function(element, newAnimation, currentAnimation) { 2120 // there can never be two structural animations running at the same time 2121 return currentAnimation.structural && newAnimation.structural; 2122 }); 2123 2124 rules.cancel.push(function(element, newAnimation, currentAnimation) { 2125 // if the previous animation is already running, but the new animation will 2126 // be triggered, but the new animation is structural 2127 return currentAnimation.state === RUNNING_STATE && newAnimation.structural; 2128 }); 2129 2130 rules.cancel.push(function(element, newAnimation, currentAnimation) { 2131 2132 2133 var nA = newAnimation.options.addClass; 2134 var nR = newAnimation.options.removeClass; 2135 var cA = currentAnimation.options.addClass; 2136 var cR = currentAnimation.options.removeClass; 2137 2138 // early detection to save the global CPU shortage :) 2139 if ((isUndefined(nA) && isUndefined(nR)) || (isUndefined(cA) && isUndefined(cR))) { 2140 return false; 2141 } 2142 2143 return (hasMatchingClasses(nA, cR)) || (hasMatchingClasses(nR, cA)); 2144 }); 2145 2146 this.$get = ['$$rAF', '$rootScope', '$rootElement', '$document', '$$HashMap', 2147 '$$animation', '$$AnimateRunner', '$templateRequest', '$$jqLite', '$$forceReflow', 2148 function($$rAF, $rootScope, $rootElement, $document, $$HashMap, 2149 $$animation, $$AnimateRunner, $templateRequest, $$jqLite, $$forceReflow) { 2150 2151 var activeAnimationsLookup = new $$HashMap(); 2152 var disabledElementsLookup = new $$HashMap(); 2153 var animationsEnabled = null; 2154 2155 function postDigestTaskFactory() { 2156 var postDigestCalled = false; 2157 return function(fn) { 2158 // we only issue a call to postDigest before 2159 // it has first passed. This prevents any callbacks 2160 // from not firing once the animation has completed 2161 // since it will be out of the digest cycle. 2162 if (postDigestCalled) { 2163 fn(); 2164 } else { 2165 $rootScope.$$postDigest(function() { 2166 postDigestCalled = true; 2167 fn(); 2168 }); 2169 } 2170 }; 2171 } 2172 2173 // Wait until all directive and route-related templates are downloaded and 2174 // compiled. The $templateRequest.totalPendingRequests variable keeps track of 2175 // all of the remote templates being currently downloaded. If there are no 2176 // templates currently downloading then the watcher will still fire anyway. 2177 var deregisterWatch = $rootScope.$watch( 2178 function() { return $templateRequest.totalPendingRequests === 0; }, 2179 function(isEmpty) { 2180 if (!isEmpty) return; 2181 deregisterWatch(); 2182 2183 // Now that all templates have been downloaded, $animate will wait until 2184 // the post digest queue is empty before enabling animations. By having two 2185 // calls to $postDigest calls we can ensure that the flag is enabled at the 2186 // very end of the post digest queue. Since all of the animations in $animate 2187 // use $postDigest, it's important that the code below executes at the end. 2188 // This basically means that the page is fully downloaded and compiled before 2189 // any animations are triggered. 2190 $rootScope.$$postDigest(function() { 2191 $rootScope.$$postDigest(function() { 2192 // we check for null directly in the event that the application already called 2193 // .enabled() with whatever arguments that it provided it with 2194 if (animationsEnabled === null) { 2195 animationsEnabled = true; 2196 } 2197 }); 2198 }); 2199 } 2200 ); 2201 2202 var callbackRegistry = {}; 2203 2204 // remember that the classNameFilter is set during the provider/config 2205 // stage therefore we can optimize here and setup a helper function 2206 var classNameFilter = $animateProvider.classNameFilter(); 2207 var isAnimatableClassName = !classNameFilter 2208 ? function() { return true; } 2209 : function(className) { 2210 return classNameFilter.test(className); 2211 }; 2212 2213 var applyAnimationClasses = applyAnimationClassesFactory($$jqLite); 2214 2215 function normalizeAnimationOptions(element, options) { 2216 return mergeAnimationOptions(element, options, {}); 2217 } 2218 2219 // IE9-11 has no method "contains" in SVG element and in Node.prototype. Bug #10259. 2220 var contains = Node.prototype.contains || function(arg) { 2221 // jshint bitwise: false 2222 return this === arg || !!(this.compareDocumentPosition(arg) & 16); 2223 // jshint bitwise: true 2224 }; 2225 2226 function findCallbacks(parent, element, event) { 2227 var targetNode = getDomNode(element); 2228 var targetParentNode = getDomNode(parent); 2229 2230 var matches = []; 2231 var entries = callbackRegistry[event]; 2232 if (entries) { 2233 forEach(entries, function(entry) { 2234 if (contains.call(entry.node, targetNode)) { 2235 matches.push(entry.callback); 2236 } else if (event === 'leave' && contains.call(entry.node, targetParentNode)) { 2237 matches.push(entry.callback); 2238 } 2239 }); 2240 } 2241 2242 return matches; 2243 } 2244 2245 return { 2246 on: function(event, container, callback) { 2247 var node = extractElementNode(container); 2248 callbackRegistry[event] = callbackRegistry[event] || []; 2249 callbackRegistry[event].push({ 2250 node: node, 2251 callback: callback 2252 }); 2253 }, 2254 2255 off: function(event, container, callback) { 2256 var entries = callbackRegistry[event]; 2257 if (!entries) return; 2258 2259 callbackRegistry[event] = arguments.length === 1 2260 ? null 2261 : filterFromRegistry(entries, container, callback); 2262 2263 function filterFromRegistry(list, matchContainer, matchCallback) { 2264 var containerNode = extractElementNode(matchContainer); 2265 return list.filter(function(entry) { 2266 var isMatch = entry.node === containerNode && 2267 (!matchCallback || entry.callback === matchCallback); 2268 return !isMatch; 2269 }); 2270 } 2271 }, 2272 2273 pin: function(element, parentElement) { 2274 assertArg(isElement(element), 'element', 'not an element'); 2275 assertArg(isElement(parentElement), 'parentElement', 'not an element'); 2276 element.data(NG_ANIMATE_PIN_DATA, parentElement); 2277 }, 2278 2279 push: function(element, event, options, domOperation) { 2280 options = options || {}; 2281 options.domOperation = domOperation; 2282 return queueAnimation(element, event, options); 2283 }, 2284 2285 // this method has four signatures: 2286 // () - global getter 2287 // (bool) - global setter 2288 // (element) - element getter 2289 // (element, bool) - element setter<F37> 2290 enabled: function(element, bool) { 2291 var argCount = arguments.length; 2292 2293 if (argCount === 0) { 2294 // () - Global getter 2295 bool = !!animationsEnabled; 2296 } else { 2297 var hasElement = isElement(element); 2298 2299 if (!hasElement) { 2300 // (bool) - Global setter 2301 bool = animationsEnabled = !!element; 2302 } else { 2303 var node = getDomNode(element); 2304 var recordExists = disabledElementsLookup.get(node); 2305 2306 if (argCount === 1) { 2307 // (element) - Element getter 2308 bool = !recordExists; 2309 } else { 2310 // (element, bool) - Element setter 2311 disabledElementsLookup.put(node, !bool); 2312 } 2313 } 2314 } 2315 2316 return bool; 2317 } 2318 }; 2319 2320 function queueAnimation(element, event, initialOptions) { 2321 // we always make a copy of the options since 2322 // there should never be any side effects on 2323 // the input data when running `$animateCss`. 2324 var options = copy(initialOptions); 2325 2326 var node, parent; 2327 element = stripCommentsFromElement(element); 2328 if (element) { 2329 node = getDomNode(element); 2330 parent = element.parent(); 2331 } 2332 2333 options = prepareAnimationOptions(options); 2334 2335 // we create a fake runner with a working promise. 2336 // These methods will become available after the digest has passed 2337 var runner = new $$AnimateRunner(); 2338 2339 // this is used to trigger callbacks in postDigest mode 2340 var runInNextPostDigestOrNow = postDigestTaskFactory(); 2341 2342 if (isArray(options.addClass)) { 2343 options.addClass = options.addClass.join(' '); 2344 } 2345 2346 if (options.addClass && !isString(options.addClass)) { 2347 options.addClass = null; 2348 } 2349 2350 if (isArray(options.removeClass)) { 2351 options.removeClass = options.removeClass.join(' '); 2352 } 2353 2354 if (options.removeClass && !isString(options.removeClass)) { 2355 options.removeClass = null; 2356 } 2357 2358 if (options.from && !isObject(options.from)) { 2359 options.from = null; 2360 } 2361 2362 if (options.to && !isObject(options.to)) { 2363 options.to = null; 2364 } 2365 2366 // there are situations where a directive issues an animation for
2367 // a jqLite wrapper that contains only comment nodes... If this 2368 // happens then there is no way we can perform an animation 2369 if (!node) { 2370 close(); 2371 return runner; 2372 } 2373 2374 var className = [node.className, options.addClass, options.removeClass].join(' '); 2375 if (!isAnimatableClassName(className)) { 2376 close(); 2377 return runner; 2378 } 2379 2380 var isStructural = ['enter', 'move', 'leave'].indexOf(event) >= 0; 2381 2382 // this is a hard disable of all animations for the application or on 2383 // the element itself, therefore there is no need to continue further 2384 // past this point if not enabled 2385 var skipAnimations = !animationsEnabled || disabledElementsLookup.get(node); 2386 var existingAnimation = (!skipAnimations && activeAnimationsLookup.get(node)) || {}; 2387 var hasExistingAnimation = !!existingAnimation.state; 2388 2389 // there is no point in traversing the same collection of parent ancestors if a followup 2390 // animation will be run on the same element that already did all that checking work 2391 if (!skipAnimations && (!hasExistingAnimation || existingAnimation.state != PRE_DIGEST_STATE)) { 2392 skipAnimations = !areAnimationsAllowed(element, parent, event); 2393 } 2394 2395 if (skipAnimations) { 2396 close(); 2397 return runner; 2398 } 2399 2400 if (isStructural) { 2401 closeChildAnimations(element); 2402 } 2403 2404 var newAnimation = { 2405 structural: isStructural, 2406 element: element, 2407 event: event, 2408 close: close, 2409 options: options, 2410 runner: runner 2411 }; 2412 2413 if (hasExistingAnimation) { 2414 var skipAnimationFlag = isAllowed('skip', element, newAnimation, existingAnimation); 2415 if (skipAnimationFlag) { 2416 if (existingAnimation.state === RUNNING_STATE) { 2417 close(); 2418 return runner; 2419 } else { 2420 mergeAnimationOptions(element, existingAnimation.options, options); 2421 return existingAnimation.runner; 2422 } 2423 } 2424 2425 var cancelAnimationFlag = isAllowed('cancel', element, newAnimation, existingAnimation); 2426 if (cancelAnimationFlag) { 2427 if (existingAnimation.state === RUNNING_STATE) { 2428 // this will end the animation right away and it is safe 2429 // to do so since the animation is already running and the 2430 // runner callback code will run in async 2431 existingAnimation.runner.end(); 2432 } else if (existingAnimation.structural) { 2433 // this means that the animation is queued into a digest, but 2434 // hasn't started yet. Therefore it is safe to run the close 2435 // method which will call the runner methods in async. 2436 existingAnimation.close(); 2437 } else { 2438 // this will merge the new animation options into existing animation options 2439 mergeAnimationOptions(element, existingAnimation.options, newAnimation.options); 2440 return existingAnimation.runner; 2441 } 2442 } else { 2443 // a joined animation means that this animation will take over the existing one 2444 // so an example would involve a leave animation taking over an enter. Then when 2445 // the postDigest kicks in the enter will be ignored. 2446 var joinAnimationFlag = isAllowed('join', element, newAnimation, existingAnimation); 2447 if (joinAnimationFlag) { 2448 if (existingAnimation.state === RUNNING_STATE) { 2449 normalizeAnimationOptions(element, options); 2450 } else { 2451 applyGeneratedPreparationClasses(element, isStructural ? event : null, options); 2452 2453 event = newAnimation.event = existingAnimation.event; 2454 options = mergeAnimationOptions(element, existingAnimation.options, newAnimation.options); 2455 2456 //we return the same runner since only the option values of this animation will 2457 //be fed into the `existingAnimation`. 2458 return existingAnimation.runner; 2459 } 2460 } 2461 } 2462 } else { 2463 // normalization in this case means that it removes redundant CSS classes that 2464 // already exist (addClass) or do not exist (removeClass) on the element 2465 normalizeAnimationOptions(element, options); 2466 } 2467 2468 // when the options are merged and cleaned up we may end up not having to do 2469 // an animation at all, therefore we should check this before issuing a post 2470 // digest callback. Structural animations will always run no matter what. 2471 var isValidAnimation = newAnimation.structural; 2472 if (!isValidAnimation) { 2473 // animate (from/to) can be quickly checked first, otherwise we check if any classes are present 2474 isValidAnimation = (newAnimation.event === 'animate' && Object.keys(newAnimation.options.to || {}).length > 0) 2475 || hasAnimationClasses(newAnimation.options); 2476 } 2477 2478 if (!isValidAnimation) { 2479 close(); 2480 clearElementAnimationState(element); 2481 return runner; 2482 } 2483 2484 // the counter keeps track of cancelled animations 2485 var counter = (existingAnimation.counter || 0) + 1; 2486 newAnimation.counter = counter; 2487 2488 markElementAnimationState(element, PRE_DIGEST_STATE, newAnimation); 2489 2490 $rootScope.$$postDigest(function() {
2491 var animationDetails = activeAnimationsLookup.get(node); 2492 var animationCancelled = !animationDetails; 2493 animationDetails = animationDetails || {}; 2494 2495 // if addClass/removeClass is called before something like enter then the 2496 // registered parent element may not be present. The code below will ensure 2497 // that a final value for parent element is obtained 2498 var parentElement = element.parent() || []; 2499 2500 // animate/structural/class-based animations all have requirements. Otherwise there 2501 // is no point in performing an animation. The parent node must also be set. 2502 var isValidAnimation = parentElement.length > 0 2503 && (animationDetails.event === 'animate' 2504 || animationDetails.structural 2505 || hasAnimationClasses(animationDetails.options)); 2506 2507 // this means that the previous animation was cancelled 2508 // even if the follow-up animation is the same event 2509 if (animationCancelled || animationDetails.counter !== counter || !isValidAnimation) { 2510 // if another animation did not take over then we need 2511 // to make sure that the domOperation and options are 2512 // handled accordingly 2513 if (animationCancelled) { 2514 applyAnimationClasses(element, options); 2515 applyAnimationStyles(element, options); 2516 } 2517 2518 // if the event changed from something like enter to leave then we do 2519 // it, otherwise if it's the same then the end result will be the same too 2520 if (animationCancelled || (isStructural && animationDetails.event !== event)) { 2521 options.domOperation(); 2522 runner.end(); 2523 } 2524 2525 // in the event that the element animation was not cancelled or a follow-up animation 2526 // isn't allowed to animate from here then we need to clear the state of the element 2527 // so that any future animations won't read the expired animation data. 2528 if (!isValidAnimation) { 2529 clearElementAnimationState(element); 2530 } 2531 2532 return; 2533 } 2534 2535 // this combined multiple class to addClass / removeClass into a setClass event 2536 // so long as a structural event did not take over the animation 2537 event = !animationDetails.structural && hasAnimationClasses(animationDetails.options, true) 2538 ? 'setClass' 2539 : animationDetails.event; 2540 2541 markElementAnimationState(element, RUNNING_STATE); 2542 var realRunner = $$animation(element, event, animationDetails.options); 2543 2544 realRunner.done(function(status) { 2545 close(!status); 2546 var animationDetails = activeAnimationsLookup.get(node); 2547 if (animationDetails && animationDetails.counter === counter) { 2548 clearElementAnimationState(getDomNode(element)); 2549 } 2550 notifyProgress(runner, event, 'close', {}); 2551 }); 2552 2553 // this will update the runner's flow-control events based on 2554 // the `realRunner` object. 2555 runner.setHost(realRunner); 2556 notifyProgress(runner, event, 'start', {}); 2557 }); 2558 2559 return runner; 2560 2561 function notifyProgress(runner, event, phase, data) { 2562 runInNextPostDigestOrNow(function() { 2563 var callbacks = findCallbacks(parent, element, event); 2564 if (callbacks.length) { 2565 // do not optimize this call here to RAF because 2566 // we don't know how heavy the callback code here will 2567 // be and if this code is buffered then this can 2568 // lead to a performance regression. 2569 $$rAF(function() { 2570 forEach(callbacks, function(callback) { 2571 callback(element, phase, data); 2572 }); 2573 }); 2574 } 2575 }); 2576 runner.progress(event, phase, data); 2577 } 2578 2579 function close(reject) { // jshint ignore:line 2580 clearGeneratedClasses(element, options); 2581 applyAnimationClasses(element, options); 2582 applyAnimationStyles(element, options); 2583 options.domOperation(); 2584 runner.complete(!reject); 2585 } 2586 } 2587 2588 function closeChildAnimations(element) { 2589 var node = getDomNode(element); 2590 var children = node.querySelectorAll('[' + NG_ANIMATE_ATTR_NAME + ']');
2591 forEach(children, function(child) { 2592 var state = parseInt(child.getAttribute(NG_ANIMATE_ATTR_NAME)); 2593 var animationDetails = activeAnimationsLookup.get(child); 2594 if (animationDetails) { 2595 switch (state) { 2596 case RUNNING_STATE: 2597 animationDetails.runner.end(); 2598 /* falls through */ 2599 case PRE_DIGEST_STATE: 2600 activeAnimationsLookup.remove(child); 2601 break; 2602 } 2603 } 2604 }); 2605 } 2606 2607 function clearElementAnimationState(element) { 2608 var node = getDomNode(element); 2609 node.removeAttribute(NG_ANIMATE_ATTR_NAME); 2610 activeAnimationsLookup.remove(node); 2611 } 2612 2613 function isMatchingElement(nodeOrElmA, nodeOrElmB) { 2614 return getDomNode(nodeOrElmA) === getDomNode(nodeOrElmB); 2615 } 2616 2617 function areAnimationsAllowed(element, parentElement, event) { 2618 var bodyElement = jqLite($document[0].body); 2619 var bodyElementDetected = isMatchingElement(element, bodyElement) || element[0].nodeName === 'HTML'; 2620 var rootElementDetected = isMatchingElement(element, $rootElement); 2621 var parentAnimationDetected = false; 2622 var animateChildren; 2623 var elementDisabled = disabledElementsLookup.get(getDomNode(element)); 2624 2625 var parentHost = element.data(NG_ANIMATE_PIN_DATA); 2626 if (parentHost) { 2627 parentElement = parentHost; 2628 } 2629 2630 while (parentElement && parentElement.length) { 2631 if (!rootElementDetected) { 2632 // angular doesn't want to attempt to animate elements outside of the application 2633 // therefore we need to ensure that the rootElement is an ancestor of the current element 2634 rootElementDetected = isMatchingElement(parentElement, $rootElement); 2635 } 2636 2637 var parentNode = parentElement[0]; 2638 if (parentNode.nodeType !== ELEMENT_NODE) { 2639 // no point in inspecting the #document element 2640 break; 2641 } 2642 2643 var details = activeAnimationsLookup.get(parentNode) || {}; 2644 // either an enter, leave or move animation will commence 2645 // therefore we can't allow any animations to take place 2646 // but if a parent animation is class-based then that's ok 2647 if (!parentAnimationDetected) { 2648 var parentElementDisabled = disabledElementsLookup.get(parentNode); 2649 2650 // disable animations if the user hasn't explicitly enabled animations on the 2651 // current element 2652 if (parentElementDisabled === true && elementDisabled !== false) { 2653 elementDisabled = true; 2654 } else if (parentElementDisabled === false) { 2655 elementDisabled = false; 2656 } 2657 parentAnimationDetected = details.structural; 2658 } 2659 2660 if (isUndefined(animateChildren) || animateChildren === true) { 2661 var value = parentElement.data(NG_ANIMATE_CHILDREN_DATA); 2662 if (isDefined(value)) { 2663 animateChildren = value; 2664 } 2665 } 2666 2667 // there is no need to continue traversing at this point 2668 if (parentAnimationDetected && animateChildren === false) break; 2669 2670 if (!rootElementDetected) { 2671 // angular doesn't want to attempt to animate elements outside of the application 2672 // therefore we need to ensure that the rootElement is an ancestor of the current element 2673 rootElementDetected = isMatchingElement(parentElement, $rootElement); 2674 if (!rootElementDetected) { 2675 parentHost = parentElement.data(NG_ANIMATE_PIN_DATA); 2676 if (parentHost) { 2677 parentElement = parentHost; 2678 rootElementDetected = true; 2679 } 2680 } 2681 } 2682 2683 if (!bodyElementDetected) { 2684 // we also need to ensure that the element is or will be apart of the body element 2685 // otherwise it is pointless to even issue an animation to be rendered 2686 bodyElementDetected = isMatchingElement(parentElement, bodyElement); 2687 } 2688 2689 parentElement = parentElement.parent(); 2690 } 2691 2692 var allowAnimation = (!parentAnimationDetected || animateChildren) && elementDisabled !== true; 2693 return allowAnimation && rootElementDetected && bodyElementDetected; 2694 } 2695 2696 function markElementAnimationState(element, state, details) { 2697 details = details || {}; 2698 details.state = state; 2699 2700 var node = getDomNode(element); 2701 node.setAttribute(NG_ANIMATE_ATTR_NAME, state); 2702 2703 var oldValue = activeAnimationsLookup.get(node); 2704 var newValue = oldValue 2705 ? extend(oldValue, details) 2706 : details; 2707 activeAnimationsLookup.put(node, newValue); 2708 } 2709 }]; 2710}]; 2711 2712var $$AnimationProvider = ['$animateProvider', function($animateProvider) { 2713 var NG_ANIMATE_REF_ATTR = 'ng-animate-ref'; 2714 2715 var drivers = this.drivers = []; 2716 2717 var RUNNER_STORAGE_KEY = '$$animationRunner'; 2718 2719 function setRunner(element, runner) { 2720 element.data(RUNNER_STORAGE_KEY, runner); 2721 } 2722 2723 function removeRunner(element) { 2724 element.removeData(RUNNER_STORAGE_KEY); 2725 } 2726 2727 function getRunner(element) { 2728 return element.data(RUNNER_STORAGE_KEY); 2729 } 2730 2731 this.$get = ['$$jqLite', '$rootScope', '$injector', '$$AnimateRunner', '$$HashMap', '$$rAFScheduler', 2732 function($$jqLite, $rootScope, $injector, $$AnimateRunner, $$HashMap, $$rAFScheduler) { 2733 2734 var animationQueue = []; 2735 var applyAnimationClasses = applyAnimationClassesFactory($$jqLite); 2736 2737 function sortAnimations(animations) { 2738 var tree = { children: [] }; 2739 var i, lookup = new $$HashMap(); 2740 2741 // this is done first beforehand so that the hashmap 2742 // is filled with a list of the elements that will be animated 2743 for (i = 0; i < animations.length; i++) { 2744 var animation = animations[i]; 2745 lookup.put(animation.domNode, animations[i] = { 2746 domNode: animation.domNode, 2747 fn: animation.fn, 2748 children: [] 2749 }); 2750 } 2751 2752 for (i = 0; i < animations.length; i++) { 2753 processNode(animations[i]); 2754 } 2755 2756 return flatten(tree); 2757 2758 function processNode(entry) { 2759 if (entry.processed) return entry; 2760 entry.processed = true; 2761 2762 var elementNode = entry.domNode; 2763 var parentNode = elementNode.parentNode; 2764 lookup.put(elementNode, entry); 2765 2766 var parentEntry; 2767 while (parentNode) { 2768 parentEntry = lookup.get(parentNode); 2769 if (parentEntry) { 2770 if (!parentEntry.processed) { 2771 parentEntry = processNode(parentEntry); 2772 } 2773 break; 2774 } 2775 parentNode = parentNode.parentNode; 2776 } 2777 2778 (parentEntry || tree).children.push(entry); 2779 return entry; 2780 } 2781 2782 function flatten(tree) { 2783 var result = []; 2784 var queue = []; 2785 var i; 2786 2787 for (i = 0; i < tree.children.length; i++) { 2788 queue.push(tree.children[i]); 2789 } 2790 2791 var remainingLevelEntries = queue.length; 2792 var nextLevelEntries = 0; 2793 var row = []; 2794 2795 for (i = 0; i < queue.length; i++) { 2796 var entry = queue[i]; 2797 if (remainingLevelEntries <= 0) { 2798 remainingLevelEntries = nextLevelEntries; 2799 nextLevelEntries = 0; 2800 result.push(row); 2801 row = []; 2802 } 2803 row.push(entry.fn);
vendor: 14,644 bytes, lines 2804-3238
2804 entry.children.forEach(function(childEntry) { 2805 nextLevelEntries++; 2806 queue.push(childEntry); 2807 }); 2808 remainingLevelEntries--; 2809 } 2810 2811 if (row.length) { 2812 result.push(row); 2813 } 2814 2815 return result; 2816 } 2817 } 2818 2819 // TODO(matsko): document the signature in a better way 2820 return function(element, event, options) { 2821 options = prepareAnimationOptions(options); 2822 var isStructural = ['enter', 'move', 'leave'].indexOf(event) >= 0; 2823 2824 // there is no animation at the current moment, however 2825 // these runner methods will get later updated with the 2826 // methods leading into the driver's end/cancel methods 2827 // for now they just stop the animation from starting 2828 var runner = new $$AnimateRunner({ 2829 end: function() { close(); }, 2830 cancel: function() { close(true); } 2831 }); 2832 2833 if (!drivers.length) { 2834 close(); 2835 return runner; 2836 } 2837 2838 setRunner(element, runner); 2839 2840 var classes = mergeClasses(element.attr('class'), mergeClasses(options.addClass, options.removeClass)); 2841 var tempClasses = options.tempClasses; 2842 if (tempClasses) { 2843 classes += ' ' + tempClasses; 2844 options.tempClasses = null; 2845 } 2846 2847 var prepareClassName; 2848 if (isStructural) { 2849 prepareClassName = 'ng-' + event + PREPARE_CLASS_SUFFIX; 2850 $$jqLite.addClass(element, prepareClassName); 2851 } 2852 2853 animationQueue.push({ 2854 // this data is used by the postDigest code and passed into 2855 // the driver step function 2856 element: element, 2857 classes: classes, 2858 event: event, 2859 structural: isStructural, 2860 options: options, 2861 beforeStart: beforeStart, 2862 close: close 2863 }); 2864 2865 element.on('$destroy', handleDestroyedElement); 2866 2867 // we only want there to be one function called within the post digest 2868 // block. This way we can group animations for all the animations that 2869 // were apart of the same postDigest flush call. 2870 if (animationQueue.length > 1) return runner; 2871 2872 $rootScope.$$postDigest(function() { 2873 var animations = []; 2874 forEach(animationQueue, function(entry) { 2875 // the element was destroyed early on which removed the runner 2876 // form its storage. This means we can't animate this element 2877 // at all and it already has been closed due to destruction. 2878 if (getRunner(entry.element)) { 2879 animations.push(entry); 2880 } else { 2881 entry.close(); 2882 } 2883 }); 2884 2885 // now any future animations will be in another postDigest 2886 animationQueue.length = 0; 2887 2888 var groupedAnimations = groupAnimations(animations); 2889 var toBeSortedAnimations = []; 2890 2891 forEach(groupedAnimations, function(animationEntry) { 2892 toBeSortedAnimations.push({ 2893 domNode: getDomNode(animationEntry.from ? animationEntry.from.element : animationEntry.element), 2894 fn: function triggerAnimationStart() { 2895 // it's important that we apply the `ng-animate` CSS class and the 2896 // temporary classes before we do any driver invoking since these 2897 // CSS classes may be required for proper CSS detection. 2898 animationEntry.beforeStart(); 2899 2900 var startAnimationFn, closeFn = animationEntry.close; 2901 2902 // in the event that the element was removed before the digest runs or 2903 // during the RAF sequencing then we should not trigger the animation. 2904 var targetElement = animationEntry.anchors 2905 ? (animationEntry.from.element || animationEntry.to.element) 2906 : animationEntry.element; 2907 2908 if (getRunner(targetElement)) { 2909 var operation = invokeFirstDriver(animationEntry); 2910 if (operation) { 2911 startAnimationFn = operation.start; 2912 } 2913 } 2914 2915 if (!startAnimationFn) { 2916 closeFn(); 2917 } else { 2918 var animationRunner = startAnimationFn(); 2919 animationRunner.done(function(status) { 2920 closeFn(!status); 2921 }); 2922 updateAnimationRunners(animationEntry, animationRunner); 2923 } 2924 } 2925 }); 2926 }); 2927 2928 // we need to sort each of the animations in order of parent to child 2929 // relationships. This ensures that the child classes are applied at the 2930 // right time. 2931 $$rAFScheduler(sortAnimations(toBeSortedAnimations)); 2932 }); 2933 2934 return runner; 2935 2936 // TODO(matsko): change to reference nodes 2937 function getAnchorNodes(node) { 2938 var SELECTOR = '[' + NG_ANIMATE_REF_ATTR + ']'; 2939 var items = node.hasAttribute(NG_ANIMATE_REF_ATTR) 2940 ? [node] 2941 : node.querySelectorAll(SELECTOR); 2942 var anchors = []; 2943 forEach(items, function(node) { 2944 var attr = node.getAttribute(NG_ANIMATE_REF_ATTR); 2945 if (attr && attr.length) { 2946 anchors.push(node); 2947 } 2948 }); 2949 return anchors; 2950 } 2951 2952 function groupAnimations(animations) { 2953 var preparedAnimations = []; 2954 var refLookup = {}; 2955 forEach(animations, function(animation, index) { 2956 var element = animation.element; 2957 var node = getDomNode(element); 2958 var event = animation.event; 2959 var enterOrMove = ['enter', 'move'].indexOf(event) >= 0; 2960 var anchorNodes = animation.structural ? getAnchorNodes(node) : []; 2961 2962 if (anchorNodes.length) { 2963 var direction = enterOrMove ? 'to' : 'from'; 2964 2965 forEach(anchorNodes, function(anchor) { 2966 var key = anchor.getAttribute(NG_ANIMATE_REF_ATTR); 2967 refLookup[key] = refLookup[key] || {}; 2968 refLookup[key][direction] = { 2969 animationID: index, 2970 element: jqLite(anchor) 2971 }; 2972 }); 2973 } else { 2974 preparedAnimations.push(animation); 2975 } 2976 }); 2977 2978 var usedIndicesLookup = {}; 2979 var anchorGroups = {}; 2980 forEach(refLookup, function(operations, key) { 2981 var from = operations.from; 2982 var to = operations.to; 2983 2984 if (!from || !to) { 2985 // only one of these is set therefore we can't have an 2986 // anchor animation since all three pieces are required 2987 var index = from ? from.animationID : to.animationID; 2988 var indexKey = index.toString(); 2989 if (!usedIndicesLookup[indexKey]) { 2990 usedIndicesLookup[indexKey] = true; 2991 preparedAnimations.push(animations[index]); 2992 } 2993 return; 2994 } 2995 2996 var fromAnimation = animations[from.animationID]; 2997 var toAnimation = animations[to.animationID]; 2998 var lookupKey = from.animationID.toString(); 2999 if (!anchorGroups[lookupKey]) { 3000 var group = anchorGroups[lookupKey] = { 3001 structural: true, 3002 beforeStart: function() { 3003 fromAnimation.beforeStart(); 3004 toAnimation.beforeStart(); 3005 }, 3006 close: function() { 3007 fromAnimation.close(); 3008 toAnimation.close(); 3009 }, 3010 classes: cssClassesIntersection(fromAnimation.classes, toAnimation.classes), 3011 from: fromAnimation, 3012 to: toAnimation, 3013 anchors: [] // TODO(matsko): change to reference nodes 3014 }; 3015 3016 // the anchor animations require that the from and to elements both have at least 3017 // one shared CSS class which effectively marries the two elements together to use 3018 // the same animation driver and to properly sequence the anchor animation. 3019 if (group.classes.length) { 3020 preparedAnimations.push(group); 3021 } else { 3022 preparedAnimations.push(fromAnimation); 3023 preparedAnimations.push(toAnimation); 3024 } 3025 } 3026 3027 anchorGroups[lookupKey].anchors.push({ 3028 'out': from.element, 'in': to.element 3029 }); 3030 }); 3031 3032 return preparedAnimations; 3033 } 3034 3035 function cssClassesIntersection(a,b) { 3036 a = a.split(' '); 3037 b = b.split(' '); 3038 var matches = []; 3039 3040 for (var i = 0; i < a.length; i++) { 3041 var aa = a[i]; 3042 if (aa.substring(0,3) === 'ng-') continue; 3043 3044 for (var j = 0; j < b.length; j++) { 3045 if (aa === b[j]) { 3046 matches.push(aa); 3047 break; 3048 } 3049 } 3050 } 3051 3052 return matches.join(' '); 3053 } 3054 3055 function invokeFirstDriver(animationDetails) { 3056 // we loop in reverse order since the more general drivers (like CSS and JS) 3057 // may attempt more elements, but custom drivers are more particular 3058 for (var i = drivers.length - 1; i >= 0; i--) { 3059 var driverName = drivers[i]; 3060 if (!$injector.has(driverName)) continue; // TODO(matsko): remove this check 3061 3062 var factory = $injector.get(driverName); 3063 var driver = factory(animationDetails); 3064 if (driver) { 3065 return driver; 3066 } 3067 } 3068 } 3069 3070 function beforeStart() { 3071 element.addClass(NG_ANIMATE_CLASSNAME); 3072 if (tempClasses) { 3073 $$jqLite.addClass(element, tempClasses); 3074 } 3075 if (prepareClassName) { 3076 $$jqLite.removeClass(element, prepareClassName); 3077 prepareClassName = null; 3078 } 3079 } 3080 3081 function updateAnimationRunners(animation, newRunner) { 3082 if (animation.from && animation.to) { 3083 update(animation.from.element); 3084 update(animation.to.element); 3085 } else { 3086 update(animation.element); 3087 } 3088 3089 function update(element) { 3090 getRunner(element).setHost(newRunner); 3091 } 3092 } 3093 3094 function handleDestroyedElement() { 3095 var runner = getRunner(element); 3096 if (runner && (event !== 'leave' || !options.$$domOperationFired)) { 3097 runner.end(); 3098 } 3099 } 3100 3101 function close(rejected) { // jshint ignore:line 3102 element.off('$destroy', handleDestroyedElement); 3103 removeRunner(element); 3104 3105 applyAnimationClasses(element, options); 3106 applyAnimationStyles(element, options); 3107 options.domOperation(); 3108 3109 if (tempClasses) { 3110 $$jqLite.removeClass(element, tempClasses); 3111 } 3112 3113 element.removeClass(NG_ANIMATE_CLASSNAME); 3114 runner.complete(!rejected); 3115 } 3116 }; 3117 }]; 3118}]; 3119 3120/** 3121 * @ngdoc directive 3122 * @name ngAnimateSwap 3123 * @restrict A 3124 * @scope 3125 * 3126 * @description 3127 * 3128 * ngAnimateSwap is a animation-oriented directive that allows for the container to 3129 * be removed and entered in whenever the associated expression changes. A 3130 * common usecase for this directive is a rotating banner component which 3131 * contains one image being present at a time. When the active image changes 3132 * then the old image will perform a `leave` animation and the new element 3133 * will be inserted via an `enter` animation. 3134 * 3135 * @example 3136 * <example name="ngAnimateSwap-directive" module="ngAnimateSwapExample" 3137 * deps="angular-animate.js" 3138 * animations="true" fixBase="true"> 3139 * <file name="index.html"> 3140 * <div class="container" ng-controller="AppCtrl"> 3141 * <div ng-animate-swap="number" class="cell swap-animation" ng-class="colorClass(number)"> 3142 * {{ number }} 3143 * </div> 3144 * </div> 3145 * </file> 3146 * <file name="script.js"> 3147 * angular.module('ngAnimateSwapExample', ['ngAnimate']) 3148 * .controller('AppCtrl', ['$scope', '$interval', function($scope, $interval) { 3149 * $scope.number = 0; 3150 * $interval(function() { 3151 * $scope.number++; 3152 * }, 1000); 3153 * 3154 * var colors = ['red','blue','green','yellow','orange']; 3155 * $scope.colorClass = function(number) { 3156 * return colors[number % colors.length]; 3157 * }; 3158 * }]); 3159 * </file> 3160 * <file name="animations.css"> 3161 * .container { 3162 * height:250px; 3163 * width:250px; 3164 * position:relative; 3165 * overflow:hidden; 3166 * border:2px solid black; 3167 * } 3168 * .container .cell { 3169 * font-size:150px; 3170 * text-align:center; 3171 * line-height:250px; 3172 * position:absolute; 3173 * top:0; 3174 * left:0; 3175 * right:0; 3176 * border-bottom:2px solid black; 3177 * } 3178 * .swap-animation.ng-enter, .swap-animation.ng-leave { 3179 * transition:0.5s linear all; 3180 * } 3181 * .swap-animation.ng-enter { 3182 * top:-250px; 3183 * } 3184 * .swap-animation.ng-enter-active { 3185 * top:0px; 3186 * } 3187 * .swap-animation.ng-leave { 3188 * top:0px; 3189 * } 3190 * .swap-animation.ng-leave-active { 3191 * top:250px; 3192 * } 3193 * .red { background:red; } 3194 * .green { background:green; } 3195 * .blue { background:blue; } 3196 * .yellow { background:yellow; } 3197 * .orange { background:orange; } 3198 * </file> 3199 * </example> 3200 */ 3201var ngAnimateSwapDirective = ['$animate', '$rootScope', function($animate, $rootScope) { 3202 return { 3203 restrict: 'A', 3204 transclude: 'element', 3205 terminal: true, 3206 priority: 600, // we use 600 here to ensure that the directive is caught before others 3207 link: function(scope, $element, attrs, ctrl, $transclude) { 3208 var previousElement, previousScope; 3209 scope.$watchCollection(attrs.ngAnimateSwap || attrs['for'], function(value) { 3210 if (previousElement) { 3211 $animate.leave(previousElement); 3212 } 3213 if (previousScope) { 3214 previousScope.$destroy(); 3215 previousScope = null; 3216 } 3217 if (value || value === 0) { 3218 previousScope = scope.$new(); 3219 $transclude(previousScope, function(element) { 3220 previousElement = element; 3221 $animate.enter(element, null, $element); 3222 }); 3223 } 3224 }); 3225 } 3226 }; 3227}]; 3228 3229/* global angularAnimateModule: true, 3230 3231 ngAnimateSwapDirective, 3232 $$AnimateAsyncRunFactory, 3233 $$rAFSchedulerFactory, 3234 $$AnimateChildrenDirective, 3235 $$AnimateQueueProvider, 3236 $$AnimationProvider, 3237 $AnimateCssProvider, 3238 $$AnimateCssDriverProvider,
3239 $$AnimateJsProvider, 3240 $$AnimateJsDriverProvider, 3241*/ 3242 3243/** 3244 * @ngdoc module 3245 * @name ngAnimate 3246 * @description 3247 * 3248 * The `ngAnimate` module provides support for CSS-based animations (keyframes and transitions) as well as JavaScript-based animations via 3249 * callback hooks. Animations are not enabled by default, however, by including `ngAnimate` the animation hooks are enabled for an Angular app. 3250 * 3251 * <div doc-module-components="ngAnimate"></div> 3252 * 3253 * # Usage 3254 * 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 3255 * using CSS (by using matching CSS selectors/styles) and the latter triggers animations that are registered via `module.animation()`. For 3256 * both CSS and JS animations the sole requirement is to have a matching `CSS class` that exists both in the registered animation and within 3257 * the HTML element that the animation will be triggered on. 3258 * 3259 * ## Directive Support 3260 * The following directives are "animation aware": 3261 * 3262 * | Directive | Supported Animations | 3263 * |----------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------| 3264 * | {@link ng.directive:ngRepeat#animations ngRepeat} | enter, leave and move | 3265 * | {@link ngRoute.directive:ngView#animations ngView} | enter and leave | 3266 * | {@link ng.directive:ngInclude#animations ngInclude} | enter and leave | 3267 * | {@link ng.directive:ngSwitch#animations ngSwitch} | enter and leave | 3268 * | {@link ng.directive:ngIf#animations ngIf} | enter and leave | 3269 * | {@link ng.directive:ngClass#animations ngClass} | add and remove (the CSS class(es) present) | 3270 * | {@link ng.directive:ngShow#animations ngShow} & {@link ng.directive:ngHide#animations ngHide} | add and remove (the ng-hide class value) | 3271 * | {@link ng.directive:form#animation-hooks form} & {@link ng.directive:ngModel#animation-hooks ngModel} | add and remove (dirty, pristine, valid, invalid & all other validations) | 3272 * | {@link module:ngMessages#animations ngMessages} | add and remove (ng-active & ng-inactive) | 3273 * | {@link module:ngMessages#animations ngMessage} | enter and leave | 3274 * 3275 * (More information can be found by visiting each the documentation associated with each directive.) 3276 * 3277 * ## CSS-based Animations 3278 * 3279 * 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 3280 * and CSS code we can create an animation that will be picked up by Angular when an the underlying directive performs an operation. 3281 * 3282 * The example below shows how an `enter` animation can be made possible on an element using `ng-if`: 3283 * 3284 * ```html 3285 * <div ng-if="bool" class="fade"> 3286 * Fade me in out 3287 * </div> 3288 * <button ng-click="bool=true">Fade In!</button> 3289 * <button ng-click="bool=false">Fade Out!</button> 3290 * ``` 3291 * 3292 * Notice the CSS class **fade**? We can now create the CSS transition code that references this class: 3293 * 3294 * ```css 3295 * /* The starting CSS styles for the enter animation */ 3296 * .fade.ng-enter { 3297 * transition:0.5s linear all; 3298 * opacity:0; 3299 * } 3300 * 3301 * /* The finishing CSS styles for the enter animation */ 3302 * .fade.ng-enter.ng-enter-active { 3303 * opacity:1; 3304 * } 3305 * ``` 3306 * 3307 * 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 3308 * 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 3309 * code **must** be defined within the starting CSS class (in this case `.ng-enter`). The destination class is what the transition will animate towards. 3310 * 3311 * 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: 3312 * 3313 * ```css 3314 * /* now the element will fade out before it is removed from the DOM */ 3315 * .fade.ng-leave { 3316 * transition:0.5s linear all; 3317 * opacity:1; 3318 * } 3319 * .fade.ng-leave.ng-leave-active { 3320 * opacity:0; 3321 * } 3322 * ``` 3323 * 3324 * We can also make use of **CSS Keyframes** by referencing the keyframe animation within the starting CSS class: 3325 * 3326 * ```css 3327 * /* there is no need to define anything inside of the destination 3328 * CSS class since the keyframe will take charge of the animation */ 3329 * .fade.ng-leave { 3330 * animation: my_fade_animation 0.5s linear; 3331 * -webkit-animation: my_fade_animation 0.5s linear; 3332 * } 3333 * 3334 * @keyframes my_fade_animation { 3335 * from { opacity:1; } 3336 * to { opacity:0; } 3337 * } 3338 * 3339 * @-webkit-keyframes my_fade_animation { 3340 * from { opacity:1; } 3341 * to { opacity:0; } 3342 * } 3343 * ``` 3344 * 3345 * Feel free also mix transitions and keyframes together as well as any other CSS classes on the same element. 3346 * 3347 * ### CSS Class-based Animations 3348 * 3349 * Class-based animations (animations that are triggered via `ngClass`, `ngShow`, `ngHide` and some other directives) have a slightly different 3350 * naming convention. Class-based animations are basic enough that a standard transition or keyframe can be referenced on the class being added 3351 * and removed. 3352 * 3353 * For example if we wanted to do a CSS animation for `ngHide` then we place an animation on the `.ng-hide` CSS class: 3354 * 3355 * ```html 3356 * <div ng-show="bool" class="fade"> 3357 * Show and hide me 3358 * </div> 3359 * <button ng-click="bool=true">Toggle</button> 3360 * 3361 * <style> 3362 * .fade.ng-hide { 3363 * transition:0.5s linear all; 3364 * opacity:0; 3365 * } 3366 * </style> 3367 * ``` 3368 * 3369 * 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
3370 * ngShow and ngHide are animation aware then we can match up a transition and ngAnimate handles the rest. 3371 * 3372 * 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 3373 * with CSS styles. 3374 * 3375 * ```html 3376 * <div ng-class="{on:onOff}" class="highlight"> 3377 * Highlight this box 3378 * </div> 3379 * <button ng-click="onOff=!onOff">Toggle</button> 3380 * 3381 * <style> 3382 * .highlight { 3383 * transition:0.5s linear all; 3384 * } 3385 * .highlight.on-add { 3386 * background:white; 3387 * } 3388 * .highlight.on { 3389 * background:yellow; 3390 * } 3391 * .highlight.on-remove { 3392 * background:black; 3393 * } 3394 * </style> 3395 * ``` 3396 * 3397 * We can also make use of CSS keyframes by placing them within the CSS classes. 3398 * 3399 * 3400 * ### CSS Staggering Animations 3401 * A Staggering animation is a collection of animations that are issued with a slight delay in between each successive operation resulting in a 3402 * curtain-like effect. The ngAnimate module (versions >=1.2) supports staggering animations and the stagger effect can be 3403 * performed by creating a **ng-EVENT-stagger** CSS class and attaching that class to the base CSS class used for 3404 * the animation. The style property expected within the stagger class can either be a **transition-delay** or an 3405 * **animation-delay** property (or both if your animation contains both transitions and keyframe animations). 3406 * 3407 * ```css 3408 * .my-animation.ng-enter { 3409 * /* standard transition code */ 3410 * transition: 1s linear all; 3411 * opacity:0; 3412 * } 3413 * .my-animation.ng-enter-stagger { 3414 * /* this will have a 100ms delay between each successive leave animation */ 3415 * transition-delay: 0.1s; 3416 * 3417 * /* As of 1.4.4, this must always be set: it signals ngAnimate 3418 * to not accidentally inherit a delay property from another CSS class */ 3419 * transition-duration: 0s; 3420 * } 3421 * .my-animation.ng-enter.ng-enter-active { 3422 * /* standard transition styles */ 3423 * opacity:1; 3424 * } 3425 * ``` 3426 * 3427 * Staggering animations work by default in ngRepeat (so long as the CSS class is defined). Outside of ngRepeat, to use staggering animations 3428 * on your own, they can be triggered by firing multiple calls to the same event on $animate. However, the restrictions surrounding this 3429 * are that each of the elements must have the same CSS className value as well as the same parent element. A stagger operation 3430 * will also be reset if one or more animation frames have passed since the multiple calls to `$animate` were fired. 3431 * 3432 * The following code will issue the **ng-leave-stagger** event on the element provided: 3433 * 3434 * ```js 3435 * var kids = parent.children(); 3436 * 3437 * $animate.leave(kids[0]); //stagger index=0 3438 * $animate.leave(kids[1]); //stagger index=1 3439 * $animate.leave(kids[2]); //stagger index=2 3440 * $animate.leave(kids[3]); //stagger index=3 3441 * $animate.leave(kids[4]); //stagger index=4 3442 * 3443 * window.requestAnimationFrame(function() { 3444 * //stagger has reset itself 3445 * $animate.leave(kids[5]); //stagger index=0 3446 * $animate.leave(kids[6]); //stagger index=1 3447 * 3448 * $scope.$digest(); 3449 * }); 3450 * ``` 3451 * 3452 * Stagger animations are currently only supported within CSS-defined animations. 3453 * 3454 * ### The `ng-animate` CSS class 3455 * 3456 * When ngAnimate is animating an element it will apply the `ng-animate` CSS class to the element for the duration of the animation. 3457 * This is a temporary CSS class and it will be removed once the animation is over (for both JavaScript and CSS-based animations). 3458 * 3459 * Therefore, animations can be applied to an element using this temporary class directly via CSS. 3460 * 3461 * ```css 3462 * .zipper.ng-animate { 3463 * transition:0.5s linear all; 3464 * } 3465 * .zipper.ng-enter { 3466 * opacity:0; 3467 * } 3468 * .zipper.ng-enter.ng-enter-active { 3469 * opacity:1; 3470 * } 3471 * .zipper.ng-leave { 3472 * opacity:1; 3473 * } 3474 * .zipper.ng-leave.ng-leave-active { 3475 * opacity:0; 3476 * } 3477 * ``` 3478 * 3479 * (Note that the `ng-animate` CSS class is reserved and it cannot be applied on an element directly since ngAnimate will always remove 3480 * the CSS class once an animation has completed.) 3481 * 3482 * 3483 * ### The `ng-[event]-prepare` class 3484 * 3485 * This is a special class that can be used to prevent unwanted flickering / flash of content before 3486 * the actual animation starts. The class is added as soon as an animation is initialized, but removed 3487 * before the actual animation starts (after waiting for a $digest). 3488 * It is also only added for *structural* animations (`enter`, `move`, and `leave`). 3489 * 3490 * In practice, flickering can appear when nesting elements with structural animations such as `ngIf` 3491 * into elements that have class-based animations such as `ngClass`. 3492 * 3493 * ```html 3494 * <div ng-class="{red: myProp}">
3495 * <div ng-class="{blue: myProp}"> 3496 * <div class="message" ng-if="myProp"></div> 3497 * </div> 3498 * </div> 3499 * ``` 3500 * 3501 * It is possible that during the `enter` animation, the `.message` div will be briefly visible before it starts animating. 3502 * In that case, you can add styles to the CSS that make sure the element stays hidden before the animation starts: 3503 * 3504 * ```css 3505 * .message.ng-enter-prepare { 3506 * opacity: 0; 3507 * } 3508 * 3509 * ``` 3510 * 3511 * ## JavaScript-based Animations 3512 * 3513 * ngAnimate also allows for animations to be consumed by JavaScript code. The approach is similar to CSS-based animations (where there is a shared 3514 * 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 3515 * `module.animation()` module function we can register the animation. 3516 * 3517 * Let's see an example of a enter/leave animation using `ngRepeat`: 3518 * 3519 * ```html 3520 * <div ng-repeat="item in items" class="slide"> 3521 * {{ item }} 3522 * </div> 3523 * ``` 3524 * 3525 * 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`: 3526 * 3527 * ```js 3528 * myModule.animation('.slide', [function() { 3529 * return { 3530 * // make note that other events (like addClass/removeClass) 3531 * // have different function input parameters 3532 * enter: function(element, doneFn) { 3533 * jQuery(element).fadeIn(1000, doneFn); 3534 * 3535 * // remember to call doneFn so that angular 3536 * // knows that the animation has concluded 3537 * }, 3538 * 3539 * move: function(element, doneFn) { 3540 * jQuery(element).fadeIn(1000, doneFn); 3541 * }, 3542 * 3543 * leave: function(element, doneFn) { 3544 * jQuery(element).fadeOut(1000, doneFn); 3545 * } 3546 * } 3547 * }]); 3548 * ``` 3549 * 3550 * The nice thing about JS-based animations is that we can inject other services and make use of advanced animation libraries such as 3551 * greensock.js and velocity.js. 3552 * 3553 * If our animation code class-based (meaning that something like `ngClass`, `ngHide` and `ngShow` triggers it) then we can still define 3554 * our animations inside of the same registered animation, however, the function input arguments are a bit different: 3555 * 3556 * ```html 3557 * <div ng-class="color" class="colorful"> 3558 * this box is moody 3559 * </div> 3560 * <button ng-click="color='red'">Change to red</button> 3561 * <button ng-click="color='blue'">Change to blue</button> 3562 * <button ng-click="color='green'">Change to green</button> 3563 * ``` 3564 * 3565 * ```js 3566 * myModule.animation('.colorful', [function() { 3567 * return { 3568 * addClass: function(element, className, doneFn) { 3569 * // do some cool animation and call the doneFn 3570 * }, 3571 * removeClass: function(element, className, doneFn) { 3572 * // do some cool animation and call the doneFn 3573 * }, 3574 * setClass: function(element, addedClass, removedClass, doneFn) { 3575 * // do some cool animation and call the doneFn 3576 * } 3577 * } 3578 * }]); 3579 * ``` 3580 * 3581 * ## CSS + JS Animations Together 3582 * 3583 * 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, 3584 * 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 3585 * charge of the animation**: 3586 * 3587 * ```html 3588 * <div ng-if="bool" class="slide"> 3589 * Slide in and out 3590 * </div> 3591 * ``` 3592 * 3593 * ```js 3594 * myModule.animation('.slide', [function() { 3595 * return { 3596 * enter: function(element, doneFn) { 3597 * jQuery(element).slideIn(1000, doneFn); 3598 * } 3599 * } 3600 * }]); 3601 * ``` 3602 * 3603 * ```css 3604 * .slide.ng-enter { 3605 * transition:0.5s linear all; 3606 * transform:translateY(-100px); 3607 * } 3608 * .slide.ng-enter.ng-enter-active { 3609 * transform:translateY(0); 3610 * } 3611 * ``` 3612 * 3613 * 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 3614 * lack of CSS animations by using the `$animateCss` service to trigger our own tweaked-out, CSS-based animations directly from 3615 * our own JS-based animation code: 3616 * 3617 * ```js 3618 * myModule.animation('.slide', ['$animateCss', function($animateCss) { 3619 * return { 3620 * enter: function(element) { 3621* // this will trigger `.slide.ng-enter` and `.slide.ng-enter-active`. 3622 * return $animateCss(element, { 3623 * event: 'enter',
3624 * structural: true 3625 * }); 3626 * } 3627 * } 3628 * }]); 3629 * ``` 3630 * 3631 * 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. 3632 * 3633 * 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 3634 * 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 3635 * data into `$animateCss` directly: 3636 * 3637 * ```js 3638 * myModule.animation('.slide', ['$animateCss', function($animateCss) { 3639 * return { 3640 * enter: function(element) { 3641 * return $animateCss(element, { 3642 * event: 'enter', 3643 * structural: true, 3644 * addClass: 'maroon-setting', 3645 * from: { height:0 }, 3646 * to: { height: 200 } 3647 * }); 3648 * } 3649 * } 3650 * }]); 3651 * ``` 3652 * 3653 * Now we can fill in the rest via our transition CSS code: 3654 * 3655 * ```css 3656 * /* the transition tells ngAnimate to make the animation happen */ 3657 * .slide.ng-enter { transition:0.5s linear all; } 3658 * 3659 * /* this extra CSS class will be absorbed into the transition 3660 * since the $animateCss code is adding the class */ 3661 * .maroon-setting { background:red; } 3662 * ``` 3663 * 3664 * 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. 3665 * 3666 * To learn more about what's possible be sure to visit the {@link ngAnimate.$animateCss $animateCss service}. 3667 * 3668 * ## Animation Anchoring (via `ng-animate-ref`) 3669 * 3670 * ngAnimate in AngularJS 1.4 comes packed with the ability to cross-animate elements between 3671 * structural areas of an application (like views) by pairing up elements using an attribute 3672 * called `ng-animate-ref`. 3673 * 3674 * Let's say for example we have two views that are managed by `ng-view` and we want to show 3675 * that there is a relationship between two components situated in within these views. By using the 3676 * `ng-animate-ref` attribute we can identify that the two components are paired together and we 3677 * can then attach an animation, which is triggered when the view changes. 3678 * 3679 * Say for example we have the following template code: 3680 * 3681 * ```html 3682 * <!-- index.html --> 3683 * <div ng-view class="view-animation"> 3684 * </div> 3685 * 3686 * <!-- home.html --> 3687 * <a href="#/banner-page"> 3688 * <img src="./banner.jpg" class="banner" ng-animate-ref="banner"> 3689 * </a> 3690 * 3691 * <!-- banner-page.html --> 3692 * <img src="./banner.jpg" class="banner" ng-animate-ref="banner"> 3693 * ``` 3694 * 3695 * Now, when the view changes (once the link is clicked), ngAnimate will examine the 3696 * HTML contents to see if there is a match reference between any components in the view 3697 * that is leaving and the view that is entering. It will scan both the view which is being 3698 * removed (leave) and inserted (enter) to see if there are any paired DOM elements that 3699 * contain a matching ref value. 3700 * 3701 * The two images match since they share the same ref value. ngAnimate will now create a 3702 * transport element (which is a clone of the first image element) and it will then attempt 3703 * to animate to the position of the second image element in the next view. For the animation to 3704 * work a special CSS class called `ng-anchor` will be added to the transported element. 3705 * 3706 * We can now attach a transition onto the `.banner.ng-anchor` CSS class and then 3707 * ngAnimate will handle the entire transition for us as well as the addition and removal of 3708 * any changes of CSS classes between the elements: 3709 * 3710 * ```css 3711 * .banner.ng-anchor { 3712 * /* this animation will last for 1 second since there are 3713 * two phases to the animation (an `in` and an `out` phase) */ 3714 * transition:0.5s linear all; 3715 * } 3716 * ``` 3717 * 3718 * We also **must** include animations for the views that are being entered and removed 3719 * (otherwise anchoring wouldn't be possible since the new view would be inserted right away). 3720 * 3721 * ```css 3722 * .view-animation.ng-enter, .view-animation.ng-leave { 3723 * transition:0.5s linear all; 3724 * position:fixed; 3725 * left:0; 3726 * top:0; 3727 * width:100%; 3728 * } 3729 * .view-animation.ng-enter { 3730 * transform:translateX(100%); 3731 * } 3732 * .view-animation.ng-leave, 3733 * .view-animation.ng-enter.ng-enter-active { 3734 * transform:translateX(0%); 3735 * } 3736 * .view-animation.ng-leave.ng-leave-active { 3737 * transform:translateX(-100%); 3738 * } 3739 * ``` 3740 *
3741 * Now we can jump back to the anchor animation. When the animation happens, there are two stages that occur: 3742 * an `out` and an `in` stage. The `out` stage happens first and that is when the element is animated away 3743 * from its origin. Once that animation is over then the `in` stage occurs which animates the 3744 * element to its destination. The reason why there are two animations is to give enough time 3745 * for the enter animation on the new element to be ready. 3746 * 3747 * The example above sets up a transition for both the in and out phases, but we can also target the out or 3748 * in phases directly via `ng-anchor-out` and `ng-anchor-in`. 3749 * 3750 * ```css 3751 * .banner.ng-anchor-out { 3752 * transition: 0.5s linear all; 3753 * 3754 * /* the scale will be applied during the out animation, 3755 * but will be animated away when the in animation runs */ 3756 * transform: scale(1.2); 3757 * } 3758 * 3759 * .banner.ng-anchor-in { 3760 * transition: 1s linear all; 3761 * } 3762 * ``` 3763 * 3764 * 3765 * 3766 * 3767 * ### Anchoring Demo 3768 * 3769 <example module="anchoringExample" 3770 name="anchoringExample" 3771 id="anchoringExample" 3772 deps="angular-animate.js;angular-route.js" 3773 animations="true"> 3774 <file name="index.html"> 3775 <a href="#/">Home</a> 3776 <hr /> 3777 <div class="view-container"> 3778 <div ng-view class="view"></div> 3779 </div> 3780 </file> 3781 <file name="script.js"> 3782 angular.module('anchoringExample', ['ngAnimate', 'ngRoute']) 3783 .config(['$routeProvider', function($routeProvider) { 3784 $routeProvider.when('/', { 3785 templateUrl: 'home.html', 3786 controller: 'HomeController as home' 3787 }); 3788 $routeProvider.when('/profile/:id', { 3789 templateUrl: 'profile.html', 3790 controller: 'ProfileController as profile' 3791 }); 3792 }]) 3793 .run(['$rootScope', function($rootScope) { 3794 $rootScope.records = [ 3795 { id:1, title: "Miss Beulah Roob" }, 3796 { id:2, title: "Trent Morissette" }, 3797 { id:3, title: "Miss Ava Pouros" }, 3798 { id:4, title: "Rod Pouros" }, 3799 { id:5, title: "Abdul Rice" }, 3800 { id:6, title: "Laurie Rutherford Sr." }, 3801 { id:7, title: "Nakia McLaughlin" }, 3802 { id:8, title: "Jordon Blanda DVM" }, 3803 { id:9, title: "Rhoda Hand" }, 3804 { id:10, title: "Alexandrea Sauer" } 3805 ]; 3806 }]) 3807 .controller('HomeController', [function() { 3808 //empty 3809 }]) 3810 .controller('ProfileController', ['$rootScope', '$routeParams', function($rootScope, $routeParams) { 3811 var index = parseInt($routeParams.id, 10); 3812 var record = $rootScope.records[index - 1]; 3813 3814 this.title = record.title; 3815 this.id = record.id; 3816 }]); 3817 </file> 3818 <file name="home.html"> 3819 <h2>Welcome to the home page</h1> 3820 <p>Please click on an element</p> 3821 <a class="record" 3822 ng-href="#/profile/{{ record.id }}" 3823 ng-animate-ref="{{ record.id }}" 3824 ng-repeat="record in records"> 3825 {{ record.title }} 3826 </a> 3827 </file> 3828 <file name="profile.html"> 3829 <div class="profile record" ng-animate-ref="{{ profile.id }}"> 3830 {{ profile.title }} 3831 </div> 3832 </file> 3833 <file name="animations.css"> 3834 .record { 3835 display:block; 3836 font-size:20px; 3837 } 3838 .profile { 3839 background:black; 3840 color:white; 3841 font-size:100px; 3842 } 3843 .view-container { 3844 position:relative; 3845 } 3846 .view-container > .view.ng-animate { 3847 position:absolute; 3848 top:0; 3849 left:0; 3850 width:100%; 3851 min-height:500px; 3852 } 3853 .view.ng-enter, .view.ng-leave, 3854 .record.ng-anchor { 3855 transition:0.5s linear all; 3856 } 3857 .view.ng-enter { 3858 transform:translateX(100%); 3859 } 3860 .view.ng-enter.ng-enter-active, .view.ng-leave { 3861 transform:translateX(0%); 3862 } 3863 .view.ng-leave.ng-leave-active { 3864 transform:translateX(-100%); 3865 } 3866 .record.ng-anchor-out { 3867 background:red; 3868 } 3869 </file> 3870 </example> 3871 *
3872 * ### How is the element transported? 3873 * 3874 * When an anchor animation occurs, ngAnimate will clone the starting element and position it exactly where the starting 3875 * element is located on screen via absolute positioning. The cloned element will be placed inside of the root element 3876 * of the application (where ng-app was defined) and all of the CSS classes of the starting element will be applied. The 3877 * element will then animate into the `out` and `in` animations and will eventually reach the coordinates and match 3878 * the dimensions of the destination element. During the entire animation a CSS class of `.ng-animate-shim` will be applied 3879 * to both the starting and destination elements in order to hide them from being visible (the CSS styling for the class 3880 * is: `visibility:hidden`). Once the anchor reaches its destination then it will be removed and the destination element 3881 * will become visible since the shim class will be removed. 3882 * 3883 * ### How is the morphing handled? 3884 * 3885 * CSS Anchoring relies on transitions and keyframes and the internal code is intelligent enough to figure out 3886 * what CSS classes differ between the starting element and the destination element. These different CSS classes 3887 * will be added/removed on the anchor element and a transition will be applied (the transition that is provided 3888 * in the anchor class). Long story short, ngAnimate will figure out what classes to add and remove which will 3889 * make the transition of the element as smooth and automatic as possible. Be sure to use simple CSS classes that 3890 * do not rely on DOM nesting structure so that the anchor element appears the same as the starting element (since 3891 * the cloned element is placed inside of root element which is likely close to the body element). 3892 * 3893 * Note that if the root element is on the `<html>` element then the cloned node will be placed inside of body. 3894 * 3895 * 3896 * ## Using $animate in your directive code 3897 * 3898 * 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? 3899 * By injecting the `$animate` service into our directive code, we can trigger structural and class-based hooks which can then be c
3899onsumed by animations. Let's 3900 * imagine we have a greeting box that shows and hides itself when the data changes 3901 * 3902 * ```html 3903 * <greeting-box active="onOrOff">Hi there</greeting-box> 3904 * ``` 3905 * 3906 * ```js 3907 * ngModule.directive('greetingBox', ['$animate', function($animate) { 3908 * return function(scope, element, attrs) { 3909 * attrs.$observe('active', function(value) { 3910 * value ? $animate.addClass(element, 'on') : $animate.removeClass(element, 'on'); 3911 * }); 3912 * }); 3913 * }]); 3914 * ``` 3915 * 3916 * 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 3917 * in our HTML code then we can trigger a CSS or JS animation to happen. 3918 * 3919 * ```css 3920 * /* normally we would create a CSS class to reference on the element */ 3921 * greeting-box.on { transition:0.5s linear all; background:green; color:white; } 3922 * ``` 3923 * 3924 * The `$animate` service contains a variety of other methods like `enter`, `leave`, `animate` and `setClass`. To learn more about what's 3925 * possible be sure to visit the {@link ng.$animate $animate service API page}. 3926 * 3927 * 3928 * ### Preventing Collisions With Third Party Libraries 3929 * 3930 * Some third-party frameworks place animation duration defaults across many element or className 3931 * selectors in order to make their code small and reuseable. This can lead to issues with ngAnimate, which 3932 * is expecting actual animations on these elements and has to wait for their completion. 3933 * 3934 * You can prevent this unwanted behavior by using a prefix on all your animation classes: 3935 * 3936 * ```css 3937 * /* prefixed with animate- */ 3938 * .animate-fade-add.animate-fade-add-active { 3939 * transition:1s linear all; 3940 * opacity:0; 3941 * } 3942 * ``` 3943 * 3944 * You then configure `$animate` to enforce this prefix: 3945 * 3946 * ```js 3947 * $animateProvider.classNameFilter(/animate-/); 3948 * ``` 3949 * 3950 * This also may provide your application with a speed boost since only specific elements containing CSS class prefix 3951 * will be evaluated for animation when any DOM changes occur in the application. 3952 * 3953 * ## Callbacks and Promises 3954 * 3955 * 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 3956 * an animation (within our directive code) then we can continue performing directive and scope related activities after the animation has 3957 * ended by chaining onto the returned promise that animation method returns. 3958 * 3959 * ```js 3960 * // somewhere within the depths of the directive 3961 * $animate.enter(element, parent).then(function() { 3962 * //the animation has completed 3963 * }); 3964 * ``` 3965 * 3966 * (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 3967 * anymore.) 3968 * 3969 * In addition to the animation promise, we can also make use of animation-related callbacks within our directives and controller code by registering 3970 * an event listener using the `$animate` service. Let's say for example that an animation was triggered on our view 3971 * routing controller to hook into that: 3972 * 3973 * ```js 3974 * ngModule.controller('HomePageController', ['$animate', function($animate) { 3975 * $animate.on('enter', ngViewElement, function(element) { 3976 * // the animation for this route has completed 3977 * }]); 3978 * }]) 3979 * ``` 3980 * 3981 * (Note that you will need to trigger a digest within the callback to get angular to notice any scope-related changes.) 3982 */ 3983 3984/** 3985 * @ngdoc service 3986 * @name $animate 3987 * @kind object 3988 * 3989 * @description 3990 * The ngAnimate `$animate` service documentation is the same for the core `$animate` service. 3991 * 3992 * Click here {@link ng.$animate to learn more about animations with `$animate`}. 3993 */ 3994angular.module('ngAnimate', []) 3995 .directive('ngAnimateSwap', ngAnimateSwapDirective) 3996 3997 .directive('ngAnimateChildren', $$AnimateChildrenDirective) 3998 .factory('$$rAFScheduler', $$rAFSchedulerFactory) 3999 4000 .provider('$$animateQueue', $$AnimateQueueProvider) 4001 .provider('$$animation', $$AnimationProvider) 4002 4003 .provider('$animateCss', $AnimateCssProvider) 4004 .provider('$$animateCssDriver', $$AnimateCssDriverProvider) 4005
4006 .provider('$$animateJs', $$AnimateJsProvider) 4007 .provider('$$animateJsDriver', $$AnimateJsDriverProvider); 4008 4009 4010})(window, window.angular);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.