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