2015-08-07 16:37:36 +02:00
/ * *
2017-07-04 07:24:12 +02:00
* @ license AngularJS v1 . 6.5
2017-02-08 07:27:36 +01:00
* ( c ) 2010 - 2017 Google , Inc . http : //angularjs.org
2015-08-07 16:37:36 +02:00
* License : MIT
* /
2016-10-12 08:08:16 +02:00
( function ( window , angular ) { 'use strict' ;
2015-08-25 17:46:14 +02:00
var ELEMENT _NODE = 1 ;
var COMMENT _NODE = 8 ;
var ADD _CLASS _SUFFIX = '-add' ;
var REMOVE _CLASS _SUFFIX = '-remove' ;
var EVENT _CLASS _PREFIX = 'ng-' ;
var ACTIVE _CLASS _SUFFIX = '-active' ;
2016-03-17 07:28:59 +01:00
var PREPARE _CLASS _SUFFIX = '-prepare' ;
2015-08-25 17:46:14 +02:00
var NG _ANIMATE _CLASSNAME = 'ng-animate' ;
var NG _ANIMATE _CHILDREN _DATA = '$$ngAnimateChildren' ;
// Detect proper transitionend/animationend event names.
var CSS _PREFIX = '' , TRANSITION _PROP , TRANSITIONEND _EVENT , ANIMATION _PROP , ANIMATIONEND _EVENT ;
// If unprefixed events are not supported but webkit-prefixed are, use the latter.
// Otherwise, just use W3C names, browsers not supporting them at all will just ignore them.
// Note: Chrome implements `window.onwebkitanimationend` and doesn't implement `window.onanimationend`
// but at the same time dispatches the `animationend` event and not `webkitAnimationEnd`.
// Register both events in case `window.onanimationend` is not supported because of that,
// do the same for `transitionend` as Safari is likely to exhibit similar behavior.
// Also, the only modern browser that uses vendor prefixes for transitions/keyframes is webkit
// therefore there is no reason to test anymore for other vendor prefixes:
// http://caniuse.com/#search=transition
2016-11-25 08:43:18 +01:00
if ( ( window . ontransitionend === undefined ) && ( window . onwebkittransitionend !== undefined ) ) {
2015-08-25 17:46:14 +02:00
CSS _PREFIX = '-webkit-' ;
TRANSITION _PROP = 'WebkitTransition' ;
TRANSITIONEND _EVENT = 'webkitTransitionEnd transitionend' ;
} else {
TRANSITION _PROP = 'transition' ;
TRANSITIONEND _EVENT = 'transitionend' ;
}
2016-11-25 08:43:18 +01:00
if ( ( window . onanimationend === undefined ) && ( window . onwebkitanimationend !== undefined ) ) {
2015-08-25 17:46:14 +02:00
CSS _PREFIX = '-webkit-' ;
ANIMATION _PROP = 'WebkitAnimation' ;
ANIMATIONEND _EVENT = 'webkitAnimationEnd animationend' ;
} else {
ANIMATION _PROP = 'animation' ;
ANIMATIONEND _EVENT = 'animationend' ;
}
var DURATION _KEY = 'Duration' ;
var PROPERTY _KEY = 'Property' ;
var DELAY _KEY = 'Delay' ;
var TIMING _KEY = 'TimingFunction' ;
var ANIMATION _ITERATION _COUNT _KEY = 'IterationCount' ;
var ANIMATION _PLAYSTATE _KEY = 'PlayState' ;
var SAFE _FAST _FORWARD _DURATION _VALUE = 9999 ;
var ANIMATION _DELAY _PROP = ANIMATION _PROP + DELAY _KEY ;
var ANIMATION _DURATION _PROP = ANIMATION _PROP + DURATION _KEY ;
var TRANSITION _DELAY _PROP = TRANSITION _PROP + DELAY _KEY ;
var TRANSITION _DURATION _PROP = TRANSITION _PROP + DURATION _KEY ;
2016-02-03 19:34:41 +01:00
var ngMinErr = angular . $$minErr ( 'ng' ) ;
2015-08-25 17:46:14 +02:00
function assertArg ( arg , name , reason ) {
if ( ! arg ) {
2016-11-25 08:43:18 +01:00
throw ngMinErr ( 'areq' , 'Argument \'{0}\' is {1}' , ( name || '?' ) , ( reason || 'required' ) ) ;
2015-08-25 17:46:14 +02:00
}
return arg ;
}
function mergeClasses ( a , b ) {
if ( ! a && ! b ) return '' ;
if ( ! a ) return b ;
if ( ! b ) return a ;
if ( isArray ( a ) ) a = a . join ( ' ' ) ;
if ( isArray ( b ) ) b = b . join ( ' ' ) ;
return a + ' ' + b ;
}
function packageStyles ( options ) {
var styles = { } ;
if ( options && ( options . to || options . from ) ) {
styles . to = options . to ;
styles . from = options . from ;
}
return styles ;
}
function pendClasses ( classes , fix , isPrefix ) {
var className = '' ;
classes = isArray ( classes )
? classes
: classes && isString ( classes ) && classes . length
? classes . split ( /\s+/ )
: [ ] ;
forEach ( classes , function ( klass , i ) {
if ( klass && klass . length > 0 ) {
className += ( i > 0 ) ? ' ' : '' ;
className += isPrefix ? fix + klass
: klass + fix ;
}
} ) ;
return className ;
}
function removeFromArray ( arr , val ) {
var index = arr . indexOf ( val ) ;
if ( val >= 0 ) {
arr . splice ( index , 1 ) ;
}
}
function stripCommentsFromElement ( element ) {
if ( element instanceof jqLite ) {
switch ( element . length ) {
case 0 :
2016-05-28 07:11:08 +02:00
return element ;
2015-08-25 17:46:14 +02:00
case 1 :
// there is no point of stripping anything if the element
// is the only element within the jqLite wrapper.
// (it's important that we retain the element instance.)
if ( element [ 0 ] . nodeType === ELEMENT _NODE ) {
return element ;
}
break ;
default :
return jqLite ( extractElementNode ( element ) ) ;
}
}
if ( element . nodeType === ELEMENT _NODE ) {
return jqLite ( element ) ;
}
}
function extractElementNode ( element ) {
if ( ! element [ 0 ] ) return element ;
for ( var i = 0 ; i < element . length ; i ++ ) {
var elm = element [ i ] ;
2016-11-25 08:43:18 +01:00
if ( elm . nodeType === ELEMENT _NODE ) {
2015-08-25 17:46:14 +02:00
return elm ;
}
}
}
function $$addClass ( $$jqLite , element , className ) {
forEach ( element , function ( elm ) {
$$jqLite . addClass ( elm , className ) ;
} ) ;
}
function $$removeClass ( $$jqLite , element , className ) {
forEach ( element , function ( elm ) {
$$jqLite . removeClass ( elm , className ) ;
} ) ;
}
function applyAnimationClassesFactory ( $$jqLite ) {
return function ( element , options ) {
if ( options . addClass ) {
$$addClass ( $$jqLite , element , options . addClass ) ;
options . addClass = null ;
}
if ( options . removeClass ) {
$$removeClass ( $$jqLite , element , options . removeClass ) ;
options . removeClass = null ;
}
2016-10-12 08:08:16 +02:00
} ;
2015-08-25 17:46:14 +02:00
}
function prepareAnimationOptions ( options ) {
options = options || { } ;
if ( ! options . $$prepared ) {
var domOperation = options . domOperation || noop ;
options . domOperation = function ( ) {
options . $$domOperationFired = true ;
domOperation ( ) ;
domOperation = noop ;
} ;
options . $$prepared = true ;
}
return options ;
}
function applyAnimationStyles ( element , options ) {
applyAnimationFromStyles ( element , options ) ;
applyAnimationToStyles ( element , options ) ;
}
function applyAnimationFromStyles ( element , options ) {
if ( options . from ) {
element . css ( options . from ) ;
options . from = null ;
}
}
function applyAnimationToStyles ( element , options ) {
if ( options . to ) {
element . css ( options . to ) ;
options . to = null ;
}
}
2016-03-17 07:28:59 +01:00
function mergeAnimationDetails ( element , oldAnimation , newAnimation ) {
var target = oldAnimation . options || { } ;
var newOptions = newAnimation . options || { } ;
2015-08-25 17:46:14 +02:00
var toAdd = ( target . addClass || '' ) + ' ' + ( newOptions . addClass || '' ) ;
var toRemove = ( target . removeClass || '' ) + ' ' + ( newOptions . removeClass || '' ) ;
var classes = resolveElementClasses ( element . attr ( 'class' ) , toAdd , toRemove ) ;
if ( newOptions . preparationClasses ) {
target . preparationClasses = concatWithSpace ( newOptions . preparationClasses , target . preparationClasses ) ;
delete newOptions . preparationClasses ;
}
// noop is basically when there is no callback; otherwise something has been set
var realDomOperation = target . domOperation !== noop ? target . domOperation : null ;
extend ( target , newOptions ) ;
// 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.
if ( realDomOperation ) {
target . domOperation = realDomOperation ;
}
if ( classes . addClass ) {
target . addClass = classes . addClass ;
} else {
target . addClass = null ;
}
if ( classes . removeClass ) {
target . removeClass = classes . removeClass ;
} else {
target . removeClass = null ;
}
2016-03-17 07:28:59 +01:00
oldAnimation . addClass = target . addClass ;
oldAnimation . removeClass = target . removeClass ;
2015-08-25 17:46:14 +02:00
return target ;
}
function resolveElementClasses ( existing , toAdd , toRemove ) {
var ADD _CLASS = 1 ;
var REMOVE _CLASS = - 1 ;
var flags = { } ;
existing = splitClassesToLookup ( existing ) ;
toAdd = splitClassesToLookup ( toAdd ) ;
forEach ( toAdd , function ( value , key ) {
flags [ key ] = ADD _CLASS ;
} ) ;
toRemove = splitClassesToLookup ( toRemove ) ;
forEach ( toRemove , function ( value , key ) {
flags [ key ] = flags [ key ] === ADD _CLASS ? null : REMOVE _CLASS ;
} ) ;
var classes = {
addClass : '' ,
removeClass : ''
} ;
forEach ( flags , function ( val , klass ) {
var prop , allow ;
if ( val === ADD _CLASS ) {
prop = 'addClass' ;
2016-10-12 08:08:16 +02:00
allow = ! existing [ klass ] || existing [ klass + REMOVE _CLASS _SUFFIX ] ;
2015-08-25 17:46:14 +02:00
} else if ( val === REMOVE _CLASS ) {
prop = 'removeClass' ;
2016-10-12 08:08:16 +02:00
allow = existing [ klass ] || existing [ klass + ADD _CLASS _SUFFIX ] ;
2015-08-25 17:46:14 +02:00
}
if ( allow ) {
if ( classes [ prop ] . length ) {
classes [ prop ] += ' ' ;
}
classes [ prop ] += klass ;
}
} ) ;
function splitClassesToLookup ( classes ) {
if ( isString ( classes ) ) {
classes = classes . split ( ' ' ) ;
}
var obj = { } ;
forEach ( classes , function ( klass ) {
// sometimes the split leaves empty string values
// incase extra spaces were applied to the options
if ( klass . length ) {
obj [ klass ] = true ;
}
} ) ;
return obj ;
}
return classes ;
}
function getDomNode ( element ) {
2016-10-12 08:08:16 +02:00
return ( element instanceof jqLite ) ? element [ 0 ] : element ;
2015-08-25 17:46:14 +02:00
}
function applyGeneratedPreparationClasses ( element , event , options ) {
var classes = '' ;
if ( event ) {
classes = pendClasses ( event , EVENT _CLASS _PREFIX , true ) ;
}
if ( options . addClass ) {
classes = concatWithSpace ( classes , pendClasses ( options . addClass , ADD _CLASS _SUFFIX ) ) ;
}
if ( options . removeClass ) {
classes = concatWithSpace ( classes , pendClasses ( options . removeClass , REMOVE _CLASS _SUFFIX ) ) ;
}
if ( classes . length ) {
options . preparationClasses = classes ;
element . addClass ( classes ) ;
}
}
function clearGeneratedClasses ( element , options ) {
if ( options . preparationClasses ) {
element . removeClass ( options . preparationClasses ) ;
options . preparationClasses = null ;
}
if ( options . activeClasses ) {
element . removeClass ( options . activeClasses ) ;
options . activeClasses = null ;
}
}
function blockTransitions ( node , duration ) {
// we use a negative delay value since it performs blocking
// yet it doesn't kill any existing transitions running on the
// same element which makes this safe for class-based animations
var value = duration ? '-' + duration + 's' : '' ;
applyInlineStyle ( node , [ TRANSITION _DELAY _PROP , value ] ) ;
return [ TRANSITION _DELAY _PROP , value ] ;
}
function blockKeyframeAnimations ( node , applyBlock ) {
var value = applyBlock ? 'paused' : '' ;
var key = ANIMATION _PROP + ANIMATION _PLAYSTATE _KEY ;
applyInlineStyle ( node , [ key , value ] ) ;
return [ key , value ] ;
}
function applyInlineStyle ( node , styleTuple ) {
var prop = styleTuple [ 0 ] ;
var value = styleTuple [ 1 ] ;
node . style [ prop ] = value ;
}
function concatWithSpace ( a , b ) {
if ( ! a ) return b ;
if ( ! b ) return a ;
return a + ' ' + b ;
}
2015-08-31 17:08:41 +02:00
var $$rAFSchedulerFactory = [ '$$rAF' , function ( $$rAF ) {
var queue , cancelFn ;
function scheduler ( tasks ) {
// we make a copy since RAFScheduler mutates the state
// of the passed in array variable and this would be difficult
// to track down on the outside code
queue = queue . concat ( tasks ) ;
nextTick ( ) ;
}
queue = scheduler . queue = [ ] ;
/ * w a i t U n t i l Q u i e t d o e s t w o t h i n g s :
2016-10-12 08:08:16 +02:00
* 1. It will run the FINAL ` fn ` value only when an uncanceled RAF has passed through
2015-08-31 17:08:41 +02:00
* 2. It will delay the next wave of tasks from running until the quiet ` fn ` has run .
*
* The motivation here is that animation code can request more time from the scheduler
* before the next wave runs . This allows for certain DOM properties such as classes to
* be resolved in time for the next animation to run .
* /
scheduler . waitUntilQuiet = function ( fn ) {
if ( cancelFn ) cancelFn ( ) ;
cancelFn = $$rAF ( function ( ) {
cancelFn = null ;
fn ( ) ;
nextTick ( ) ;
} ) ;
} ;
return scheduler ;
function nextTick ( ) {
if ( ! queue . length ) return ;
var items = queue . shift ( ) ;
for ( var i = 0 ; i < items . length ; i ++ ) {
items [ i ] ( ) ;
}
if ( ! cancelFn ) {
$$rAF ( function ( ) {
if ( ! cancelFn ) nextTick ( ) ;
} ) ;
}
}
} ] ;
2016-03-17 07:28:59 +01:00
/ * *
* @ ngdoc directive
* @ name ngAnimateChildren
* @ restrict AE
* @ element ANY
*
* @ description
*
* ngAnimateChildren allows you to specify that children of this element should animate even if any
* of the children ' s parents are currently animating . By default , when an element has an active ` enter ` , ` leave ` , or ` move `
* ( structural ) animation , child elements that also have an active structural animation are not animated .
*
2016-12-17 07:24:00 +01:00
* Note that even if ` ngAnimateChildren ` is set , no child animations will run when the parent element is removed from the DOM ( ` leave ` animation ) .
2016-03-17 07:28:59 +01:00
*
*
* @ param { string } ngAnimateChildren If the value is empty , ` true ` or ` on ` ,
* then child animations are allowed . If the value is ` false ` , child animations are not allowed .
*
* @ example
* < example module = "ngAnimateChildren" name = "ngAnimateChildren" deps = "angular-animate.js" animations = "true" >
< file name = "index.html" >
2016-11-25 08:43:18 +01:00
< div ng - controller = "MainController as main" >
2016-03-17 07:28:59 +01:00
< label > Show container ? < input type = "checkbox" ng - model = "main.enterElement" / > < / l a b e l >
< label > Animate children ? < input type = "checkbox" ng - model = "main.animateChildren" / > < / l a b e l >
< hr >
< div ng - animate - children = "{{main.animateChildren}}" >
< div ng - if = "main.enterElement" class = "container" >
List of items :
< div ng - repeat = "item in [0, 1, 2, 3]" class = "item" > Item { { item } } < / d i v >
< / d i v >
< / d i v >
< / d i v >
< / f i l e >
< file name = "animations.css" >
. container . ng - enter ,
. container . ng - leave {
transition : all ease 1.5 s ;
}
. container . ng - enter ,
. container . ng - leave - active {
opacity : 0 ;
}
. container . ng - leave ,
. container . ng - enter - active {
opacity : 1 ;
}
. item {
background : firebrick ;
color : # FFF ;
margin - bottom : 10 px ;
}
. item . ng - enter ,
. item . ng - leave {
transition : transform 1.5 s ease ;
}
. item . ng - enter {
transform : translateX ( 50 px ) ;
}
. item . ng - enter - active {
transform : translateX ( 0 ) ;
}
< / f i l e >
< file name = "script.js" >
angular . module ( 'ngAnimateChildren' , [ 'ngAnimate' ] )
2016-11-25 08:43:18 +01:00
. controller ( 'MainController' , function MainController ( ) {
2016-03-17 07:28:59 +01:00
this . animateChildren = false ;
this . enterElement = false ;
} ) ;
< / f i l e >
< / e x a m p l e >
* /
var $$AnimateChildrenDirective = [ '$interpolate' , function ( $interpolate ) {
return {
link : function ( scope , element , attrs ) {
var val = attrs . ngAnimateChildren ;
2016-10-12 08:08:16 +02:00
if ( isString ( val ) && val . length === 0 ) { //empty attribute
2016-03-17 07:28:59 +01:00
element . data ( NG _ANIMATE _CHILDREN _DATA , true ) ;
} else {
// Interpolate and set the value, so that it is available to
// animations that run right after compilation
setData ( $interpolate ( val ) ( scope ) ) ;
attrs . $observe ( 'ngAnimateChildren' , setData ) ;
}
function setData ( value ) {
2015-08-25 17:46:14 +02:00
value = value === 'on' || value === 'true' ;
element . data ( NG _ANIMATE _CHILDREN _DATA , value ) ;
2016-03-17 07:28:59 +01:00
}
2015-08-25 17:46:14 +02:00
}
} ;
} ] ;
2015-08-07 16:37:36 +02:00
2016-11-25 08:43:18 +01:00
/* exported $AnimateCssProvider */
2015-08-31 17:08:41 +02:00
var ANIMATE _TIMER _KEY = '$$animateCss' ;
2015-08-07 16:37:36 +02:00
/ * *
2015-08-25 17:46:14 +02:00
* @ ngdoc service
* @ name $animateCss
* @ kind object
2015-08-07 16:37:36 +02:00
*
2015-08-25 17:46:14 +02:00
* @ description
* The ` $ animateCss ` service is a useful utility to trigger customized CSS - based transitions / keyframes
* from a JavaScript - based animation or directly from a directive . The purpose of ` $ animateCss ` is NOT
* to side - step how ` $ animate ` and ngAnimate work , but the goal is to allow pre - existing animations or
* directives to create more complex animations that can be purely driven using CSS code .
2015-08-07 16:37:36 +02:00
*
2015-08-25 17:46:14 +02:00
* Note that only browsers that support CSS transitions and / or keyframe animations are capable of
* rendering animations triggered via ` $ animateCss ` ( bad news for IE9 and lower ) .
2015-08-07 16:37:36 +02:00
*
2015-08-25 17:46:14 +02:00
* # # Usage
* Once again , ` $ animateCss ` is designed to be used inside of a registered JavaScript animation that
* is powered by ngAnimate . It is possible to use ` $ animateCss ` directly inside of a directive , however ,
* any automatic control over cancelling animations and / or preventing animations from being run on
* child elements will not be handled by Angular . For this to work as expected , please use ` $ animate ` to
* trigger the animation and then setup a JavaScript animation that injects ` $ animateCss ` to trigger
* the CSS animation .
2015-08-07 16:37:36 +02:00
*
2015-08-25 17:46:14 +02:00
* The example below shows how we can create a folding animation on an element using ` ng-if ` :
2015-08-07 16:37:36 +02:00
*
* ` ` ` html
2015-08-25 17:46:14 +02:00
* <!-- notice the ` fold-animation ` CSS class -- >
* < div ng - if = "onOff" class = "fold-animation" >
* This element will go BOOM
2015-08-07 16:37:36 +02:00
* < / d i v >
2015-08-25 17:46:14 +02:00
* < button ng - click = "onOff=true" > Fold In < / b u t t o n >
2015-08-07 16:37:36 +02:00
* ` ` `
*
2015-08-25 17:46:14 +02:00
* Now we create the * * JavaScript animation * * that will trigger the CSS transition :
2015-08-07 16:37:36 +02:00
*
2015-08-25 17:46:14 +02:00
* ` ` ` js
* ngModule . animation ( '.fold-animation' , [ '$animateCss' , function ( $animateCss ) {
* return {
* enter : function ( element , doneFn ) {
* var height = element [ 0 ] . offsetHeight ;
* return $animateCss ( element , {
* from : { height : '0px' } ,
* to : { height : height + 'px' } ,
* duration : 1 // one second
* } ) ;
* }
* }
* } ] ) ;
* ` ` `
2015-08-07 16:37:36 +02:00
*
2015-08-25 17:46:14 +02:00
* # # More Advanced Uses
2015-08-07 16:37:36 +02:00
*
2015-08-25 17:46:14 +02:00
* ` $ animateCss ` is the underlying code that ngAnimate uses to power * * CSS - based animations * * behind the scenes . Therefore CSS hooks
* like ` .ng-EVENT ` , ` .ng-EVENT-active ` , ` .ng-EVENT-stagger ` are all features that can be triggered using ` $ animateCss ` via JavaScript code .
2015-08-07 16:37:36 +02:00
*
2015-08-25 17:46:14 +02:00
* This also means that just about any combination of adding classes , removing classes , setting styles , dynamically setting a keyframe animation ,
* applying a hardcoded duration or delay value , changing the animation easing or applying a stagger animation are all options that work with
* ` $ animateCss ` . The service itself is smart enough to figure out the combination of options and examine the element styling properties in order
* to provide a working animation that will run in CSS .
2015-08-07 16:37:36 +02:00
*
2015-08-25 17:46:14 +02:00
* The example below showcases a more advanced version of the ` .fold-animation ` from the example above :
2015-08-07 16:37:36 +02:00
*
2015-08-25 17:46:14 +02:00
* ` ` ` js
* ngModule . animation ( '.fold-animation' , [ '$animateCss' , function ( $animateCss ) {
* return {
* enter : function ( element , doneFn ) {
* var height = element [ 0 ] . offsetHeight ;
* return $animateCss ( element , {
* addClass : 'red large-text pulse-twice' ,
* easing : 'ease-out' ,
* from : { height : '0px' } ,
* to : { height : height + 'px' } ,
* duration : 1 // one second
* } ) ;
* }
* }
* } ] ) ;
2015-08-07 16:37:36 +02:00
* ` ` `
*
2015-08-25 17:46:14 +02:00
* Since we ' re adding / removing CSS classes then the CSS transition will also pick those up :
2015-08-07 16:37:36 +02:00
*
* ` ` ` css
2015-08-25 17:46:14 +02:00
* / & # 4 2 ; s i n c e a h a r d c o d e d d u r a t i o n v a l u e o f 1 w a s p r o v i d e d i n t h e J a v a S c r i p t a n i m a t i o n c o d e ,
* the CSS classes below will be transitioned despite them being defined as regular CSS classes & # 42 ; /
* . red { background : red ; }
* . large - text { font - size : 20 px ; }
*
* /* we can also use a keyframe animation and $animateCss will make it work alongside the transition */
* . pulse - twice {
* animation : 0.5 s pulse linear 2 ;
* - webkit - animation : 0.5 s pulse linear 2 ;
2015-08-07 16:37:36 +02:00
* }
*
2015-08-25 17:46:14 +02:00
* @ keyframes pulse {
* from { transform : scale ( 0.5 ) ; }
* to { transform : scale ( 1.5 ) ; }
* }
2015-08-07 16:37:36 +02:00
*
2015-08-25 17:46:14 +02:00
* @ - webkit - keyframes pulse {
* from { - webkit - transform : scale ( 0.5 ) ; }
* to { - webkit - transform : scale ( 1.5 ) ; }
2015-08-07 16:37:36 +02:00
* }
* ` ` `
*
2015-08-25 17:46:14 +02:00
* Given this complex combination of CSS classes , styles and options , ` $ animateCss ` will figure everything out and make the animation happen .
2015-08-07 16:37:36 +02:00
*
2015-08-25 17:46:14 +02:00
* # # How the Options are handled
2015-08-07 16:37:36 +02:00
*
2015-08-25 17:46:14 +02:00
* ` $ animateCss ` is very versatile and intelligent when it comes to figuring out what configurations to apply to the element to ensure the animation
* works with the options provided . Say for example we were adding a class that contained a keyframe value and we wanted to also animate some inline
* styles using the ` from ` and ` to ` properties .
2015-08-07 16:37:36 +02:00
*
* ` ` ` js
2015-08-25 17:46:14 +02:00
* var animator = $animateCss ( element , {
* from : { background : 'red' } ,
* to : { background : 'blue' }
* } ) ;
* animator . start ( ) ;
2015-08-07 16:37:36 +02:00
* ` ` `
*
* ` ` ` css
2015-08-25 17:46:14 +02:00
* . rotating - animation {
* animation : 0.5 s rotate linear ;
* - webkit - animation : 0.5 s rotate linear ;
2015-08-07 16:37:36 +02:00
* }
*
2015-08-25 17:46:14 +02:00
* @ keyframes rotate {
* from { transform : rotate ( 0 deg ) ; }
* to { transform : rotate ( 360 deg ) ; }
2015-08-07 16:37:36 +02:00
* }
2015-08-25 17:46:14 +02:00
*
* @ - webkit - keyframes rotate {
* from { - webkit - transform : rotate ( 0 deg ) ; }
* to { - webkit - transform : rotate ( 360 deg ) ; }
2015-08-07 16:37:36 +02:00
* }
* ` ` `
*
2015-08-25 17:46:14 +02:00
* The missing pieces here are that we do not have a transition set ( within the CSS code nor within the ` $ animateCss ` options ) and the duration of the animation is
* going to be detected from what the keyframe styles on the CSS class are . In this event , ` $ animateCss ` will automatically create an inline transition
* style matching the duration detected from the keyframe style ( which is present in the CSS class that is being added ) and then prepare both the transition
* and keyframe animations to run in parallel on the element . Then when the animation is underway the provided ` from ` and ` to ` CSS styles will be applied
* and spread across the transition and keyframe animation .
2015-08-07 16:37:36 +02:00
*
2015-08-25 17:46:14 +02:00
* # # What is returned
2015-08-07 16:37:36 +02:00
*
2015-08-25 17:46:14 +02:00
* ` $ animateCss ` works in two stages : a preparation phase and an animation phase . Therefore when ` $ animateCss ` is first called it will NOT actually
* start the animation . All that is going on here is that the element is being prepared for the animation ( which means that the generated CSS classes are
* added and removed on the element ) . Once ` $ animateCss ` is called it will return an object with the following properties :
2015-08-07 16:37:36 +02:00
*
* ` ` ` js
2015-08-25 17:46:14 +02:00
* var animator = $animateCss ( element , { ... } ) ;
2015-08-07 16:37:36 +02:00
* ` ` `
*
2015-08-25 17:46:14 +02:00
* Now what do the contents of our ` animator ` variable look like :
2015-08-07 16:37:36 +02:00
*
* ` ` ` js
2015-08-25 17:46:14 +02:00
* {
* // starts the animation
* start : Function ,
2015-08-07 16:37:36 +02:00
*
2015-08-25 17:46:14 +02:00
* // ends (aborts) the animation
* end : Function
* }
2015-08-07 16:37:36 +02:00
* ` ` `
*
2015-08-25 17:46:14 +02:00
* 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 .
2016-10-12 08:08:16 +02:00
* 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
2015-08-25 17:46:14 +02:00
* applied to the element during the preparation phase ) . Note that all other properties such as duration , delay , transitions and keyframes are just properties
* and that changing them will not reconfigure the parameters of the animation .
*
* # # # runner . done ( ) vs runner . then ( )
* It is documented that ` animation.start() ` will return a promise object and this is true , however , there is also an additional method available on the
* runner called ` .done(callbackFn) ` . The done method works the same as ` .finally(callbackFn) ` , however , it does * * not trigger a digest to occur * * .
* Therefore , for performance reasons , it ' s always best to use ` runner.done(callback) ` instead of ` runner.then() ` , ` runner.catch() ` or ` runner.finally() `
* unless you really need a digest to kick off afterwards .
*
* Keep in mind that , to make this easier , ngAnimate has tweaked the JS animations API to recognize when a runner instance is returned from $animateCss
* ( so there is no need to call ` runner.done(doneFn) ` inside of your JavaScript animation code ) .
* Check the { @ link ngAnimate . $animateCss # usage animation code above } to see how this works .
*
* @ param { DOMElement } element the element that will be animated
* @ param { object } options the animation - related options that will be applied during the animation
*
* * ` event ` - The DOM event ( e . g . enter , leave , move ) . When used , a generated CSS class of ` ng-EVENT ` and ` ng-EVENT-active ` will be applied
* to the element during the animation . Multiple events can be provided when spaces are used as a separator . ( Note that this will not perform any DOM operation . )
2015-12-04 15:07:07 +01:00
* * ` structural ` - Indicates that the ` ng- ` prefix will be added to the event class . Setting to ` false ` or omitting will turn ` ng-EVENT ` and
* ` ng-EVENT-active ` in ` EVENT ` and ` EVENT-active ` . Unused if ` event ` is omitted .
2015-08-25 17:46:14 +02:00
* * ` easing ` - The CSS easing value that will be applied to the transition or keyframe animation ( or both ) .
2015-10-13 22:05:39 +02:00
* * ` transitionStyle ` - The raw CSS transition style that will be used ( e . g . ` 1s linear all ` ) .
2015-08-25 17:46:14 +02:00
* * ` keyframeStyle ` - The raw CSS keyframe animation style that will be used ( e . g . ` 1s my_animation linear ` ) .
* * ` from ` - The starting CSS styles ( a key / value object ) that will be applied at the start of the animation .
* * ` to ` - The ending CSS styles ( a key / value object ) that will be applied across the animation via a CSS transition .
* * ` addClass ` - A space separated list of CSS classes that will be added to the element and spread across the animation .
* * ` removeClass ` - A space separated list of CSS classes that will be removed from the element and spread across the animation .
* * ` duration ` - A number value representing the total duration of the transition and / or keyframe ( note that a value of 1 is 1000 ms ) . If a value of ` 0 `
* is provided then the animation will be skipped entirely .
* * ` delay ` - A number value representing the total delay of the transition and / or keyframe ( note that a value of 1 is 1000 ms ) . If a value of ` true ` is
* used then whatever delay value is detected from the CSS classes will be mirrored on the elements styles ( e . g . by setting delay true then the style value
* of the element will be ` transition-delay: DETECTED_VALUE ` ) . Using ` true ` is useful when you want the CSS classes and inline styles to all share the same
* CSS delay value .
* * ` stagger ` - A numeric time value representing the delay between successively animated elements
* ( { @ link ngAnimate # css - staggering - animations Click here to learn how CSS - based staggering works in ngAnimate . } )
* * ` staggerIndex ` - The numeric index representing the stagger item ( e . g . a value of 5 is equal to the sixth item in the stagger ; therefore when a
2016-10-12 08:08:16 +02:00
* ` stagger ` option value of ` 0.1 ` is used then there will be a stagger delay of ` 600ms ` )
* * ` applyClassesEarly ` - Whether or not the classes being added or removed will be used when detecting the animation . This is set by ` $ animate ` when enter / leave / move animations are fired to ensure that the CSS classes are resolved in time . ( Note that this will prevent any transitions from occurring on the classes being added and removed . )
2015-10-13 22:05:39 +02:00
* * ` cleanupStyles ` - Whether or not the provided ` from ` and ` to ` styles will be removed once
* the animation is closed . This is useful for when the styles are used purely for the sake of
2016-10-12 08:08:16 +02:00
* the animation and do not have a lasting visual effect on the element ( e . g . a collapse and open animation ) .
2015-10-13 22:05:39 +02:00
* By default this value is set to ` false ` .
2015-08-25 17:46:14 +02:00
*
* @ return { object } an object with start and end methods and details about the animation .
*
* * ` start ` - The method to start the animation . This will return a ` Promise ` when called .
* * ` end ` - This method will cancel the animation and remove all applied CSS classes and styles .
2015-08-07 16:37:36 +02:00
* /
2015-08-25 17:46:14 +02:00
var ONE _SECOND = 1000 ;
var ELAPSED _TIME _MAX _DECIMAL _PLACES = 3 ;
var CLOSING _TIME _BUFFER = 1.5 ;
var DETECT _CSS _PROPERTIES = {
transitionDuration : TRANSITION _DURATION _PROP ,
transitionDelay : TRANSITION _DELAY _PROP ,
transitionProperty : TRANSITION _PROP + PROPERTY _KEY ,
animationDuration : ANIMATION _DURATION _PROP ,
animationDelay : ANIMATION _DELAY _PROP ,
animationIterationCount : ANIMATION _PROP + ANIMATION _ITERATION _COUNT _KEY
} ;
var DETECT _STAGGER _CSS _PROPERTIES = {
transitionDuration : TRANSITION _DURATION _PROP ,
transitionDelay : TRANSITION _DELAY _PROP ,
animationDuration : ANIMATION _DURATION _PROP ,
animationDelay : ANIMATION _DELAY _PROP
} ;
function getCssKeyframeDurationStyle ( duration ) {
return [ ANIMATION _DURATION _PROP , duration + 's' ] ;
}
function getCssDelayStyle ( delay , isKeyframeAnimation ) {
var prop = isKeyframeAnimation ? ANIMATION _DELAY _PROP : TRANSITION _DELAY _PROP ;
return [ prop , delay + 's' ] ;
}
function computeCssStyles ( $window , element , properties ) {
var styles = Object . create ( null ) ;
var detectedStyles = $window . getComputedStyle ( element ) || { } ;
forEach ( properties , function ( formalStyleName , actualStyleName ) {
var val = detectedStyles [ formalStyleName ] ;
if ( val ) {
var c = val . charAt ( 0 ) ;
// only numerical-based values have a negative sign or digit as the first value
if ( c === '-' || c === '+' || c >= 0 ) {
val = parseMaxTime ( val ) ;
}
2015-08-07 16:37:36 +02:00
2015-08-25 17:46:14 +02:00
// by setting this to null in the event that the delay is not set or is set directly as 0
2016-10-12 08:08:16 +02:00
// then we can still allow for negative values to be used later on and not mistake this
2015-08-25 17:46:14 +02:00
// value for being greater than any other negative value.
if ( val === 0 ) {
val = null ;
2015-08-07 16:37:36 +02:00
}
2015-08-25 17:46:14 +02:00
styles [ actualStyleName ] = val ;
}
} ) ;
return styles ;
}
function parseMaxTime ( str ) {
var maxValue = 0 ;
var values = str . split ( /\s*,\s*/ ) ;
forEach ( values , function ( value ) {
// it's always safe to consider only second values and omit `ms` values since
// getComputedStyle will always handle the conversion for us
2016-11-25 08:43:18 +01:00
if ( value . charAt ( value . length - 1 ) === 's' ) {
2015-08-25 17:46:14 +02:00
value = value . substring ( 0 , value . length - 1 ) ;
}
value = parseFloat ( value ) || 0 ;
maxValue = maxValue ? Math . max ( value , maxValue ) : value ;
} ) ;
return maxValue ;
}
function truthyTimingValue ( val ) {
return val === 0 || val != null ;
}
function getCssTransitionDurationStyle ( duration , applyOnlyDuration ) {
var style = TRANSITION _PROP ;
var value = duration + 's' ;
if ( applyOnlyDuration ) {
style += DURATION _KEY ;
} else {
value += ' linear all' ;
}
return [ style , value ] ;
}
function createLocalCacheLookup ( ) {
var cache = Object . create ( null ) ;
return {
flush : function ( ) {
cache = Object . create ( null ) ;
} ,
count : function ( key ) {
var entry = cache [ key ] ;
return entry ? entry . total : 0 ;
} ,
get : function ( key ) {
var entry = cache [ key ] ;
return entry && entry . value ;
} ,
put : function ( key , value ) {
if ( ! cache [ key ] ) {
cache [ key ] = { total : 1 , value : value } ;
} else {
cache [ key ] . total ++ ;
2015-08-07 16:37:36 +02:00
}
}
2015-08-25 17:46:14 +02:00
} ;
}
2015-08-07 16:37:36 +02:00
2015-10-13 22:05:39 +02:00
// we do not reassign an already present style value since
// if we detect the style property value again we may be
// detecting styles that were added via the `from` styles.
// We make use of `isDefined` here since an empty string
// or null value (which is what getPropertyValue will return
// for a non-existing style) will still be marked as a valid
// value for the style (a falsy value implies that the style
// is to be removed at the end of the animation). If we had a simple
// "OR" statement then it would not be enough to catch that.
function registerRestorableStyles ( backup , node , properties ) {
forEach ( properties , function ( prop ) {
backup [ prop ] = isDefined ( backup [ prop ] )
? backup [ prop ]
: node . style . getPropertyValue ( prop ) ;
} ) ;
}
2016-11-25 08:43:18 +01:00
var $AnimateCssProvider = [ '$animateProvider' , /** @this */ function ( $animateProvider ) {
2015-08-25 17:46:14 +02:00
var gcsLookup = createLocalCacheLookup ( ) ;
var gcsStaggerLookup = createLocalCacheLookup ( ) ;
2015-08-07 16:37:36 +02:00
2015-08-31 17:08:41 +02:00
this . $get = [ '$window' , '$$jqLite' , '$$AnimateRunner' , '$timeout' ,
2016-02-03 19:34:41 +01:00
'$$forceReflow' , '$sniffer' , '$$rAFScheduler' , '$$animateQueue' ,
2015-08-31 17:08:41 +02:00
function ( $window , $$jqLite , $$AnimateRunner , $timeout ,
2016-02-03 19:34:41 +01:00
$$forceReflow , $sniffer , $$rAFScheduler , $$animateQueue ) {
2015-08-25 17:46:14 +02:00
var applyAnimationClasses = applyAnimationClassesFactory ( $$jqLite ) ;
2015-08-07 16:37:36 +02:00
2015-08-25 17:46:14 +02:00
var parentCounter = 0 ;
function gcsHashFn ( node , extraClasses ) {
2016-11-25 08:43:18 +01:00
var KEY = '$$ngAnimateParentKey' ;
2015-08-25 17:46:14 +02:00
var parentNode = node . parentNode ;
var parentID = parentNode [ KEY ] || ( parentNode [ KEY ] = ++ parentCounter ) ;
return parentID + '-' + node . getAttribute ( 'class' ) + '-' + extraClasses ;
2015-08-07 16:37:36 +02:00
}
2015-08-25 17:46:14 +02:00
function computeCachedCssStyles ( node , className , cacheKey , properties ) {
var timings = gcsLookup . get ( cacheKey ) ;
2015-08-07 16:37:36 +02:00
2015-08-25 17:46:14 +02:00
if ( ! timings ) {
timings = computeCssStyles ( $window , node , properties ) ;
if ( timings . animationIterationCount === 'infinite' ) {
timings . animationIterationCount = 1 ;
2015-08-07 16:37:36 +02:00
}
}
2015-08-25 17:46:14 +02:00
// we keep putting this in multiple times even though the value and the cacheKey are the same
2016-10-12 08:08:16 +02:00
// because we're keeping an internal tally of how many duplicate animations are detected.
2015-08-25 17:46:14 +02:00
gcsLookup . put ( cacheKey , timings ) ;
return timings ;
}
2015-08-07 16:37:36 +02:00
2015-08-25 17:46:14 +02:00
function computeCachedCssStaggerStyles ( node , className , cacheKey , properties ) {
var stagger ;
2015-08-07 16:37:36 +02:00
2015-08-25 17:46:14 +02:00
// if we have one or more existing matches of matching elements
// containing the same parent + CSS styles (which is how cacheKey works)
// then staggering is possible
if ( gcsLookup . count ( cacheKey ) > 0 ) {
stagger = gcsStaggerLookup . get ( cacheKey ) ;
2015-08-07 16:37:36 +02:00
2015-08-25 17:46:14 +02:00
if ( ! stagger ) {
var staggerClassName = pendClasses ( className , '-stagger' ) ;
2015-08-07 16:37:36 +02:00
2015-08-25 17:46:14 +02:00
$$jqLite . addClass ( node , staggerClassName ) ;
2015-08-07 16:37:36 +02:00
2015-08-25 17:46:14 +02:00
stagger = computeCssStyles ( $window , node , properties ) ;
2015-08-07 16:37:36 +02:00
2015-08-25 17:46:14 +02:00
// force the conversion of a null value to zero incase not set
stagger . animationDuration = Math . max ( stagger . animationDuration , 0 ) ;
stagger . transitionDuration = Math . max ( stagger . transitionDuration , 0 ) ;
2015-08-07 16:37:36 +02:00
2015-08-25 17:46:14 +02:00
$$jqLite . removeClass ( node , staggerClassName ) ;
gcsStaggerLookup . put ( cacheKey , stagger ) ;
2015-08-07 16:37:36 +02:00
}
}
2015-08-25 17:46:14 +02:00
return stagger || { } ;
}
2015-08-07 16:37:36 +02:00
2015-08-25 17:46:14 +02:00
var rafWaitQueue = [ ] ;
function waitUntilQuiet ( callback ) {
rafWaitQueue . push ( callback ) ;
2015-08-31 17:08:41 +02:00
$$rAFScheduler . waitUntilQuiet ( function ( ) {
2015-08-25 17:46:14 +02:00
gcsLookup . flush ( ) ;
gcsStaggerLookup . flush ( ) ;
// DO NOT REMOVE THIS LINE OR REFACTOR OUT THE `pageWidth` variable.
// PLEASE EXAMINE THE `$$forceReflow` service to understand why.
var pageWidth = $$forceReflow ( ) ;
// we use a for loop to ensure that if the queue is changed
// during this looping then it will consider new requests
for ( var i = 0 ; i < rafWaitQueue . length ; i ++ ) {
rafWaitQueue [ i ] ( pageWidth ) ;
2015-08-07 16:37:36 +02:00
}
2015-08-25 17:46:14 +02:00
rafWaitQueue . length = 0 ;
} ) ;
}
2015-08-07 16:37:36 +02:00
2015-08-25 17:46:14 +02:00
function computeTimings ( node , className , cacheKey ) {
var timings = computeCachedCssStyles ( node , className , cacheKey , DETECT _CSS _PROPERTIES ) ;
var aD = timings . animationDelay ;
var tD = timings . transitionDelay ;
timings . maxDelay = aD && tD
? Math . max ( aD , tD )
: ( aD || tD ) ;
timings . maxDuration = Math . max (
timings . animationDuration * timings . animationIterationCount ,
timings . transitionDuration ) ;
2015-08-07 16:37:36 +02:00
2015-08-25 17:46:14 +02:00
return timings ;
}
2015-08-07 16:37:36 +02:00
2016-02-03 19:34:41 +01:00
return function init ( element , initialOptions ) {
// all of the animation functions should create
// a copy of the options data, however, if a
// parent service has already created a copy then
// we should stick to using that
var options = initialOptions || { } ;
if ( ! options . $$prepared ) {
options = prepareAnimationOptions ( copy ( options ) ) ;
}
2015-10-13 22:05:39 +02:00
var restoreStyles = { } ;
2015-08-25 17:46:14 +02:00
var node = getDomNode ( element ) ;
2015-08-31 17:08:41 +02:00
if ( ! node
|| ! node . parentNode
2016-02-03 19:34:41 +01:00
|| ! $$animateQueue . enabled ( ) ) {
2015-08-25 17:46:14 +02:00
return closeAndReturnNoopAnimator ( ) ;
}
2015-08-07 16:37:36 +02:00
2015-08-25 17:46:14 +02:00
var temporaryStyles = [ ] ;
var classes = element . attr ( 'class' ) ;
var styles = packageStyles ( options ) ;
var animationClosed ;
var animationPaused ;
var animationCompleted ;
var runner ;
var runnerHost ;
var maxDelay ;
var maxDelayTime ;
var maxDuration ;
var maxDurationTime ;
2016-02-03 19:34:41 +01:00
var startTime ;
var events = [ ] ;
2015-08-25 17:46:14 +02:00
if ( options . duration === 0 || ( ! $sniffer . animations && ! $sniffer . transitions ) ) {
return closeAndReturnNoopAnimator ( ) ;
}
2015-08-07 16:37:36 +02:00
2015-08-25 17:46:14 +02:00
var method = options . event && isArray ( options . event )
? options . event . join ( ' ' )
: options . event ;
2015-08-07 16:37:36 +02:00
2015-08-25 17:46:14 +02:00
var isStructural = method && options . structural ;
var structuralClassName = '' ;
var addRemoveClassName = '' ;
2015-08-07 16:37:36 +02:00
2015-08-25 17:46:14 +02:00
if ( isStructural ) {
structuralClassName = pendClasses ( method , EVENT _CLASS _PREFIX , true ) ;
} else if ( method ) {
structuralClassName = method ;
}
2015-08-07 16:37:36 +02:00
2015-08-25 17:46:14 +02:00
if ( options . addClass ) {
addRemoveClassName += pendClasses ( options . addClass , ADD _CLASS _SUFFIX ) ;
}
if ( options . removeClass ) {
if ( addRemoveClassName . length ) {
addRemoveClassName += ' ' ;
2015-08-07 16:37:36 +02:00
}
2015-08-25 17:46:14 +02:00
addRemoveClassName += pendClasses ( options . removeClass , REMOVE _CLASS _SUFFIX ) ;
}
2015-08-07 16:37:36 +02:00
2015-08-25 17:46:14 +02:00
// there may be a situation where a structural animation is combined together
// with CSS classes that need to resolve before the animation is computed.
// However this means that there is no explicit CSS code to block the animation
// from happening (by setting 0s none in the class name). If this is the case
// we need to apply the classes before the first rAF so we know to continue if
// there actually is a detected transition or keyframe animation
if ( options . applyClassesEarly && addRemoveClassName . length ) {
applyAnimationClasses ( element , options ) ;
2015-08-07 16:37:36 +02:00
}
2015-08-25 17:46:14 +02:00
var preparationClasses = [ structuralClassName , addRemoveClassName ] . join ( ' ' ) . trim ( ) ;
var fullClassName = classes + ' ' + preparationClasses ;
var activeClasses = pendClasses ( preparationClasses , ACTIVE _CLASS _SUFFIX ) ;
var hasToStyles = styles . to && Object . keys ( styles . to ) . length > 0 ;
var containsKeyframeAnimation = ( options . keyframeStyle || '' ) . length > 0 ;
// there is no way we can trigger an animation if no styles and
// no classes are being applied which would then trigger a transition,
// unless there a is raw keyframe value that is applied to the element.
if ( ! containsKeyframeAnimation
&& ! hasToStyles
&& ! preparationClasses ) {
return closeAndReturnNoopAnimator ( ) ;
}
2015-08-07 16:37:36 +02:00
2015-08-25 17:46:14 +02:00
var cacheKey , stagger ;
if ( options . stagger > 0 ) {
var staggerVal = parseFloat ( options . stagger ) ;
stagger = {
transitionDelay : staggerVal ,
animationDelay : staggerVal ,
transitionDuration : 0 ,
animationDuration : 0
} ;
} else {
cacheKey = gcsHashFn ( node , fullClassName ) ;
stagger = computeCachedCssStaggerStyles ( node , preparationClasses , cacheKey , DETECT _STAGGER _CSS _PROPERTIES ) ;
}
2015-08-07 16:37:36 +02:00
2015-08-25 17:46:14 +02:00
if ( ! options . $$skipPreparationClasses ) {
$$jqLite . addClass ( element , preparationClasses ) ;
}
2015-08-07 16:37:36 +02:00
2015-08-25 17:46:14 +02:00
var applyOnlyDuration ;
2015-08-07 16:37:36 +02:00
2015-08-25 17:46:14 +02:00
if ( options . transitionStyle ) {
var transitionStyle = [ TRANSITION _PROP , options . transitionStyle ] ;
applyInlineStyle ( node , transitionStyle ) ;
temporaryStyles . push ( transitionStyle ) ;
}
2015-08-07 16:37:36 +02:00
2015-08-25 17:46:14 +02:00
if ( options . duration >= 0 ) {
applyOnlyDuration = node . style [ TRANSITION _PROP ] . length > 0 ;
var durationStyle = getCssTransitionDurationStyle ( options . duration , applyOnlyDuration ) ;
2015-08-07 16:37:36 +02:00
2015-08-25 17:46:14 +02:00
// we set the duration so that it will be picked up by getComputedStyle later
applyInlineStyle ( node , durationStyle ) ;
temporaryStyles . push ( durationStyle ) ;
}
2015-08-21 19:40:07 +02:00
2015-08-25 17:46:14 +02:00
if ( options . keyframeStyle ) {
var keyframeStyle = [ ANIMATION _PROP , options . keyframeStyle ] ;
applyInlineStyle ( node , keyframeStyle ) ;
temporaryStyles . push ( keyframeStyle ) ;
}
2015-08-21 19:40:07 +02:00
2015-08-25 17:46:14 +02:00
var itemIndex = stagger
? options . staggerIndex >= 0
? options . staggerIndex
: gcsLookup . count ( cacheKey )
: 0 ;
var isFirst = itemIndex === 0 ;
// this is a pre-emptive way of forcing the setup classes to be added and applied INSTANTLY
// without causing any combination of transitions to kick in. By adding a negative delay value
// it forces the setup class' transition to end immediately. We later then remove the negative
// transition delay to allow for the transition to naturally do it's thing. The beauty here is
// that if there is no transition defined then nothing will happen and this will also allow
// other transitions to be stacked on top of each other without any chopping them out.
if ( isFirst && ! options . skipBlocking ) {
blockTransitions ( node , SAFE _FAST _FORWARD _DURATION _VALUE ) ;
}
2015-08-07 16:37:36 +02:00
2015-08-25 17:46:14 +02:00
var timings = computeTimings ( node , fullClassName , cacheKey ) ;
var relativeDelay = timings . maxDelay ;
maxDelay = Math . max ( relativeDelay , 0 ) ;
maxDuration = timings . maxDuration ;
var flags = { } ;
flags . hasTransitions = timings . transitionDuration > 0 ;
flags . hasAnimations = timings . animationDuration > 0 ;
2016-11-25 08:43:18 +01:00
flags . hasTransitionAll = flags . hasTransitions && timings . transitionProperty === 'all' ;
2015-08-25 17:46:14 +02:00
flags . applyTransitionDuration = hasToStyles && (
( flags . hasTransitions && ! flags . hasTransitionAll )
|| ( flags . hasAnimations && ! flags . hasTransitions ) ) ;
flags . applyAnimationDuration = options . duration && flags . hasAnimations ;
flags . applyTransitionDelay = truthyTimingValue ( options . delay ) && ( flags . applyTransitionDuration || flags . hasTransitions ) ;
flags . applyAnimationDelay = truthyTimingValue ( options . delay ) && flags . hasAnimations ;
flags . recalculateTimingStyles = addRemoveClassName . length > 0 ;
if ( flags . applyTransitionDuration || flags . applyAnimationDuration ) {
maxDuration = options . duration ? parseFloat ( options . duration ) : maxDuration ;
if ( flags . applyTransitionDuration ) {
flags . hasTransitions = true ;
timings . transitionDuration = maxDuration ;
applyOnlyDuration = node . style [ TRANSITION _PROP + PROPERTY _KEY ] . length > 0 ;
temporaryStyles . push ( getCssTransitionDurationStyle ( maxDuration , applyOnlyDuration ) ) ;
}
2015-08-07 16:37:36 +02:00
2015-08-25 17:46:14 +02:00
if ( flags . applyAnimationDuration ) {
flags . hasAnimations = true ;
timings . animationDuration = maxDuration ;
temporaryStyles . push ( getCssKeyframeDurationStyle ( maxDuration ) ) ;
}
}
2015-08-07 16:37:36 +02:00
2015-08-25 17:46:14 +02:00
if ( maxDuration === 0 && ! flags . recalculateTimingStyles ) {
return closeAndReturnNoopAnimator ( ) ;
}
2015-08-07 16:37:36 +02:00
2015-08-31 17:08:41 +02:00
if ( options . delay != null ) {
2016-02-03 19:34:41 +01:00
var delayStyle ;
2016-11-25 08:43:18 +01:00
if ( typeof options . delay !== 'boolean' ) {
2016-02-03 19:34:41 +01:00
delayStyle = parseFloat ( options . delay ) ;
// number in options.delay means we have to recalculate the delay for the closing timeout
maxDelay = Math . max ( delayStyle , 0 ) ;
}
2015-08-31 17:08:41 +02:00
if ( flags . applyTransitionDelay ) {
temporaryStyles . push ( getCssDelayStyle ( delayStyle ) ) ;
}
if ( flags . applyAnimationDelay ) {
temporaryStyles . push ( getCssDelayStyle ( delayStyle , true ) ) ;
}
}
2015-08-25 17:46:14 +02:00
// we need to recalculate the delay value since we used a pre-emptive negative
// delay value and the delay value is required for the final event checking. This
// property will ensure that this will happen after the RAF phase has passed.
if ( options . duration == null && timings . transitionDuration > 0 ) {
flags . recalculateTimingStyles = flags . recalculateTimingStyles || isFirst ;
}
2015-08-07 16:37:36 +02:00
2015-08-25 17:46:14 +02:00
maxDelayTime = maxDelay * ONE _SECOND ;
maxDurationTime = maxDuration * ONE _SECOND ;
if ( ! options . skipBlocking ) {
flags . blockTransition = timings . transitionDuration > 0 ;
flags . blockKeyframeAnimation = timings . animationDuration > 0 &&
stagger . animationDelay > 0 &&
stagger . animationDuration === 0 ;
}
2015-08-07 16:37:36 +02:00
2015-10-13 22:05:39 +02:00
if ( options . from ) {
if ( options . cleanupStyles ) {
registerRestorableStyles ( restoreStyles , node , Object . keys ( options . from ) ) ;
}
applyAnimationFromStyles ( element , options ) ;
}
2015-08-07 16:37:36 +02:00
2015-08-25 17:46:14 +02:00
if ( flags . blockTransition || flags . blockKeyframeAnimation ) {
applyBlocking ( maxDuration ) ;
} else if ( ! options . skipBlocking ) {
blockTransitions ( node , false ) ;
}
2015-08-07 16:37:36 +02:00
2015-08-25 17:46:14 +02:00
// TODO(matsko): for 1.5 change this code to have an animator object for better debugging
return {
$$willAnimate : true ,
end : endFn ,
start : function ( ) {
if ( animationClosed ) return ;
runnerHost = {
end : endFn ,
cancel : cancelFn ,
resume : null , //this will be set during the start() phase
pause : null
} ;
2015-08-07 16:37:36 +02:00
2015-08-25 17:46:14 +02:00
runner = new $$AnimateRunner ( runnerHost ) ;
2015-08-07 16:37:36 +02:00
2015-08-25 17:46:14 +02:00
waitUntilQuiet ( start ) ;
2015-08-07 16:37:36 +02:00
2015-08-25 17:46:14 +02:00
// we don't have access to pause/resume the animation
// since it hasn't run yet. AnimateRunner will therefore
// set noop functions for resume and pause and they will
// later be overridden once the animation is triggered
return runner ;
2015-08-07 16:37:36 +02:00
}
2015-08-25 17:46:14 +02:00
} ;
2015-08-07 16:37:36 +02:00
2015-08-25 17:46:14 +02:00
function endFn ( ) {
close ( ) ;
}
2015-08-07 16:37:36 +02:00
2015-08-25 17:46:14 +02:00
function cancelFn ( ) {
close ( true ) ;
}
2015-08-07 16:37:36 +02:00
2016-11-25 08:43:18 +01:00
function close ( rejected ) {
2015-08-25 17:46:14 +02:00
// if the promise has been called already then we shouldn't close
// the animation again
if ( animationClosed || ( animationCompleted && animationPaused ) ) return ;
animationClosed = true ;
animationPaused = false ;
2015-08-07 16:37:36 +02:00
2015-08-25 17:46:14 +02:00
if ( ! options . $$skipPreparationClasses ) {
$$jqLite . removeClass ( element , preparationClasses ) ;
}
$$jqLite . removeClass ( element , activeClasses ) ;
2015-08-07 16:37:36 +02:00
2015-08-25 17:46:14 +02:00
blockKeyframeAnimations ( node , false ) ;
blockTransitions ( node , false ) ;
forEach ( temporaryStyles , function ( entry ) {
// There is only one way to remove inline style properties entirely from elements.
// By using `removeProperty` this works, but we need to convert camel-cased CSS
// styles down to hyphenated values.
node . style [ entry [ 0 ] ] = '' ;
2015-08-07 16:37:36 +02:00
} ) ;
2015-08-25 17:46:14 +02:00
applyAnimationClasses ( element , options ) ;
applyAnimationStyles ( element , options ) ;
2015-08-07 16:37:36 +02:00
2015-10-13 22:05:39 +02:00
if ( Object . keys ( restoreStyles ) . length ) {
forEach ( restoreStyles , function ( value , prop ) {
2016-11-25 08:43:18 +01:00
if ( value ) {
node . style . setProperty ( prop , value ) ;
} else {
node . style . removeProperty ( prop ) ;
}
2015-10-13 22:05:39 +02:00
} ) ;
}
2015-08-25 17:46:14 +02:00
// the reason why we have this option is to allow a synchronous closing callback
// that is fired as SOON as the animation ends (when the CSS is removed) or if
// the animation never takes off at all. A good example is a leave animation since
// the element must be removed just after the animation is over or else the element
// will appear on screen for one animation frame causing an overbearing flicker.
if ( options . onDone ) {
options . onDone ( ) ;
2015-08-07 16:37:36 +02:00
}
2016-02-03 19:34:41 +01:00
if ( events && events . length ) {
// Remove the transitionend / animationend listener(s)
element . off ( events . join ( ' ' ) , onAnimationProgress ) ;
}
2016-03-17 07:28:59 +01:00
//Cancel the fallback closing timeout and remove the timer data
var animationTimerData = element . data ( ANIMATE _TIMER _KEY ) ;
if ( animationTimerData ) {
$timeout . cancel ( animationTimerData [ 0 ] . timer ) ;
element . removeData ( ANIMATE _TIMER _KEY ) ;
}
2015-08-25 17:46:14 +02:00
// if the preparation function fails then the promise is not setup
if ( runner ) {
runner . complete ( ! rejected ) ;
2015-08-07 16:37:36 +02:00
}
2015-08-25 17:46:14 +02:00
}
2015-08-07 16:37:36 +02:00
2015-08-25 17:46:14 +02:00
function applyBlocking ( duration ) {
if ( flags . blockTransition ) {
blockTransitions ( node , duration ) ;
2015-08-07 16:37:36 +02:00
}
2015-08-25 17:46:14 +02:00
if ( flags . blockKeyframeAnimation ) {
blockKeyframeAnimations ( node , ! ! duration ) ;
2015-08-07 16:37:36 +02:00
}
2015-08-25 17:46:14 +02:00
}
2015-08-07 16:37:36 +02:00
2015-08-25 17:46:14 +02:00
function closeAndReturnNoopAnimator ( ) {
runner = new $$AnimateRunner ( {
end : endFn ,
cancel : cancelFn
} ) ;
2015-08-07 16:37:36 +02:00
2015-08-31 17:08:41 +02:00
// should flush the cache animation
waitUntilQuiet ( noop ) ;
2015-08-25 17:46:14 +02:00
close ( ) ;
2015-08-07 16:37:36 +02:00
2015-08-25 17:46:14 +02:00
return {
$$willAnimate : false ,
start : function ( ) {
return runner ;
} ,
end : endFn
} ;
}
2015-08-07 16:37:36 +02:00
2016-02-03 19:34:41 +01:00
function onAnimationProgress ( event ) {
event . stopPropagation ( ) ;
var ev = event . originalEvent || event ;
// we now always use `Date.now()` due to the recent changes with
// event.timeStamp in Firefox, Webkit and Chrome (see #13494 for more info)
var timeStamp = ev . $manualTimeStamp || Date . now ( ) ;
/ * F i r e f o x ( o r p o s s i b l y j u s t G e c k o ) l i k e s t o n o t r o u n d v a l u e s u p
* when a ms measurement is used for the animation * /
var elapsedTime = parseFloat ( ev . elapsedTime . toFixed ( ELAPSED _TIME _MAX _DECIMAL _PLACES ) ) ;
/ * $ m a n u a l T i m e S t a m p i s a m o c k e d t i m e S t a m p v a l u e w h i c h i s s e t
* within browserTrigger ( ) . This is only here so that tests can
* mock animations properly . Real events fallback to event . timeStamp ,
* or , if they don ' t , then a timeStamp is automatically created for them .
* We ' re checking to see if the timeStamp surpasses the expected delay ,
* but we ' re using elapsedTime instead of the timeStamp on the 2 nd
* pre - condition since animationPauseds sometimes close off early * /
if ( Math . max ( timeStamp - startTime , 0 ) >= maxDelayTime && elapsedTime >= maxDuration ) {
// we set this flag to ensure that if the transition is paused then, when resumed,
// the animation will automatically close itself since transitions cannot be paused.
animationCompleted = true ;
close ( ) ;
}
}
2015-08-25 17:46:14 +02:00
function start ( ) {
if ( animationClosed ) return ;
if ( ! node . parentNode ) {
close ( ) ;
return ;
}
2015-08-07 16:37:36 +02:00
2015-08-25 17:46:14 +02:00
// even though we only pause keyframe animations here the pause flag
// will still happen when transitions are used. Only the transition will
// not be paused since that is not possible. If the animation ends when
// paused then it will not complete until unpaused or cancelled.
var playPause = function ( playAnimation ) {
if ( ! animationCompleted ) {
animationPaused = ! playAnimation ;
if ( timings . animationDuration ) {
var value = blockKeyframeAnimations ( node , animationPaused ) ;
2016-11-25 08:43:18 +01:00
if ( animationPaused ) {
temporaryStyles . push ( value ) ;
} else {
removeFromArray ( temporaryStyles , value ) ;
}
2015-08-07 16:37:36 +02:00
}
2015-08-25 17:46:14 +02:00
} else if ( animationPaused && playAnimation ) {
animationPaused = false ;
close ( ) ;
2015-08-07 16:37:36 +02:00
}
2015-08-25 17:46:14 +02:00
} ;
2015-08-07 16:37:36 +02:00
2016-10-12 08:08:16 +02:00
// checking the stagger duration prevents an accidentally cascade of the CSS delay style
2015-08-25 17:46:14 +02:00
// being inherited from the parent. If the transition duration is zero then we can safely
2016-10-12 08:08:16 +02:00
// rely that the delay value is an intentional stagger delay style.
2015-08-25 17:46:14 +02:00
var maxStagger = itemIndex > 0
&& ( ( timings . transitionDuration && stagger . transitionDuration === 0 ) ||
( timings . animationDuration && stagger . animationDuration === 0 ) )
&& Math . max ( stagger . animationDelay , stagger . transitionDelay ) ;
if ( maxStagger ) {
$timeout ( triggerAnimationStart ,
Math . floor ( maxStagger * itemIndex * ONE _SECOND ) ,
false ) ;
} else {
triggerAnimationStart ( ) ;
2015-08-07 16:37:36 +02:00
}
2015-08-25 17:46:14 +02:00
// this will decorate the existing promise runner with pause/resume methods
runnerHost . resume = function ( ) {
playPause ( true ) ;
} ;
2015-08-07 16:37:36 +02:00
2015-08-25 17:46:14 +02:00
runnerHost . pause = function ( ) {
playPause ( false ) ;
} ;
2015-08-07 16:37:36 +02:00
2015-08-25 17:46:14 +02:00
function triggerAnimationStart ( ) {
// just incase a stagger animation kicks in when the animation
// itself was cancelled entirely
if ( animationClosed ) return ;
2015-08-07 16:37:36 +02:00
2015-08-25 17:46:14 +02:00
applyBlocking ( false ) ;
2015-08-07 16:37:36 +02:00
2015-08-25 17:46:14 +02:00
forEach ( temporaryStyles , function ( entry ) {
var key = entry [ 0 ] ;
var value = entry [ 1 ] ;
node . style [ key ] = value ;
} ) ;
2015-08-07 16:37:36 +02:00
2015-08-25 17:46:14 +02:00
applyAnimationClasses ( element , options ) ;
$$jqLite . addClass ( element , activeClasses ) ;
if ( flags . recalculateTimingStyles ) {
2017-01-12 07:31:09 +01:00
fullClassName = node . getAttribute ( 'class' ) + ' ' + preparationClasses ;
2015-08-25 17:46:14 +02:00
cacheKey = gcsHashFn ( node , fullClassName ) ;
timings = computeTimings ( node , fullClassName , cacheKey ) ;
relativeDelay = timings . maxDelay ;
maxDelay = Math . max ( relativeDelay , 0 ) ;
maxDuration = timings . maxDuration ;
if ( maxDuration === 0 ) {
close ( ) ;
return ;
}
2015-08-07 16:37:36 +02:00
2015-08-25 17:46:14 +02:00
flags . hasTransitions = timings . transitionDuration > 0 ;
flags . hasAnimations = timings . animationDuration > 0 ;
2015-08-07 16:37:36 +02:00
}
2015-08-31 17:08:41 +02:00
if ( flags . applyAnimationDelay ) {
2016-11-25 08:43:18 +01:00
relativeDelay = typeof options . delay !== 'boolean' && truthyTimingValue ( options . delay )
2015-08-25 17:46:14 +02:00
? parseFloat ( options . delay )
: relativeDelay ;
maxDelay = Math . max ( relativeDelay , 0 ) ;
2015-08-31 17:08:41 +02:00
timings . animationDelay = relativeDelay ;
delayStyle = getCssDelayStyle ( relativeDelay , true ) ;
temporaryStyles . push ( delayStyle ) ;
node . style [ delayStyle [ 0 ] ] = delayStyle [ 1 ] ;
2015-08-07 16:37:36 +02:00
}
2015-08-25 17:46:14 +02:00
maxDelayTime = maxDelay * ONE _SECOND ;
maxDurationTime = maxDuration * ONE _SECOND ;
2015-08-07 16:37:36 +02:00
2015-08-25 17:46:14 +02:00
if ( options . easing ) {
var easeProp , easeVal = options . easing ;
if ( flags . hasTransitions ) {
easeProp = TRANSITION _PROP + TIMING _KEY ;
temporaryStyles . push ( [ easeProp , easeVal ] ) ;
node . style [ easeProp ] = easeVal ;
}
if ( flags . hasAnimations ) {
easeProp = ANIMATION _PROP + TIMING _KEY ;
temporaryStyles . push ( [ easeProp , easeVal ] ) ;
node . style [ easeProp ] = easeVal ;
}
}
2015-08-07 16:37:36 +02:00
2015-08-25 17:46:14 +02:00
if ( timings . transitionDuration ) {
events . push ( TRANSITIONEND _EVENT ) ;
}
2015-08-07 16:37:36 +02:00
2015-08-25 17:46:14 +02:00
if ( timings . animationDuration ) {
events . push ( ANIMATIONEND _EVENT ) ;
}
2015-08-07 16:37:36 +02:00
2015-08-25 17:46:14 +02:00
startTime = Date . now ( ) ;
2015-08-31 17:08:41 +02:00
var timerTime = maxDelayTime + CLOSING _TIME _BUFFER * maxDurationTime ;
var endTime = startTime + timerTime ;
var animationsData = element . data ( ANIMATE _TIMER _KEY ) || [ ] ;
var setupFallbackTimer = true ;
if ( animationsData . length ) {
var currentTimerData = animationsData [ 0 ] ;
setupFallbackTimer = endTime > currentTimerData . expectedEndTime ;
if ( setupFallbackTimer ) {
$timeout . cancel ( currentTimerData . timer ) ;
} else {
animationsData . push ( close ) ;
}
}
if ( setupFallbackTimer ) {
var timer = $timeout ( onAnimationExpired , timerTime , false ) ;
animationsData [ 0 ] = {
timer : timer ,
expectedEndTime : endTime
} ;
animationsData . push ( close ) ;
element . data ( ANIMATE _TIMER _KEY , animationsData ) ;
}
2015-08-07 16:37:36 +02:00
2016-02-03 19:34:41 +01:00
if ( events . length ) {
element . on ( events . join ( ' ' ) , onAnimationProgress ) ;
}
2015-10-13 22:05:39 +02:00
if ( options . to ) {
if ( options . cleanupStyles ) {
registerRestorableStyles ( restoreStyles , node , Object . keys ( options . to ) ) ;
}
applyAnimationToStyles ( element , options ) ;
}
2015-08-25 17:46:14 +02:00
}
2015-08-07 16:37:36 +02:00
2015-08-25 17:46:14 +02:00
function onAnimationExpired ( ) {
2015-08-31 17:08:41 +02:00
var animationsData = element . data ( ANIMATE _TIMER _KEY ) ;
// this will be false in the event that the element was
// removed from the DOM (via a leave animation or something
// similar)
if ( animationsData ) {
for ( var i = 1 ; i < animationsData . length ; i ++ ) {
animationsData [ i ] ( ) ;
}
element . removeData ( ANIMATE _TIMER _KEY ) ;
}
2015-08-07 16:37:36 +02:00
}
}
2015-08-31 17:08:41 +02:00
} ;
2015-08-25 17:46:14 +02:00
} ] ;
} ] ;
2015-08-07 16:37:36 +02:00
2016-11-25 08:43:18 +01:00
var $$AnimateCssDriverProvider = [ '$$animationProvider' , /** @this */ function ( $$animationProvider ) {
2015-08-25 17:46:14 +02:00
$$animationProvider . drivers . push ( '$$animateCssDriver' ) ;
2015-08-07 16:37:36 +02:00
2015-08-25 17:46:14 +02:00
var NG _ANIMATE _SHIM _CLASS _NAME = 'ng-animate-shim' ;
var NG _ANIMATE _ANCHOR _CLASS _NAME = 'ng-anchor' ;
2015-08-07 16:37:36 +02:00
2015-08-25 17:46:14 +02:00
var NG _OUT _ANCHOR _CLASS _NAME = 'ng-anchor-out' ;
var NG _IN _ANCHOR _CLASS _NAME = 'ng-anchor-in' ;
2015-08-07 16:37:36 +02:00
2015-10-13 22:05:39 +02:00
function isDocumentFragment ( node ) {
return node . parentNode && node . parentNode . nodeType === 11 ;
}
this . $get = [ '$animateCss' , '$rootScope' , '$$AnimateRunner' , '$rootElement' , '$sniffer' , '$$jqLite' , '$document' ,
function ( $animateCss , $rootScope , $$AnimateRunner , $rootElement , $sniffer , $$jqLite , $document ) {
2015-08-07 16:37:36 +02:00
2015-08-25 17:46:14 +02:00
// only browsers that support these properties can render animations
if ( ! $sniffer . animations && ! $sniffer . transitions ) return noop ;
2015-08-07 16:37:36 +02:00
2015-10-13 22:05:39 +02:00
var bodyNode = $document [ 0 ] . body ;
2015-08-25 17:46:14 +02:00
var rootNode = getDomNode ( $rootElement ) ;
2015-08-07 16:37:36 +02:00
2015-10-13 22:05:39 +02:00
var rootBodyElement = jqLite (
// this is to avoid using something that exists outside of the body
2016-10-12 08:08:16 +02:00
// we also special case the doc fragment case because our unit test code
2015-10-13 22:05:39 +02:00
// appends the $rootElement to the body after the app has been bootstrapped
isDocumentFragment ( rootNode ) || bodyNode . contains ( rootNode ) ? rootNode : bodyNode
) ;
2015-08-07 16:37:36 +02:00
2015-08-31 17:08:41 +02:00
return function initDriverFn ( animationDetails ) {
2015-08-25 17:46:14 +02:00
return animationDetails . from && animationDetails . to
? prepareFromToAnchorAnimation ( animationDetails . from ,
animationDetails . to ,
animationDetails . classes ,
animationDetails . anchors )
2015-08-31 17:08:41 +02:00
: prepareRegularAnimation ( animationDetails ) ;
2015-08-25 17:46:14 +02:00
} ;
2015-08-07 16:37:36 +02:00
2015-08-25 17:46:14 +02:00
function filterCssClasses ( classes ) {
//remove all the `ng-` stuff
return classes . replace ( /\bng-\S+\b/g , '' ) ;
}
2015-08-07 16:37:36 +02:00
2015-08-25 17:46:14 +02:00
function getUniqueValues ( a , b ) {
if ( isString ( a ) ) a = a . split ( ' ' ) ;
if ( isString ( b ) ) b = b . split ( ' ' ) ;
return a . filter ( function ( val ) {
return b . indexOf ( val ) === - 1 ;
} ) . join ( ' ' ) ;
}
2015-08-07 16:37:36 +02:00
2015-08-25 17:46:14 +02:00
function prepareAnchoredAnimation ( classes , outAnchor , inAnchor ) {
var clone = jqLite ( getDomNode ( outAnchor ) . cloneNode ( true ) ) ;
var startingClasses = filterCssClasses ( getClassVal ( clone ) ) ;
2015-08-07 16:37:36 +02:00
2015-08-25 17:46:14 +02:00
outAnchor . addClass ( NG _ANIMATE _SHIM _CLASS _NAME ) ;
inAnchor . addClass ( NG _ANIMATE _SHIM _CLASS _NAME ) ;
2015-08-07 16:37:36 +02:00
2015-08-25 17:46:14 +02:00
clone . addClass ( NG _ANIMATE _ANCHOR _CLASS _NAME ) ;
2015-08-07 16:37:36 +02:00
2015-08-25 17:46:14 +02:00
rootBodyElement . append ( clone ) ;
2015-08-07 16:37:36 +02:00
2015-08-25 17:46:14 +02:00
var animatorIn , animatorOut = prepareOutAnimation ( ) ;
2015-08-07 16:37:36 +02:00
2015-08-25 17:46:14 +02:00
// the user may not end up using the `out` animation and
// only making use of the `in` animation or vice-versa.
// In either case we should allow this and not assume the
// animation is over unless both animations are not used.
if ( ! animatorOut ) {
animatorIn = prepareInAnimation ( ) ;
if ( ! animatorIn ) {
return end ( ) ;
2015-08-07 16:37:36 +02:00
}
2015-08-25 17:46:14 +02:00
}
2015-08-07 16:37:36 +02:00
2015-08-25 17:46:14 +02:00
var startingAnimator = animatorOut || animatorIn ;
return {
start : function ( ) {
var runner ;
var currentAnimation = startingAnimator . start ( ) ;
currentAnimation . done ( function ( ) {
currentAnimation = null ;
if ( ! animatorIn ) {
animatorIn = prepareInAnimation ( ) ;
if ( animatorIn ) {
currentAnimation = animatorIn . start ( ) ;
currentAnimation . done ( function ( ) {
currentAnimation = null ;
end ( ) ;
runner . complete ( ) ;
} ) ;
return currentAnimation ;
}
}
// in the event that there is no `in` animation
end ( ) ;
runner . complete ( ) ;
} ) ;
runner = new $$AnimateRunner ( {
end : endFn ,
cancel : endFn
} ) ;
2015-08-07 16:37:36 +02:00
2015-08-25 17:46:14 +02:00
return runner ;
2015-08-07 16:37:36 +02:00
2015-08-25 17:46:14 +02:00
function endFn ( ) {
if ( currentAnimation ) {
currentAnimation . end ( ) ;
}
2015-08-07 16:37:36 +02:00
}
}
2015-08-25 17:46:14 +02:00
} ;
2015-08-07 16:37:36 +02:00
2015-08-25 17:46:14 +02:00
function calculateAnchorStyles ( anchor ) {
var styles = { } ;
var coords = getDomNode ( anchor ) . getBoundingClientRect ( ) ;
// we iterate directly since safari messes up and doesn't return
2016-10-12 08:08:16 +02:00
// all the keys for the coords object when iterated
2015-08-25 17:46:14 +02:00
forEach ( [ 'width' , 'height' , 'top' , 'left' ] , function ( key ) {
var value = coords [ key ] ;
switch ( key ) {
case 'top' :
value += bodyNode . scrollTop ;
break ;
case 'left' :
value += bodyNode . scrollLeft ;
break ;
}
styles [ key ] = Math . floor ( value ) + 'px' ;
} ) ;
return styles ;
}
function prepareOutAnimation ( ) {
var animator = $animateCss ( clone , {
addClass : NG _OUT _ANCHOR _CLASS _NAME ,
delay : true ,
from : calculateAnchorStyles ( outAnchor )
} ) ;
2015-08-07 16:37:36 +02:00
2015-08-25 17:46:14 +02:00
// read the comment within `prepareRegularAnimation` to understand
// why this check is necessary
return animator . $$willAnimate ? animator : null ;
2015-08-07 16:37:36 +02:00
}
2015-08-25 17:46:14 +02:00
function getClassVal ( element ) {
return element . attr ( 'class' ) || '' ;
}
function prepareInAnimation ( ) {
var endingClasses = filterCssClasses ( getClassVal ( inAnchor ) ) ;
var toAdd = getUniqueValues ( endingClasses , startingClasses ) ;
var toRemove = getUniqueValues ( startingClasses , endingClasses ) ;
2015-08-07 16:37:36 +02:00
2015-08-25 17:46:14 +02:00
var animator = $animateCss ( clone , {
to : calculateAnchorStyles ( inAnchor ) ,
addClass : NG _IN _ANCHOR _CLASS _NAME + ' ' + toAdd ,
removeClass : NG _OUT _ANCHOR _CLASS _NAME + ' ' + toRemove ,
delay : true
2015-08-07 16:37:36 +02:00
} ) ;
2015-08-25 17:46:14 +02:00
// read the comment within `prepareRegularAnimation` to understand
// why this check is necessary
return animator . $$willAnimate ? animator : null ;
}
2015-08-07 16:37:36 +02:00
2015-08-25 17:46:14 +02:00
function end ( ) {
clone . remove ( ) ;
outAnchor . removeClass ( NG _ANIMATE _SHIM _CLASS _NAME ) ;
inAnchor . removeClass ( NG _ANIMATE _SHIM _CLASS _NAME ) ;
}
}
2015-08-07 16:37:36 +02:00
2015-08-25 17:46:14 +02:00
function prepareFromToAnchorAnimation ( from , to , classes , anchors ) {
var fromAnimation = prepareRegularAnimation ( from , noop ) ;
var toAnimation = prepareRegularAnimation ( to , noop ) ;
var anchorAnimations = [ ] ;
forEach ( anchors , function ( anchor ) {
var outElement = anchor [ 'out' ] ;
var inElement = anchor [ 'in' ] ;
var animator = prepareAnchoredAnimation ( classes , outElement , inElement ) ;
if ( animator ) {
anchorAnimations . push ( animator ) ;
2015-08-07 16:37:36 +02:00
}
2015-08-25 17:46:14 +02:00
} ) ;
2015-08-07 16:37:36 +02:00
2015-08-25 17:46:14 +02:00
// no point in doing anything when there are no elements to animate
if ( ! fromAnimation && ! toAnimation && anchorAnimations . length === 0 ) return ;
2015-08-07 16:37:36 +02:00
2015-08-25 17:46:14 +02:00
return {
start : function ( ) {
var animationRunners = [ ] ;
2015-08-07 16:37:36 +02:00
2015-08-25 17:46:14 +02:00
if ( fromAnimation ) {
animationRunners . push ( fromAnimation . start ( ) ) ;
2015-08-07 16:37:36 +02:00
}
2015-08-25 17:46:14 +02:00
if ( toAnimation ) {
animationRunners . push ( toAnimation . start ( ) ) ;
2015-08-07 16:37:36 +02:00
}
2015-08-25 17:46:14 +02:00
forEach ( anchorAnimations , function ( animation ) {
animationRunners . push ( animation . start ( ) ) ;
} ) ;
2015-08-07 16:37:36 +02:00
2015-08-25 17:46:14 +02:00
var runner = new $$AnimateRunner ( {
end : endFn ,
cancel : endFn // CSS-driven animations cannot be cancelled, only ended
} ) ;
2015-08-07 16:37:36 +02:00
2015-08-25 17:46:14 +02:00
$$AnimateRunner . all ( animationRunners , function ( status ) {
runner . complete ( status ) ;
} ) ;
2015-08-07 16:37:36 +02:00
2015-08-25 17:46:14 +02:00
return runner ;
2015-08-07 16:37:36 +02:00
2015-08-25 17:46:14 +02:00
function endFn ( ) {
forEach ( animationRunners , function ( runner ) {
runner . end ( ) ;
} ) ;
}
2015-08-07 16:37:36 +02:00
}
2015-08-25 17:46:14 +02:00
} ;
}
2015-08-07 16:37:36 +02:00
2015-08-31 17:08:41 +02:00
function prepareRegularAnimation ( animationDetails ) {
2015-08-25 17:46:14 +02:00
var element = animationDetails . element ;
var options = animationDetails . options || { } ;
2015-08-07 16:37:36 +02:00
2015-08-25 17:46:14 +02:00
if ( animationDetails . structural ) {
options . event = animationDetails . event ;
2015-08-31 17:08:41 +02:00
options . structural = true ;
options . applyClassesEarly = true ;
2015-08-07 16:37:36 +02:00
2015-08-25 17:46:14 +02:00
// we special case the leave animation since we want to ensure that
// the element is removed as soon as the animation is over. Otherwise
// a flicker might appear or the element may not be removed at all
if ( animationDetails . event === 'leave' ) {
options . onDone = options . domOperation ;
2015-08-07 16:37:36 +02:00
}
}
2015-08-25 17:46:14 +02:00
// We assign the preparationClasses as the actual animation event since
// the internals of $animateCss will just suffix the event token values
// with `-active` to trigger the animation.
if ( options . preparationClasses ) {
options . event = concatWithSpace ( options . event , options . preparationClasses ) ;
2015-08-07 16:37:36 +02:00
}
2015-08-25 17:46:14 +02:00
var animator = $animateCss ( element , options ) ;
// the driver lookup code inside of $$animation attempts to spawn a
// driver one by one until a driver returns a.$$willAnimate animator object.
// $animateCss will always return an object, however, it will pass in
// a flag as a hint as to whether an animation was detected or not
return animator . $$willAnimate ? animator : null ;
}
} ] ;
} ] ;
// TODO(matsko): use caching here to speed things up for detection
// TODO(matsko): add documentation
// by the time...
2016-11-25 08:43:18 +01:00
var $$AnimateJsProvider = [ '$animateProvider' , /** @this */ function ( $animateProvider ) {
2015-08-31 17:08:41 +02:00
this . $get = [ '$injector' , '$$AnimateRunner' , '$$jqLite' ,
function ( $injector , $$AnimateRunner , $$jqLite ) {
2015-08-25 17:46:14 +02:00
var applyAnimationClasses = applyAnimationClassesFactory ( $$jqLite ) ;
// $animateJs(element, 'enter');
return function ( element , event , classes , options ) {
2016-02-03 19:34:41 +01:00
var animationClosed = false ;
2015-08-25 17:46:14 +02:00
// the `classes` argument is optional and if it is not used
// then the classes will be resolved from the element's className
// property as well as options.addClass/options.removeClass.
if ( arguments . length === 3 && isObject ( classes ) ) {
options = classes ;
classes = null ;
2015-08-07 16:37:36 +02:00
}
2015-08-25 17:46:14 +02:00
options = prepareAnimationOptions ( options ) ;
if ( ! classes ) {
classes = element . attr ( 'class' ) || '' ;
if ( options . addClass ) {
classes += ' ' + options . addClass ;
}
if ( options . removeClass ) {
classes += ' ' + options . removeClass ;
2015-08-07 16:37:36 +02:00
}
}
2015-08-25 17:46:14 +02:00
var classesToAdd = options . addClass ;
var classesToRemove = options . removeClass ;
// the lookupAnimations function returns a series of animation objects that are
// matched up with one or more of the CSS classes. These animation objects are
// defined via the module.animation factory function. If nothing is detected then
// we don't return anything which then makes $animation query the next driver.
var animations = lookupAnimations ( classes ) ;
var before , after ;
if ( animations . length ) {
var afterFn , beforeFn ;
2016-11-25 08:43:18 +01:00
if ( event === 'leave' ) {
2015-08-25 17:46:14 +02:00
beforeFn = 'leave' ;
afterFn = 'afterLeave' ; // TODO(matsko): get rid of this
2015-08-07 16:37:36 +02:00
} else {
2015-08-25 17:46:14 +02:00
beforeFn = 'before' + event . charAt ( 0 ) . toUpperCase ( ) + event . substr ( 1 ) ;
afterFn = event ;
2015-08-07 16:37:36 +02:00
}
2015-08-25 17:46:14 +02:00
if ( event !== 'enter' && event !== 'move' ) {
before = packageAnimations ( element , event , options , animations , beforeFn ) ;
2015-08-07 16:37:36 +02:00
}
2015-08-25 17:46:14 +02:00
after = packageAnimations ( element , event , options , animations , afterFn ) ;
}
2015-08-07 16:37:36 +02:00
2015-08-25 17:46:14 +02:00
// no matching animations
if ( ! before && ! after ) return ;
2015-08-07 16:37:36 +02:00
2015-08-25 17:46:14 +02:00
function applyOptions ( ) {
options . domOperation ( ) ;
applyAnimationClasses ( element , options ) ;
2015-08-07 16:37:36 +02:00
}
2016-02-03 19:34:41 +01:00
function close ( ) {
animationClosed = true ;
applyOptions ( ) ;
applyAnimationStyles ( element , options ) ;
}
var runner ;
2015-08-25 17:46:14 +02:00
return {
2016-02-03 19:34:41 +01:00
$$willAnimate : true ,
end : function ( ) {
if ( runner ) {
runner . end ( ) ;
} else {
close ( ) ;
runner = new $$AnimateRunner ( ) ;
runner . complete ( true ) ;
}
return runner ;
} ,
2015-08-25 17:46:14 +02:00
start : function ( ) {
2016-02-03 19:34:41 +01:00
if ( runner ) {
return runner ;
}
runner = new $$AnimateRunner ( ) ;
2015-08-25 17:46:14 +02:00
var closeActiveAnimations ;
var chain = [ ] ;
if ( before ) {
chain . push ( function ( fn ) {
closeActiveAnimations = before ( fn ) ;
} ) ;
2015-08-07 16:37:36 +02:00
}
2015-08-25 17:46:14 +02:00
if ( chain . length ) {
chain . push ( function ( fn ) {
applyOptions ( ) ;
fn ( true ) ;
} ) ;
} else {
applyOptions ( ) ;
2015-08-07 16:37:36 +02:00
}
2015-08-25 17:46:14 +02:00
if ( after ) {
chain . push ( function ( fn ) {
closeActiveAnimations = after ( fn ) ;
} ) ;
2015-08-07 16:37:36 +02:00
}
2015-08-25 17:46:14 +02:00
2016-02-03 19:34:41 +01:00
runner . setHost ( {
2015-08-25 17:46:14 +02:00
end : function ( ) {
endAnimations ( ) ;
} ,
cancel : function ( ) {
endAnimations ( true ) ;
}
} ) ;
$$AnimateRunner . chain ( chain , onComplete ) ;
return runner ;
function onComplete ( success ) {
2016-02-03 19:34:41 +01:00
close ( success ) ;
2015-08-25 17:46:14 +02:00
runner . complete ( success ) ;
2015-08-07 16:37:36 +02:00
}
2015-08-25 17:46:14 +02:00
function endAnimations ( cancelled ) {
if ( ! animationClosed ) {
( closeActiveAnimations || noop ) ( cancelled ) ;
onComplete ( cancelled ) ;
}
2015-08-07 16:37:36 +02:00
}
}
} ;
2015-08-25 17:46:14 +02:00
function executeAnimationFn ( fn , element , event , options , onDone ) {
var args ;
switch ( event ) {
case 'animate' :
args = [ element , options . from , options . to , onDone ] ;
break ;
case 'setClass' :
args = [ element , classesToAdd , classesToRemove , onDone ] ;
break ;
case 'addClass' :
args = [ element , classesToAdd , onDone ] ;
break ;
case 'removeClass' :
args = [ element , classesToRemove , onDone ] ;
break ;
default :
args = [ element , onDone ] ;
break ;
}
args . push ( options ) ;
var value = fn . apply ( fn , args ) ;
if ( value ) {
if ( isFunction ( value . start ) ) {
value = value . start ( ) ;
}
if ( value instanceof $$AnimateRunner ) {
value . done ( onDone ) ;
} else if ( isFunction ( value ) ) {
// optional onEnd / onCancel callback
return value ;
}
}
return noop ;
}
function groupEventedAnimations ( element , event , options , animations , fnName ) {
var operations = [ ] ;
forEach ( animations , function ( ani ) {
var animation = ani [ fnName ] ;
if ( ! animation ) return ;
// note that all of these animations will run in parallel
operations . push ( function ( ) {
var runner ;
var endProgressCb ;
var resolved = false ;
var onAnimationComplete = function ( rejected ) {
if ( ! resolved ) {
resolved = true ;
( endProgressCb || noop ) ( rejected ) ;
runner . complete ( ! rejected ) ;
}
} ;
runner = new $$AnimateRunner ( {
end : function ( ) {
onAnimationComplete ( ) ;
} ,
cancel : function ( ) {
onAnimationComplete ( true ) ;
}
} ) ;
endProgressCb = executeAnimationFn ( animation , element , event , options , function ( result ) {
var cancelled = result === false ;
onAnimationComplete ( cancelled ) ;
} ) ;
return runner ;
} ) ;
} ) ;
return operations ;
}
function packageAnimations ( element , event , options , animations , fnName ) {
var operations = groupEventedAnimations ( element , event , options , animations , fnName ) ;
if ( operations . length === 0 ) {
2016-11-25 08:43:18 +01:00
var a , b ;
2015-08-25 17:46:14 +02:00
if ( fnName === 'beforeSetClass' ) {
a = groupEventedAnimations ( element , 'removeClass' , options , animations , 'beforeRemoveClass' ) ;
b = groupEventedAnimations ( element , 'addClass' , options , animations , 'beforeAddClass' ) ;
} else if ( fnName === 'setClass' ) {
a = groupEventedAnimations ( element , 'removeClass' , options , animations , 'removeClass' ) ;
b = groupEventedAnimations ( element , 'addClass' , options , animations , 'addClass' ) ;
}
if ( a ) {
operations = operations . concat ( a ) ;
}
if ( b ) {
operations = operations . concat ( b ) ;
}
}
if ( operations . length === 0 ) return ;
// TODO(matsko): add documentation
return function startAnimation ( callback ) {
var runners = [ ] ;
if ( operations . length ) {
forEach ( operations , function ( animateFn ) {
runners . push ( animateFn ( ) ) ;
} ) ;
}
2016-11-25 08:43:18 +01:00
if ( runners . length ) {
$$AnimateRunner . all ( runners , callback ) ;
} else {
callback ( ) ;
}
2015-08-25 17:46:14 +02:00
return function endFn ( reject ) {
forEach ( runners , function ( runner ) {
2016-11-25 08:43:18 +01:00
if ( reject ) {
runner . cancel ( ) ;
} else {
runner . end ( ) ;
}
2015-08-25 17:46:14 +02:00
} ) ;
} ;
} ;
}
} ;
function lookupAnimations ( classes ) {
classes = isArray ( classes ) ? classes : classes . split ( ' ' ) ;
var matches = [ ] , flagMap = { } ;
2016-11-25 08:43:18 +01:00
for ( var i = 0 ; i < classes . length ; i ++ ) {
2015-08-25 17:46:14 +02:00
var klass = classes [ i ] ,
animationFactory = $animateProvider . $$registeredAnimations [ klass ] ;
if ( animationFactory && ! flagMap [ klass ] ) {
matches . push ( $injector . get ( animationFactory ) ) ;
flagMap [ klass ] = true ;
}
}
return matches ;
}
} ] ;
} ] ;
2016-11-25 08:43:18 +01:00
var $$AnimateJsDriverProvider = [ '$$animationProvider' , /** @this */ function ( $$animationProvider ) {
2015-08-25 17:46:14 +02:00
$$animationProvider . drivers . push ( '$$animateJsDriver' ) ;
this . $get = [ '$$animateJs' , '$$AnimateRunner' , function ( $$animateJs , $$AnimateRunner ) {
return function initDriverFn ( animationDetails ) {
if ( animationDetails . from && animationDetails . to ) {
var fromAnimation = prepareAnimation ( animationDetails . from ) ;
var toAnimation = prepareAnimation ( animationDetails . to ) ;
if ( ! fromAnimation && ! toAnimation ) return ;
return {
start : function ( ) {
var animationRunners = [ ] ;
if ( fromAnimation ) {
animationRunners . push ( fromAnimation . start ( ) ) ;
}
if ( toAnimation ) {
animationRunners . push ( toAnimation . start ( ) ) ;
}
$$AnimateRunner . all ( animationRunners , done ) ;
var runner = new $$AnimateRunner ( {
end : endFnFactory ( ) ,
cancel : endFnFactory ( )
} ) ;
return runner ;
function endFnFactory ( ) {
return function ( ) {
forEach ( animationRunners , function ( runner ) {
// at this point we cannot cancel animations for groups just yet. 1.5+
runner . end ( ) ;
} ) ;
} ;
}
function done ( status ) {
runner . complete ( status ) ;
}
}
} ;
} else {
return prepareAnimation ( animationDetails ) ;
}
} ;
function prepareAnimation ( animationDetails ) {
// TODO(matsko): make sure to check for grouped animations and delegate down to normal animations
var element = animationDetails . element ;
var event = animationDetails . event ;
var options = animationDetails . options ;
var classes = animationDetails . classes ;
return $$animateJs ( element , event , classes , options ) ;
}
} ] ;
} ] ;
var NG _ANIMATE _ATTR _NAME = 'data-ng-animate' ;
var NG _ANIMATE _PIN _DATA = '$ngAnimatePin' ;
2016-11-25 08:43:18 +01:00
var $$AnimateQueueProvider = [ '$animateProvider' , /** @this */ function ( $animateProvider ) {
2015-08-25 17:46:14 +02:00
var PRE _DIGEST _STATE = 1 ;
var RUNNING _STATE = 2 ;
2016-02-03 19:34:41 +01:00
var ONE _SPACE = ' ' ;
2015-08-25 17:46:14 +02:00
var rules = this . rules = {
skip : [ ] ,
cancel : [ ] ,
join : [ ]
} ;
2016-02-03 19:34:41 +01:00
function makeTruthyCssClassMap ( classString ) {
if ( ! classString ) {
return null ;
}
var keys = classString . split ( ONE _SPACE ) ;
var map = Object . create ( null ) ;
forEach ( keys , function ( key ) {
map [ key ] = true ;
} ) ;
return map ;
}
function hasMatchingClasses ( newClassString , currentClassString ) {
if ( newClassString && currentClassString ) {
var currentClassMap = makeTruthyCssClassMap ( currentClassString ) ;
return newClassString . split ( ONE _SPACE ) . some ( function ( className ) {
return currentClassMap [ className ] ;
} ) ;
}
}
2017-02-08 07:27:36 +01:00
function isAllowed ( ruleType , currentAnimation , previousAnimation ) {
2015-08-25 17:46:14 +02:00
return rules [ ruleType ] . some ( function ( fn ) {
2017-02-08 07:27:36 +01:00
return fn ( currentAnimation , previousAnimation ) ;
2015-08-25 17:46:14 +02:00
} ) ;
}
2016-03-17 07:28:59 +01:00
function hasAnimationClasses ( animation , and ) {
var a = ( animation . addClass || '' ) . length > 0 ;
var b = ( animation . removeClass || '' ) . length > 0 ;
2015-08-25 17:46:14 +02:00
return and ? a && b : a || b ;
}
2017-02-08 07:27:36 +01:00
rules . join . push ( function ( newAnimation , currentAnimation ) {
2015-08-25 17:46:14 +02:00
// if the new animation is class-based then we can just tack that on
2016-03-17 07:28:59 +01:00
return ! newAnimation . structural && hasAnimationClasses ( newAnimation ) ;
2015-08-25 17:46:14 +02:00
} ) ;
2017-02-08 07:27:36 +01:00
rules . skip . push ( function ( newAnimation , currentAnimation ) {
2015-08-25 17:46:14 +02:00
// there is no need to animate anything if no classes are being added and
// there is no structural animation that will be triggered
2016-03-17 07:28:59 +01:00
return ! newAnimation . structural && ! hasAnimationClasses ( newAnimation ) ;
2015-08-25 17:46:14 +02:00
} ) ;
2017-02-08 07:27:36 +01:00
rules . skip . push ( function ( newAnimation , currentAnimation ) {
2015-08-25 17:46:14 +02:00
// why should we trigger a new structural animation if the element will
// be removed from the DOM anyway?
2016-11-25 08:43:18 +01:00
return currentAnimation . event === 'leave' && newAnimation . structural ;
2015-08-25 17:46:14 +02:00
} ) ;
2017-02-08 07:27:36 +01:00
rules . skip . push ( function ( newAnimation , currentAnimation ) {
2015-08-25 17:46:14 +02:00
// if there is an ongoing current animation then don't even bother running the class-based animation
return currentAnimation . structural && currentAnimation . state === RUNNING _STATE && ! newAnimation . structural ;
} ) ;
2017-02-08 07:27:36 +01:00
rules . cancel . push ( function ( newAnimation , currentAnimation ) {
2015-08-25 17:46:14 +02:00
// there can never be two structural animations running at the same time
return currentAnimation . structural && newAnimation . structural ;
} ) ;
2017-02-08 07:27:36 +01:00
rules . cancel . push ( function ( newAnimation , currentAnimation ) {
2015-08-25 17:46:14 +02:00
// if the previous animation is already running, but the new animation will
// be triggered, but the new animation is structural
return currentAnimation . state === RUNNING _STATE && newAnimation . structural ;
} ) ;
2017-02-08 07:27:36 +01:00
rules . cancel . push ( function ( newAnimation , currentAnimation ) {
2016-10-12 08:08:16 +02:00
// cancel the animation if classes added / removed in both animation cancel each other out,
// but only if the current animation isn't structural
if ( currentAnimation . structural ) return false ;
2016-03-17 07:28:59 +01:00
var nA = newAnimation . addClass ;
var nR = newAnimation . removeClass ;
var cA = currentAnimation . addClass ;
var cR = currentAnimation . removeClass ;
2016-02-03 19:34:41 +01:00
// early detection to save the global CPU shortage :)
if ( ( isUndefined ( nA ) && isUndefined ( nR ) ) || ( isUndefined ( cA ) && isUndefined ( cR ) ) ) {
return false ;
}
2016-03-17 07:28:59 +01:00
return hasMatchingClasses ( nA , cR ) || hasMatchingClasses ( nR , cA ) ;
2015-08-25 17:46:14 +02:00
} ) ;
2017-02-08 07:27:36 +01:00
this . $get = [ '$$rAF' , '$rootScope' , '$rootElement' , '$document' , '$$Map' ,
2015-08-25 17:46:14 +02:00
'$$animation' , '$$AnimateRunner' , '$templateRequest' , '$$jqLite' , '$$forceReflow' ,
2017-01-12 07:31:09 +01:00
'$$isDocumentHidden' ,
2017-02-08 07:27:36 +01:00
function ( $$rAF , $rootScope , $rootElement , $document , $$Map ,
2017-01-12 07:31:09 +01:00
$$animation , $$AnimateRunner , $templateRequest , $$jqLite , $$forceReflow ,
$$isDocumentHidden ) {
2015-08-25 17:46:14 +02:00
2017-02-08 07:27:36 +01:00
var activeAnimationsLookup = new $$Map ( ) ;
var disabledElementsLookup = new $$Map ( ) ;
2015-08-25 17:46:14 +02:00
var animationsEnabled = null ;
2015-10-13 22:05:39 +02:00
function postDigestTaskFactory ( ) {
var postDigestCalled = false ;
return function ( fn ) {
// we only issue a call to postDigest before
// it has first passed. This prevents any callbacks
// from not firing once the animation has completed
// since it will be out of the digest cycle.
if ( postDigestCalled ) {
fn ( ) ;
} else {
$rootScope . $$postDigest ( function ( ) {
postDigestCalled = true ;
fn ( ) ;
} ) ;
}
} ;
}
2015-08-25 17:46:14 +02:00
// Wait until all directive and route-related templates are downloaded and
// compiled. The $templateRequest.totalPendingRequests variable keeps track of
// all of the remote templates being currently downloaded. If there are no
// templates currently downloading then the watcher will still fire anyway.
var deregisterWatch = $rootScope . $watch (
function ( ) { return $templateRequest . totalPendingRequests === 0 ; } ,
function ( isEmpty ) {
if ( ! isEmpty ) return ;
deregisterWatch ( ) ;
// Now that all templates have been downloaded, $animate will wait until
// the post digest queue is empty before enabling animations. By having two
// calls to $postDigest calls we can ensure that the flag is enabled at the
// very end of the post digest queue. Since all of the animations in $animate
// use $postDigest, it's important that the code below executes at the end.
// This basically means that the page is fully downloaded and compiled before
// any animations are triggered.
$rootScope . $$postDigest ( function ( ) {
$rootScope . $$postDigest ( function ( ) {
// we check for null directly in the event that the application already called
// .enabled() with whatever arguments that it provided it with
if ( animationsEnabled === null ) {
animationsEnabled = true ;
}
} ) ;
} ) ;
}
) ;
2016-10-12 08:08:16 +02:00
var callbackRegistry = Object . create ( null ) ;
2015-08-25 17:46:14 +02:00
2017-07-04 07:24:12 +02:00
// remember that the `customFilter`/`classNameFilter` are set during the
// provider/config stage therefore we can optimize here and setup helper functions
var customFilter = $animateProvider . customFilter ( ) ;
2015-08-25 17:46:14 +02:00
var classNameFilter = $animateProvider . classNameFilter ( ) ;
2017-07-04 07:24:12 +02:00
var returnTrue = function ( ) { return true ; } ;
var isAnimatableByFilter = customFilter || returnTrue ;
var isAnimatableClassName = ! classNameFilter ? returnTrue : function ( node , options ) {
var className = [ node . getAttribute ( 'class' ) , options . addClass , options . removeClass ] . join ( ' ' ) ;
return classNameFilter . test ( className ) ;
} ;
2015-08-25 17:46:14 +02:00
var applyAnimationClasses = applyAnimationClassesFactory ( $$jqLite ) ;
2016-03-17 07:28:59 +01:00
function normalizeAnimationDetails ( element , animation ) {
return mergeAnimationDetails ( element , animation , { } ) ;
2015-08-25 17:46:14 +02:00
}
2016-02-03 19:34:41 +01:00
// IE9-11 has no method "contains" in SVG element and in Node.prototype. Bug #10259.
2016-11-25 08:43:18 +01:00
var contains = window . Node . prototype . contains || /** @this */ function ( arg ) {
// eslint-disable-next-line no-bitwise
2016-02-03 19:34:41 +01:00
return this === arg || ! ! ( this . compareDocumentPosition ( arg ) & 16 ) ;
} ;
2017-02-08 07:27:36 +01:00
function findCallbacks ( targetParentNode , targetNode , event ) {
2015-08-25 17:46:14 +02:00
var matches = [ ] ;
var entries = callbackRegistry [ event ] ;
if ( entries ) {
forEach ( entries , function ( entry ) {
2016-02-03 19:34:41 +01:00
if ( contains . call ( entry . node , targetNode ) ) {
2015-08-25 17:46:14 +02:00
matches . push ( entry . callback ) ;
2016-02-03 19:34:41 +01:00
} else if ( event === 'leave' && contains . call ( entry . node , targetParentNode ) ) {
2015-12-04 15:07:07 +01:00
matches . push ( entry . callback ) ;
2015-08-25 17:46:14 +02:00
}
} ) ;
}
return matches ;
}
2016-10-12 08:08:16 +02:00
function filterFromRegistry ( list , matchContainer , matchCallback ) {
var containerNode = extractElementNode ( matchContainer ) ;
return list . filter ( function ( entry ) {
var isMatch = entry . node === containerNode &&
( ! matchCallback || entry . callback === matchCallback ) ;
return ! isMatch ;
} ) ;
}
2017-02-08 07:27:36 +01:00
function cleanupEventListeners ( phase , node ) {
if ( phase === 'close' && ! node . parentNode ) {
2016-10-12 08:08:16 +02:00
// If the element is not attached to a parentNode, it has been removed by
// the domOperation, and we can safely remove the event callbacks
2017-02-08 07:27:36 +01:00
$animate . off ( node ) ;
2016-10-12 08:08:16 +02:00
}
}
var $animate = {
2015-08-25 17:46:14 +02:00
on : function ( event , container , callback ) {
var node = extractElementNode ( container ) ;
callbackRegistry [ event ] = callbackRegistry [ event ] || [ ] ;
callbackRegistry [ event ] . push ( {
node : node ,
callback : callback
} ) ;
2016-10-12 08:08:16 +02:00
// Remove the callback when the element is removed from the DOM
jqLite ( container ) . on ( '$destroy' , function ( ) {
var animationDetails = activeAnimationsLookup . get ( node ) ;
if ( ! animationDetails ) {
// If there's an animation ongoing, the callback calling code will remove
// the event listeners. If we'd remove here, the callbacks would be removed
// before the animation ends
$animate . off ( event , container , callback ) ;
}
} ) ;
2015-08-25 17:46:14 +02:00
} ,
off : function ( event , container , callback ) {
2016-10-12 08:08:16 +02:00
if ( arguments . length === 1 && ! isString ( arguments [ 0 ] ) ) {
container = arguments [ 0 ] ;
for ( var eventType in callbackRegistry ) {
callbackRegistry [ eventType ] = filterFromRegistry ( callbackRegistry [ eventType ] , container ) ;
}
return ;
}
2015-08-25 17:46:14 +02:00
var entries = callbackRegistry [ event ] ;
if ( ! entries ) return ;
callbackRegistry [ event ] = arguments . length === 1
? null
: filterFromRegistry ( entries , container , callback ) ;
} ,
pin : function ( element , parentElement ) {
assertArg ( isElement ( element ) , 'element' , 'not an element' ) ;
assertArg ( isElement ( parentElement ) , 'parentElement' , 'not an element' ) ;
element . data ( NG _ANIMATE _PIN _DATA , parentElement ) ;
} ,
push : function ( element , event , options , domOperation ) {
options = options || { } ;
options . domOperation = domOperation ;
return queueAnimation ( element , event , options ) ;
} ,
// this method has four signatures:
// () - global getter
// (bool) - global setter
// (element) - element getter
// (element, bool) - element setter<F37>
enabled : function ( element , bool ) {
var argCount = arguments . length ;
if ( argCount === 0 ) {
// () - Global getter
bool = ! ! animationsEnabled ;
} else {
var hasElement = isElement ( element ) ;
if ( ! hasElement ) {
// (bool) - Global setter
bool = animationsEnabled = ! ! element ;
} else {
var node = getDomNode ( element ) ;
if ( argCount === 1 ) {
// (element) - Element getter
2016-10-12 08:08:16 +02:00
bool = ! disabledElementsLookup . get ( node ) ;
2015-08-25 17:46:14 +02:00
} else {
// (element, bool) - Element setter
2017-02-08 07:27:36 +01:00
disabledElementsLookup . set ( node , ! bool ) ;
2015-08-25 17:46:14 +02:00
}
}
}
return bool ;
}
} ;
2016-10-12 08:08:16 +02:00
return $animate ;
2017-02-08 07:27:36 +01:00
function queueAnimation ( originalElement , event , initialOptions ) {
2016-02-03 19:34:41 +01:00
// we always make a copy of the options since
// there should never be any side effects on
// the input data when running `$animateCss`.
var options = copy ( initialOptions ) ;
2017-02-08 07:27:36 +01:00
var element = stripCommentsFromElement ( originalElement ) ;
var node = getDomNode ( element ) ;
var parentNode = node && node . parentNode ;
2015-08-25 17:46:14 +02:00
options = prepareAnimationOptions ( options ) ;
// we create a fake runner with a working promise.
// These methods will become available after the digest has passed
var runner = new $$AnimateRunner ( ) ;
2015-10-13 22:05:39 +02:00
// this is used to trigger callbacks in postDigest mode
var runInNextPostDigestOrNow = postDigestTaskFactory ( ) ;
2015-08-25 17:46:14 +02:00
if ( isArray ( options . addClass ) ) {
options . addClass = options . addClass . join ( ' ' ) ;
}
if ( options . addClass && ! isString ( options . addClass ) ) {
options . addClass = null ;
}
if ( isArray ( options . removeClass ) ) {
options . removeClass = options . removeClass . join ( ' ' ) ;
}
if ( options . removeClass && ! isString ( options . removeClass ) ) {
options . removeClass = null ;
}
if ( options . from && ! isObject ( options . from ) ) {
options . from = null ;
}
if ( options . to && ! isObject ( options . to ) ) {
options . to = null ;
}
2017-07-04 07:24:12 +02:00
// If animations are hard-disabled for the whole application there is no need to continue.
// There are also situations where a directive issues an animation for a jqLite wrapper that
// contains only comment nodes. In this case, there is no way we can perform an animation.
if ( ! animationsEnabled ||
! node ||
! isAnimatableByFilter ( node , event , initialOptions ) ||
! isAnimatableClassName ( node , options ) ) {
2015-08-25 17:46:14 +02:00
close ( ) ;
return runner ;
}
var isStructural = [ 'enter' , 'move' , 'leave' ] . indexOf ( event ) >= 0 ;
2017-01-12 07:31:09 +01:00
var documentHidden = $$isDocumentHidden ( ) ;
2016-05-28 07:11:08 +02:00
2017-07-04 07:24:12 +02:00
// This is a hard disable of all animations the element itself, therefore there is no need to
// continue further past this point if not enabled
2016-02-03 19:34:41 +01:00
// Animations are also disabled if the document is currently hidden (page is not visible
// to the user), because browsers slow down or do not flush calls to requestAnimationFrame
2017-07-04 07:24:12 +02:00
var skipAnimations = documentHidden || disabledElementsLookup . get ( node ) ;
2015-08-25 17:46:14 +02:00
var existingAnimation = ( ! skipAnimations && activeAnimationsLookup . get ( node ) ) || { } ;
var hasExistingAnimation = ! ! existingAnimation . state ;
// there is no point in traversing the same collection of parent ancestors if a followup
// animation will be run on the same element that already did all that checking work
2016-11-25 08:43:18 +01:00
if ( ! skipAnimations && ( ! hasExistingAnimation || existingAnimation . state !== PRE _DIGEST _STATE ) ) {
2017-02-08 07:27:36 +01:00
skipAnimations = ! areAnimationsAllowed ( node , parentNode , event ) ;
2015-08-25 17:46:14 +02:00
}
if ( skipAnimations ) {
2016-05-28 07:11:08 +02:00
// Callbacks should fire even if the document is hidden (regression fix for issue #14120)
if ( documentHidden ) notifyProgress ( runner , event , 'start' ) ;
2015-08-25 17:46:14 +02:00
close ( ) ;
2016-05-28 07:11:08 +02:00
if ( documentHidden ) notifyProgress ( runner , event , 'close' ) ;
2015-08-25 17:46:14 +02:00
return runner ;
}
if ( isStructural ) {
2017-02-08 07:27:36 +01:00
closeChildAnimations ( node ) ;
2015-08-25 17:46:14 +02:00
}
var newAnimation = {
structural : isStructural ,
element : element ,
event : event ,
2016-03-17 07:28:59 +01:00
addClass : options . addClass ,
removeClass : options . removeClass ,
2015-08-25 17:46:14 +02:00
close : close ,
options : options ,
runner : runner
} ;
if ( hasExistingAnimation ) {
2017-02-08 07:27:36 +01:00
var skipAnimationFlag = isAllowed ( 'skip' , newAnimation , existingAnimation ) ;
2015-08-25 17:46:14 +02:00
if ( skipAnimationFlag ) {
if ( existingAnimation . state === RUNNING _STATE ) {
close ( ) ;
return runner ;
} else {
2016-03-17 07:28:59 +01:00
mergeAnimationDetails ( element , existingAnimation , newAnimation ) ;
2015-08-25 17:46:14 +02:00
return existingAnimation . runner ;
}
}
2017-02-08 07:27:36 +01:00
var cancelAnimationFlag = isAllowed ( 'cancel' , newAnimation , existingAnimation ) ;
2015-08-25 17:46:14 +02:00
if ( cancelAnimationFlag ) {
if ( existingAnimation . state === RUNNING _STATE ) {
// this will end the animation right away and it is safe
// to do so since the animation is already running and the
// runner callback code will run in async
existingAnimation . runner . end ( ) ;
} else if ( existingAnimation . structural ) {
// this means that the animation is queued into a digest, but
// hasn't started yet. Therefore it is safe to run the close
// method which will call the runner methods in async.
existingAnimation . close ( ) ;
} else {
// this will merge the new animation options into existing animation options
2016-03-17 07:28:59 +01:00
mergeAnimationDetails ( element , existingAnimation , newAnimation ) ;
2015-08-25 17:46:14 +02:00
return existingAnimation . runner ;
}
} else {
// a joined animation means that this animation will take over the existing one
// so an example would involve a leave animation taking over an enter. Then when
// the postDigest kicks in the enter will be ignored.
2017-02-08 07:27:36 +01:00
var joinAnimationFlag = isAllowed ( 'join' , newAnimation , existingAnimation ) ;
2015-08-25 17:46:14 +02:00
if ( joinAnimationFlag ) {
if ( existingAnimation . state === RUNNING _STATE ) {
2016-03-17 07:28:59 +01:00
normalizeAnimationDetails ( element , newAnimation ) ;
2015-08-25 17:46:14 +02:00
} else {
applyGeneratedPreparationClasses ( element , isStructural ? event : null , options ) ;
event = newAnimation . event = existingAnimation . event ;
2016-03-17 07:28:59 +01:00
options = mergeAnimationDetails ( element , existingAnimation , newAnimation ) ;
2015-08-25 17:46:14 +02:00
//we return the same runner since only the option values of this animation will
//be fed into the `existingAnimation`.
return existingAnimation . runner ;
}
}
}
} else {
// normalization in this case means that it removes redundant CSS classes that
// already exist (addClass) or do not exist (removeClass) on the element
2016-03-17 07:28:59 +01:00
normalizeAnimationDetails ( element , newAnimation ) ;
2015-08-25 17:46:14 +02:00
}
// when the options are merged and cleaned up we may end up not having to do
// an animation at all, therefore we should check this before issuing a post
// digest callback. Structural animations will always run no matter what.
var isValidAnimation = newAnimation . structural ;
if ( ! isValidAnimation ) {
// animate (from/to) can be quickly checked first, otherwise we check if any classes are present
isValidAnimation = ( newAnimation . event === 'animate' && Object . keys ( newAnimation . options . to || { } ) . length > 0 )
2016-03-17 07:28:59 +01:00
|| hasAnimationClasses ( newAnimation ) ;
2015-08-25 17:46:14 +02:00
}
if ( ! isValidAnimation ) {
close ( ) ;
2017-02-08 07:27:36 +01:00
clearElementAnimationState ( node ) ;
2015-08-25 17:46:14 +02:00
return runner ;
}
// the counter keeps track of cancelled animations
var counter = ( existingAnimation . counter || 0 ) + 1 ;
newAnimation . counter = counter ;
2017-02-08 07:27:36 +01:00
markElementAnimationState ( node , PRE _DIGEST _STATE , newAnimation ) ;
2015-08-25 17:46:14 +02:00
$rootScope . $$postDigest ( function ( ) {
2017-02-08 07:27:36 +01:00
// It is possible that the DOM nodes inside `originalElement` have been replaced. This can
// happen if the animated element is a transcluded clone and also has a `templateUrl`
// directive on it. Therefore, we must recreate `element` in order to interact with the
// actual DOM nodes.
// Note: We still need to use the old `node` for certain things, such as looking up in
// HashMaps where it was used as the key.
element = stripCommentsFromElement ( originalElement ) ;
2015-08-25 17:46:14 +02:00
var animationDetails = activeAnimationsLookup . get ( node ) ;
var animationCancelled = ! animationDetails ;
animationDetails = animationDetails || { } ;
// if addClass/removeClass is called before something like enter then the
// registered parent element may not be present. The code below will ensure
// that a final value for parent element is obtained
var parentElement = element . parent ( ) || [ ] ;
// animate/structural/class-based animations all have requirements. Otherwise there
// is no point in performing an animation. The parent node must also be set.
var isValidAnimation = parentElement . length > 0
&& ( animationDetails . event === 'animate'
|| animationDetails . structural
2016-03-17 07:28:59 +01:00
|| hasAnimationClasses ( animationDetails ) ) ;
2015-08-25 17:46:14 +02:00
// this means that the previous animation was cancelled
// even if the follow-up animation is the same event
if ( animationCancelled || animationDetails . counter !== counter || ! isValidAnimation ) {
// if another animation did not take over then we need
// to make sure that the domOperation and options are
// handled accordingly
if ( animationCancelled ) {
applyAnimationClasses ( element , options ) ;
applyAnimationStyles ( element , options ) ;
}
// if the event changed from something like enter to leave then we do
// it, otherwise if it's the same then the end result will be the same too
if ( animationCancelled || ( isStructural && animationDetails . event !== event ) ) {
options . domOperation ( ) ;
runner . end ( ) ;
}
// in the event that the element animation was not cancelled or a follow-up animation
// isn't allowed to animate from here then we need to clear the state of the element
// so that any future animations won't read the expired animation data.
if ( ! isValidAnimation ) {
2017-02-08 07:27:36 +01:00
clearElementAnimationState ( node ) ;
2015-08-25 17:46:14 +02:00
}
return ;
}
// this combined multiple class to addClass / removeClass into a setClass event
// so long as a structural event did not take over the animation
2016-03-17 07:28:59 +01:00
event = ! animationDetails . structural && hasAnimationClasses ( animationDetails , true )
2015-08-25 17:46:14 +02:00
? 'setClass'
: animationDetails . event ;
2017-02-08 07:27:36 +01:00
markElementAnimationState ( node , RUNNING _STATE ) ;
2015-08-31 17:08:41 +02:00
var realRunner = $$animation ( element , event , animationDetails . options ) ;
2015-08-25 17:46:14 +02:00
2016-05-28 07:11:08 +02:00
// this will update the runner's flow-control events based on
// the `realRunner` object.
runner . setHost ( realRunner ) ;
notifyProgress ( runner , event , 'start' , { } ) ;
2015-08-25 17:46:14 +02:00
realRunner . done ( function ( status ) {
close ( ! status ) ;
var animationDetails = activeAnimationsLookup . get ( node ) ;
if ( animationDetails && animationDetails . counter === counter ) {
2017-02-08 07:27:36 +01:00
clearElementAnimationState ( node ) ;
2015-08-25 17:46:14 +02:00
}
notifyProgress ( runner , event , 'close' , { } ) ;
} ) ;
} ) ;
return runner ;
function notifyProgress ( runner , event , phase , data ) {
2015-10-13 22:05:39 +02:00
runInNextPostDigestOrNow ( function ( ) {
2017-02-08 07:27:36 +01:00
var callbacks = findCallbacks ( parentNode , node , event ) ;
2015-10-13 22:05:39 +02:00
if ( callbacks . length ) {
// do not optimize this call here to RAF because
// we don't know how heavy the callback code here will
// be and if this code is buffered then this can
// lead to a performance regression.
$$rAF ( function ( ) {
forEach ( callbacks , function ( callback ) {
callback ( element , phase , data ) ;
} ) ;
2017-02-08 07:27:36 +01:00
cleanupEventListeners ( phase , node ) ;
2015-10-13 22:05:39 +02:00
} ) ;
2016-10-12 08:08:16 +02:00
} else {
2017-02-08 07:27:36 +01:00
cleanupEventListeners ( phase , node ) ;
2015-10-13 22:05:39 +02:00
}
} ) ;
2015-08-25 17:46:14 +02:00
runner . progress ( event , phase , data ) ;
}
2016-11-25 08:43:18 +01:00
function close ( reject ) {
2015-08-25 17:46:14 +02:00
clearGeneratedClasses ( element , options ) ;
applyAnimationClasses ( element , options ) ;
applyAnimationStyles ( element , options ) ;
options . domOperation ( ) ;
runner . complete ( ! reject ) ;
}
}
2017-02-08 07:27:36 +01:00
function closeChildAnimations ( node ) {
2015-08-25 17:46:14 +02:00
var children = node . querySelectorAll ( '[' + NG _ANIMATE _ATTR _NAME + ']' ) ;
forEach ( children , function ( child ) {
2016-11-25 08:43:18 +01:00
var state = parseInt ( child . getAttribute ( NG _ANIMATE _ATTR _NAME ) , 10 ) ;
2015-08-25 17:46:14 +02:00
var animationDetails = activeAnimationsLookup . get ( child ) ;
2016-02-03 19:34:41 +01:00
if ( animationDetails ) {
switch ( state ) {
case RUNNING _STATE :
animationDetails . runner . end ( ) ;
/* falls through */
case PRE _DIGEST _STATE :
2017-02-08 07:27:36 +01:00
activeAnimationsLookup . delete ( child ) ;
2016-02-03 19:34:41 +01:00
break ;
}
2015-08-25 17:46:14 +02:00
}
} ) ;
}
2017-02-08 07:27:36 +01:00
function clearElementAnimationState ( node ) {
2015-08-25 17:46:14 +02:00
node . removeAttribute ( NG _ANIMATE _ATTR _NAME ) ;
2017-02-08 07:27:36 +01:00
activeAnimationsLookup . delete ( node ) ;
2015-08-25 17:46:14 +02:00
}
2016-02-03 19:34:41 +01:00
/ * *
* This fn returns false if any of the following is true :
* a ) animations on any parent element are disabled , and animations on the element aren ' t explicitly allowed
* b ) a parent element has an ongoing structural animation , and animateChildren is false
* c ) the element is not a child of the body
* d ) the element is not a child of the $rootElement
* /
2017-02-08 07:27:36 +01:00
function areAnimationsAllowed ( node , parentNode , event ) {
var bodyNode = $document [ 0 ] . body ;
var rootNode = getDomNode ( $rootElement ) ;
var bodyNodeDetected = ( node === bodyNode ) || node . nodeName === 'HTML' ;
var rootNodeDetected = ( node === rootNode ) ;
2015-08-25 17:46:14 +02:00
var parentAnimationDetected = false ;
2017-02-08 07:27:36 +01:00
var elementDisabled = disabledElementsLookup . get ( node ) ;
2015-08-25 17:46:14 +02:00
var animateChildren ;
2017-02-08 07:27:36 +01:00
var parentHost = jqLite . data ( node , NG _ANIMATE _PIN _DATA ) ;
2015-08-25 17:46:14 +02:00
if ( parentHost ) {
2017-02-08 07:27:36 +01:00
parentNode = getDomNode ( parentHost ) ;
2015-08-25 17:46:14 +02:00
}
2017-02-08 07:27:36 +01:00
while ( parentNode ) {
if ( ! rootNodeDetected ) {
2015-08-25 17:46:14 +02:00
// angular doesn't want to attempt to animate elements outside of the application
// therefore we need to ensure that the rootElement is an ancestor of the current element
2017-02-08 07:27:36 +01:00
rootNodeDetected = ( parentNode === rootNode ) ;
2015-08-25 17:46:14 +02:00
}
2017-02-08 07:27:36 +01:00
if ( parentNode . nodeType !== ELEMENT _NODE ) {
2015-08-25 17:46:14 +02:00
// no point in inspecting the #document element
break ;
}
2017-02-08 07:27:36 +01:00
var details = activeAnimationsLookup . get ( parentNode ) || { } ;
2015-08-25 17:46:14 +02:00
// either an enter, leave or move animation will commence
// therefore we can't allow any animations to take place
// but if a parent animation is class-based then that's ok
if ( ! parentAnimationDetected ) {
2017-02-08 07:27:36 +01:00
var parentNodeDisabled = disabledElementsLookup . get ( parentNode ) ;
2016-02-03 19:34:41 +01:00
2017-02-08 07:27:36 +01:00
if ( parentNodeDisabled === true && elementDisabled !== false ) {
2016-02-03 19:34:41 +01:00
// disable animations if the user hasn't explicitly enabled animations on the
// current element
elementDisabled = true ;
// element is disabled via parent element, no need to check anything else
break ;
2017-02-08 07:27:36 +01:00
} else if ( parentNodeDisabled === false ) {
2016-02-03 19:34:41 +01:00
elementDisabled = false ;
}
parentAnimationDetected = details . structural ;
2015-08-25 17:46:14 +02:00
}
if ( isUndefined ( animateChildren ) || animateChildren === true ) {
2017-02-08 07:27:36 +01:00
var value = jqLite . data ( parentNode , NG _ANIMATE _CHILDREN _DATA ) ;
2015-08-25 17:46:14 +02:00
if ( isDefined ( value ) ) {
animateChildren = value ;
}
}
// there is no need to continue traversing at this point
if ( parentAnimationDetected && animateChildren === false ) break ;
2017-02-08 07:27:36 +01:00
if ( ! bodyNodeDetected ) {
2016-02-03 19:34:41 +01:00
// we also need to ensure that the element is or will be a part of the body element
2015-08-25 17:46:14 +02:00
// otherwise it is pointless to even issue an animation to be rendered
2017-02-08 07:27:36 +01:00
bodyNodeDetected = ( parentNode === bodyNode ) ;
2015-08-25 17:46:14 +02:00
}
2017-02-08 07:27:36 +01:00
if ( bodyNodeDetected && rootNodeDetected ) {
2016-02-03 19:34:41 +01:00
// If both body and root have been found, any other checks are pointless,
// as no animation data should live outside the application
break ;
}
2017-02-08 07:27:36 +01:00
if ( ! rootNodeDetected ) {
// If `rootNode` is not detected, check if `parentNode` is pinned to another element
parentHost = jqLite . data ( parentNode , NG _ANIMATE _PIN _DATA ) ;
2016-02-03 19:34:41 +01:00
if ( parentHost ) {
// The pin target element becomes the next parent element
2017-02-08 07:27:36 +01:00
parentNode = getDomNode ( parentHost ) ;
2016-02-03 19:34:41 +01:00
continue ;
}
}
2017-02-08 07:27:36 +01:00
parentNode = parentNode . parentNode ;
2015-08-25 17:46:14 +02:00
}
2016-02-03 19:34:41 +01:00
var allowAnimation = ( ! parentAnimationDetected || animateChildren ) && elementDisabled !== true ;
2017-02-08 07:27:36 +01:00
return allowAnimation && rootNodeDetected && bodyNodeDetected ;
2015-08-25 17:46:14 +02:00
}
2017-02-08 07:27:36 +01:00
function markElementAnimationState ( node , state , details ) {
2015-08-25 17:46:14 +02:00
details = details || { } ;
details . state = state ;
node . setAttribute ( NG _ANIMATE _ATTR _NAME , state ) ;
var oldValue = activeAnimationsLookup . get ( node ) ;
var newValue = oldValue
? extend ( oldValue , details )
: details ;
2017-02-08 07:27:36 +01:00
activeAnimationsLookup . set ( node , newValue ) ;
2015-08-25 17:46:14 +02:00
}
} ] ;
} ] ;
2016-11-25 08:43:18 +01:00
/* exported $$AnimationProvider */
var $$AnimationProvider = [ '$animateProvider' , /** @this */ function ( $animateProvider ) {
2015-08-25 17:46:14 +02:00
var NG _ANIMATE _REF _ATTR = 'ng-animate-ref' ;
var drivers = this . drivers = [ ] ;
var RUNNER _STORAGE _KEY = '$$animationRunner' ;
function setRunner ( element , runner ) {
element . data ( RUNNER _STORAGE _KEY , runner ) ;
}
function removeRunner ( element ) {
element . removeData ( RUNNER _STORAGE _KEY ) ;
}
function getRunner ( element ) {
return element . data ( RUNNER _STORAGE _KEY ) ;
}
2017-02-08 07:27:36 +01:00
this . $get = [ '$$jqLite' , '$rootScope' , '$injector' , '$$AnimateRunner' , '$$Map' , '$$rAFScheduler' ,
function ( $$jqLite , $rootScope , $injector , $$AnimateRunner , $$Map , $$rAFScheduler ) {
2015-08-25 17:46:14 +02:00
var animationQueue = [ ] ;
var applyAnimationClasses = applyAnimationClassesFactory ( $$jqLite ) ;
function sortAnimations ( animations ) {
var tree = { children : [ ] } ;
2017-02-08 07:27:36 +01:00
var i , lookup = new $$Map ( ) ;
2015-08-25 17:46:14 +02:00
2017-02-08 07:27:36 +01:00
// this is done first beforehand so that the map
2015-08-25 17:46:14 +02:00
// is filled with a list of the elements that will be animated
for ( i = 0 ; i < animations . length ; i ++ ) {
var animation = animations [ i ] ;
2017-02-08 07:27:36 +01:00
lookup . set ( animation . domNode , animations [ i ] = {
2015-08-25 17:46:14 +02:00
domNode : animation . domNode ,
fn : animation . fn ,
children : [ ]
} ) ;
}
for ( i = 0 ; i < animations . length ; i ++ ) {
processNode ( animations [ i ] ) ;
}
return flatten ( tree ) ;
function processNode ( entry ) {
if ( entry . processed ) return entry ;
entry . processed = true ;
var elementNode = entry . domNode ;
var parentNode = elementNode . parentNode ;
2017-02-08 07:27:36 +01:00
lookup . set ( elementNode , entry ) ;
2015-08-25 17:46:14 +02:00
var parentEntry ;
while ( parentNode ) {
parentEntry = lookup . get ( parentNode ) ;
if ( parentEntry ) {
if ( ! parentEntry . processed ) {
parentEntry = processNode ( parentEntry ) ;
}
break ;
}
parentNode = parentNode . parentNode ;
}
( parentEntry || tree ) . children . push ( entry ) ;
return entry ;
}
function flatten ( tree ) {
var result = [ ] ;
var queue = [ ] ;
var i ;
for ( i = 0 ; i < tree . children . length ; i ++ ) {
queue . push ( tree . children [ i ] ) ;
}
var remainingLevelEntries = queue . length ;
var nextLevelEntries = 0 ;
var row = [ ] ;
for ( i = 0 ; i < queue . length ; i ++ ) {
var entry = queue [ i ] ;
if ( remainingLevelEntries <= 0 ) {
remainingLevelEntries = nextLevelEntries ;
nextLevelEntries = 0 ;
2015-08-31 17:08:41 +02:00
result . push ( row ) ;
2015-08-25 17:46:14 +02:00
row = [ ] ;
}
row . push ( entry . fn ) ;
2015-08-31 17:08:41 +02:00
entry . children . forEach ( function ( childEntry ) {
2015-08-25 17:46:14 +02:00
nextLevelEntries ++ ;
queue . push ( childEntry ) ;
} ) ;
remainingLevelEntries -- ;
}
if ( row . length ) {
2015-08-31 17:08:41 +02:00
result . push ( row ) ;
2015-08-25 17:46:14 +02:00
}
2015-08-31 17:08:41 +02:00
2015-08-25 17:46:14 +02:00
return result ;
}
}
// TODO(matsko): document the signature in a better way
2015-08-31 17:08:41 +02:00
return function ( element , event , options ) {
2015-08-25 17:46:14 +02:00
options = prepareAnimationOptions ( options ) ;
var isStructural = [ 'enter' , 'move' , 'leave' ] . indexOf ( event ) >= 0 ;
// there is no animation at the current moment, however
// these runner methods will get later updated with the
// methods leading into the driver's end/cancel methods
// for now they just stop the animation from starting
var runner = new $$AnimateRunner ( {
end : function ( ) { close ( ) ; } ,
cancel : function ( ) { close ( true ) ; }
} ) ;
if ( ! drivers . length ) {
close ( ) ;
return runner ;
}
setRunner ( element , runner ) ;
var classes = mergeClasses ( element . attr ( 'class' ) , mergeClasses ( options . addClass , options . removeClass ) ) ;
var tempClasses = options . tempClasses ;
if ( tempClasses ) {
classes += ' ' + tempClasses ;
options . tempClasses = null ;
}
2016-03-17 07:28:59 +01:00
var prepareClassName ;
if ( isStructural ) {
prepareClassName = 'ng-' + event + PREPARE _CLASS _SUFFIX ;
$$jqLite . addClass ( element , prepareClassName ) ;
}
2015-08-25 17:46:14 +02:00
animationQueue . push ( {
// this data is used by the postDigest code and passed into
// the driver step function
element : element ,
classes : classes ,
event : event ,
structural : isStructural ,
options : options ,
beforeStart : beforeStart ,
close : close
} ) ;
element . on ( '$destroy' , handleDestroyedElement ) ;
// we only want there to be one function called within the post digest
// block. This way we can group animations for all the animations that
// were apart of the same postDigest flush call.
if ( animationQueue . length > 1 ) return runner ;
$rootScope . $$postDigest ( function ( ) {
var animations = [ ] ;
forEach ( animationQueue , function ( entry ) {
// the element was destroyed early on which removed the runner
// form its storage. This means we can't animate this element
// at all and it already has been closed due to destruction.
2015-08-31 17:08:41 +02:00
if ( getRunner ( entry . element ) ) {
2015-08-25 17:46:14 +02:00
animations . push ( entry ) ;
} else {
entry . close ( ) ;
}
} ) ;
// now any future animations will be in another postDigest
animationQueue . length = 0 ;
var groupedAnimations = groupAnimations ( animations ) ;
var toBeSortedAnimations = [ ] ;
forEach ( groupedAnimations , function ( animationEntry ) {
toBeSortedAnimations . push ( {
domNode : getDomNode ( animationEntry . from ? animationEntry . from . element : animationEntry . element ) ,
fn : function triggerAnimationStart ( ) {
// it's important that we apply the `ng-animate` CSS class and the
// temporary classes before we do any driver invoking since these
// CSS classes may be required for proper CSS detection.
animationEntry . beforeStart ( ) ;
var startAnimationFn , closeFn = animationEntry . close ;
// in the event that the element was removed before the digest runs or
// during the RAF sequencing then we should not trigger the animation.
var targetElement = animationEntry . anchors
? ( animationEntry . from . element || animationEntry . to . element )
: animationEntry . element ;
if ( getRunner ( targetElement ) ) {
2015-08-31 17:08:41 +02:00
var operation = invokeFirstDriver ( animationEntry ) ;
2015-08-25 17:46:14 +02:00
if ( operation ) {
startAnimationFn = operation . start ;
}
}
if ( ! startAnimationFn ) {
closeFn ( ) ;
} else {
var animationRunner = startAnimationFn ( ) ;
animationRunner . done ( function ( status ) {
closeFn ( ! status ) ;
} ) ;
updateAnimationRunners ( animationEntry , animationRunner ) ;
}
}
} ) ;
} ) ;
// we need to sort each of the animations in order of parent to child
2015-08-31 17:08:41 +02:00
// relationships. This ensures that the child classes are applied at the
// right time.
$$rAFScheduler ( sortAnimations ( toBeSortedAnimations ) ) ;
2015-08-25 17:46:14 +02:00
} ) ;
return runner ;
// TODO(matsko): change to reference nodes
function getAnchorNodes ( node ) {
var SELECTOR = '[' + NG _ANIMATE _REF _ATTR + ']' ;
var items = node . hasAttribute ( NG _ANIMATE _REF _ATTR )
? [ node ]
: node . querySelectorAll ( SELECTOR ) ;
var anchors = [ ] ;
forEach ( items , function ( node ) {
var attr = node . getAttribute ( NG _ANIMATE _REF _ATTR ) ;
if ( attr && attr . length ) {
anchors . push ( node ) ;
}
} ) ;
return anchors ;
}
function groupAnimations ( animations ) {
var preparedAnimations = [ ] ;
var refLookup = { } ;
forEach ( animations , function ( animation , index ) {
var element = animation . element ;
var node = getDomNode ( element ) ;
var event = animation . event ;
var enterOrMove = [ 'enter' , 'move' ] . indexOf ( event ) >= 0 ;
var anchorNodes = animation . structural ? getAnchorNodes ( node ) : [ ] ;
if ( anchorNodes . length ) {
var direction = enterOrMove ? 'to' : 'from' ;
forEach ( anchorNodes , function ( anchor ) {
var key = anchor . getAttribute ( NG _ANIMATE _REF _ATTR ) ;
refLookup [ key ] = refLookup [ key ] || { } ;
refLookup [ key ] [ direction ] = {
animationID : index ,
element : jqLite ( anchor )
} ;
} ) ;
} else {
preparedAnimations . push ( animation ) ;
}
} ) ;
var usedIndicesLookup = { } ;
var anchorGroups = { } ;
forEach ( refLookup , function ( operations , key ) {
var from = operations . from ;
var to = operations . to ;
if ( ! from || ! to ) {
// only one of these is set therefore we can't have an
// anchor animation since all three pieces are required
var index = from ? from . animationID : to . animationID ;
var indexKey = index . toString ( ) ;
if ( ! usedIndicesLookup [ indexKey ] ) {
usedIndicesLookup [ indexKey ] = true ;
preparedAnimations . push ( animations [ index ] ) ;
}
return ;
}
var fromAnimation = animations [ from . animationID ] ;
var toAnimation = animations [ to . animationID ] ;
var lookupKey = from . animationID . toString ( ) ;
if ( ! anchorGroups [ lookupKey ] ) {
var group = anchorGroups [ lookupKey ] = {
2015-08-31 17:08:41 +02:00
structural : true ,
2015-08-25 17:46:14 +02:00
beforeStart : function ( ) {
fromAnimation . beforeStart ( ) ;
toAnimation . beforeStart ( ) ;
} ,
close : function ( ) {
fromAnimation . close ( ) ;
toAnimation . close ( ) ;
} ,
classes : cssClassesIntersection ( fromAnimation . classes , toAnimation . classes ) ,
from : fromAnimation ,
to : toAnimation ,
anchors : [ ] // TODO(matsko): change to reference nodes
} ;
// the anchor animations require that the from and to elements both have at least
2016-10-12 08:08:16 +02:00
// one shared CSS class which effectively marries the two elements together to use
2015-08-25 17:46:14 +02:00
// the same animation driver and to properly sequence the anchor animation.
if ( group . classes . length ) {
preparedAnimations . push ( group ) ;
} else {
preparedAnimations . push ( fromAnimation ) ;
preparedAnimations . push ( toAnimation ) ;
}
}
anchorGroups [ lookupKey ] . anchors . push ( {
'out' : from . element , 'in' : to . element
} ) ;
} ) ;
return preparedAnimations ;
}
function cssClassesIntersection ( a , b ) {
a = a . split ( ' ' ) ;
b = b . split ( ' ' ) ;
var matches = [ ] ;
for ( var i = 0 ; i < a . length ; i ++ ) {
var aa = a [ i ] ;
if ( aa . substring ( 0 , 3 ) === 'ng-' ) continue ;
for ( var j = 0 ; j < b . length ; j ++ ) {
if ( aa === b [ j ] ) {
matches . push ( aa ) ;
break ;
}
}
}
return matches . join ( ' ' ) ;
}
2015-08-31 17:08:41 +02:00
function invokeFirstDriver ( animationDetails ) {
2015-08-25 17:46:14 +02:00
// we loop in reverse order since the more general drivers (like CSS and JS)
// may attempt more elements, but custom drivers are more particular
for ( var i = drivers . length - 1 ; i >= 0 ; i -- ) {
var driverName = drivers [ i ] ;
var factory = $injector . get ( driverName ) ;
2015-08-31 17:08:41 +02:00
var driver = factory ( animationDetails ) ;
2015-08-25 17:46:14 +02:00
if ( driver ) {
return driver ;
}
}
}
function beforeStart ( ) {
element . addClass ( NG _ANIMATE _CLASSNAME ) ;
if ( tempClasses ) {
$$jqLite . addClass ( element , tempClasses ) ;
}
2016-03-17 07:28:59 +01:00
if ( prepareClassName ) {
$$jqLite . removeClass ( element , prepareClassName ) ;
prepareClassName = null ;
}
2015-08-25 17:46:14 +02:00
}
function updateAnimationRunners ( animation , newRunner ) {
if ( animation . from && animation . to ) {
update ( animation . from . element ) ;
update ( animation . to . element ) ;
} else {
update ( animation . element ) ;
}
function update ( element ) {
2016-10-12 08:08:16 +02:00
var runner = getRunner ( element ) ;
if ( runner ) runner . setHost ( newRunner ) ;
2015-08-25 17:46:14 +02:00
}
}
function handleDestroyedElement ( ) {
var runner = getRunner ( element ) ;
if ( runner && ( event !== 'leave' || ! options . $$domOperationFired ) ) {
runner . end ( ) ;
}
}
2016-11-25 08:43:18 +01:00
function close ( rejected ) {
2015-08-25 17:46:14 +02:00
element . off ( '$destroy' , handleDestroyedElement ) ;
removeRunner ( element ) ;
applyAnimationClasses ( element , options ) ;
applyAnimationStyles ( element , options ) ;
options . domOperation ( ) ;
if ( tempClasses ) {
$$jqLite . removeClass ( element , tempClasses ) ;
}
element . removeClass ( NG _ANIMATE _CLASSNAME ) ;
runner . complete ( ! rejected ) ;
}
} ;
} ] ;
} ] ;
2016-10-12 08:08:16 +02:00
/ * *
* @ ngdoc directive
* @ name ngAnimateSwap
* @ restrict A
* @ scope
*
* @ description
*
* ngAnimateSwap is a animation - oriented directive that allows for the container to
* be removed and entered in whenever the associated expression changes . A
* common usecase for this directive is a rotating banner or slider component which
* contains one image being present at a time . When the active image changes
* then the old image will perform a ` leave ` animation and the new element
* will be inserted via an ` enter ` animation .
*
* @ animations
* | Animation | Occurs |
* | -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- | -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- |
* | { @ link ng . $animate # enter enter } | when the new element is inserted to the DOM |
* | { @ link ng . $animate # leave leave } | when the old element is removed from the DOM |
*
* @ example
* < example name = "ngAnimateSwap-directive" module = "ngAnimateSwapExample"
* deps = "angular-animate.js"
* animations = "true" fixBase = "true" >
* < file name = "index.html" >
* < div class = "container" ng - controller = "AppCtrl" >
* < div ng - animate - swap = "number" class = "cell swap-animation" ng - class = "colorClass(number)" >
* { { number } }
* < / d i v >
* < / d i v >
* < / f i l e >
* < file name = "script.js" >
* angular . module ( 'ngAnimateSwapExample' , [ 'ngAnimate' ] )
* . controller ( 'AppCtrl' , [ '$scope' , '$interval' , function ( $scope , $interval ) {
* $scope . number = 0 ;
* $interval ( function ( ) {
* $scope . number ++ ;
* } , 1000 ) ;
*
* var colors = [ 'red' , 'blue' , 'green' , 'yellow' , 'orange' ] ;
* $scope . colorClass = function ( number ) {
* return colors [ number % colors . length ] ;
* } ;
* } ] ) ;
* < / f i l e >
* < file name = "animations.css" >
* . container {
* height : 250 px ;
* width : 250 px ;
* position : relative ;
* overflow : hidden ;
* border : 2 px solid black ;
* }
* . container . cell {
* font - size : 150 px ;
* text - align : center ;
* line - height : 250 px ;
* position : absolute ;
* top : 0 ;
* left : 0 ;
* right : 0 ;
* border - bottom : 2 px solid black ;
* }
* . swap - animation . ng - enter , . swap - animation . ng - leave {
* transition : 0.5 s linear all ;
* }
* . swap - animation . ng - enter {
* top : - 250 px ;
* }
* . swap - animation . ng - enter - active {
* top : 0 px ;
* }
* . swap - animation . ng - leave {
* top : 0 px ;
* }
* . swap - animation . ng - leave - active {
* top : 250 px ;
* }
* . red { background : red ; }
* . green { background : green ; }
* . blue { background : blue ; }
* . yellow { background : yellow ; }
* . orange { background : orange ; }
* < / f i l e >
* < / e x a m p l e >
* /
var ngAnimateSwapDirective = [ '$animate' , '$rootScope' , function ( $animate , $rootScope ) {
return {
restrict : 'A' ,
transclude : 'element' ,
terminal : true ,
priority : 600 , // we use 600 here to ensure that the directive is caught before others
link : function ( scope , $element , attrs , ctrl , $transclude ) {
var previousElement , previousScope ;
scope . $watchCollection ( attrs . ngAnimateSwap || attrs [ 'for' ] , function ( value ) {
if ( previousElement ) {
$animate . leave ( previousElement ) ;
}
if ( previousScope ) {
previousScope . $destroy ( ) ;
previousScope = null ;
}
if ( value || value === 0 ) {
previousScope = scope . $new ( ) ;
$transclude ( previousScope , function ( element ) {
previousElement = element ;
$animate . enter ( element , null , $element ) ;
} ) ;
}
} ) ;
}
} ;
} ] ;
2015-08-25 17:46:14 +02:00
/ * *
* @ ngdoc module
* @ name ngAnimate
* @ description
*
* The ` ngAnimate ` module provides support for CSS - based animations ( keyframes and transitions ) as well as JavaScript - based animations via
2015-08-31 17:08:41 +02:00
* callback hooks . Animations are not enabled by default , however , by including ` ngAnimate ` the animation hooks are enabled for an Angular app .
2015-08-25 17:46:14 +02:00
*
* < div doc - module - components = "ngAnimate" > < / d i v >
*
* # Usage
* Simply put , there are two ways to make use of animations when ngAnimate is used : by using * * CSS * * and * * JavaScript * * . The former works purely based
* using CSS ( by using matching CSS selectors / styles ) and the latter triggers animations that are registered via ` module.animation() ` . For
* both CSS and JS animations the sole requirement is to have a matching ` CSS class ` that exists both in the registered animation and within
* the HTML element that the animation will be triggered on .
*
* # # Directive Support
* The following directives are "animation aware" :
*
* | Directive | Supported Animations |
* | -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- | -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- |
* | { @ link ng . directive : ngRepeat # animations ngRepeat } | enter , leave and move |
* | { @ link ngRoute . directive : ngView # animations ngView } | enter and leave |
* | { @ link ng . directive : ngInclude # animations ngInclude } | enter and leave |
* | { @ link ng . directive : ngSwitch # animations ngSwitch } | enter and leave |
* | { @ link ng . directive : ngIf # animations ngIf } | enter and leave |
* | { @ link ng . directive : ngClass # animations ngClass } | add and remove ( the CSS class ( es ) present ) |
* | { @ link ng . directive : ngShow # animations ngShow } & { @ link ng . directive : ngHide # animations ngHide } | add and remove ( the ng - hide class value ) |
* | { @ link ng . directive : form # animation - hooks form } & { @ link ng . directive : ngModel # animation - hooks ngModel } | add and remove ( dirty , pristine , valid , invalid & all other validations ) |
* | { @ link module : ngMessages # animations ngMessages } | add and remove ( ng - active & ng - inactive ) |
* | { @ link module : ngMessages # animations ngMessage } | enter and leave |
*
* ( More information can be found by visiting each the documentation associated with each directive . )
*
* # # CSS - based Animations
*
* CSS - based animations with ngAnimate are unique since they require no JavaScript code at all . By using a CSS class that we reference between our HTML
2016-12-17 07:24:00 +01:00
* and CSS code we can create an animation that will be picked up by Angular when an underlying directive performs an operation .
2015-08-25 17:46:14 +02:00
*
2015-08-31 17:08:41 +02:00
* The example below shows how an ` enter ` animation can be made possible on an element using ` ng-if ` :
2015-08-25 17:46:14 +02:00
*
* ` ` ` html
* < div ng - if = "bool" class = "fade" >
* Fade me in out
* < / d i v >
* < button ng - click = "bool=true" > Fade In ! < / b u t t o n >
* < button ng - click = "bool=false" > Fade Out ! < / b u t t o n >
* ` ` `
*
* Notice the CSS class * * fade * * ? We can now create the CSS transition code that references this class :
*
* ` ` ` css
* /* The starting CSS styles for the enter animation */
* . fade . ng - enter {
* transition : 0.5 s linear all ;
* opacity : 0 ;
* }
*
* /* The finishing CSS styles for the enter animation */
* . fade . ng - enter . ng - enter - active {
* opacity : 1 ;
* }
* ` ` `
*
* The key thing to remember here is that , depending on the animation event ( which each of the directives above trigger depending on what ' s going on ) two
* generated CSS classes will be applied to the element ; in the example above we have ` .ng-enter ` and ` .ng-enter-active ` . For CSS transitions , the transition
* code * * must * * be defined within the starting CSS class ( in this case ` .ng-enter ` ) . The destination class is what the transition will animate towards .
*
* If for example we wanted to create animations for ` leave ` and ` move ` ( ngRepeat triggers move ) then we can do so using the same CSS naming conventions :
*
* ` ` ` css
* /* now the element will fade out before it is removed from the DOM */
* . fade . ng - leave {
* transition : 0.5 s linear all ;
* opacity : 1 ;
* }
* . fade . ng - leave . ng - leave - active {
* opacity : 0 ;
* }
* ` ` `
*
* We can also make use of * * CSS Keyframes * * by referencing the keyframe animation within the starting CSS class :
*
* ` ` ` css
* / & # 4 2 ; t h e r e i s n o n e e d t o d e f i n e a n y t h i n g i n s i d e o f t h e d e s t i n a t i o n
* CSS class since the keyframe will take charge of the animation & # 42 ; /
* . fade . ng - leave {
* animation : my _fade _animation 0.5 s linear ;
* - webkit - animation : my _fade _animation 0.5 s linear ;
* }
*
* @ keyframes my _fade _animation {
* from { opacity : 1 ; }
* to { opacity : 0 ; }
* }
*
* @ - webkit - keyframes my _fade _animation {
* from { opacity : 1 ; }
* to { opacity : 0 ; }
* }
* ` ` `
*
* Feel free also mix transitions and keyframes together as well as any other CSS classes on the same element .
*
* # # # CSS Class - based Animations
*
* Class - based animations ( animations that are triggered via ` ngClass ` , ` ngShow ` , ` ngHide ` and some other directives ) have a slightly different
* naming convention . Class - based animations are basic enough that a standard transition or keyframe can be referenced on the class being added
* and removed .
*
* For example if we wanted to do a CSS animation for ` ngHide ` then we place an animation on the ` .ng-hide ` CSS class :
*
* ` ` ` html
* < div ng - show = "bool" class = "fade" >
* Show and hide me
* < / d i v >
2016-10-12 08:08:16 +02:00
* < button ng - click = "bool=!bool" > Toggle < / b u t t o n >
2015-08-25 17:46:14 +02:00
*
* < style >
* . fade . ng - hide {
* transition : 0.5 s linear all ;
* opacity : 0 ;
* }
* < / s t y l e >
* ` ` `
*
* All that is going on here with ngShow / ngHide behind the scenes is the ` .ng-hide ` class is added / removed ( when the hidden state is valid ) . Since
* ngShow and ngHide are animation aware then we can match up a transition and ngAnimate handles the rest .
*
* In addition the addition and removal of the CSS class , ngAnimate also provides two helper methods that we can use to further decorate the animation
* with CSS styles .
*
* ` ` ` html
* < div ng - class = "{on:onOff}" class = "highlight" >
* Highlight this box
* < / d i v >
* < button ng - click = "onOff=!onOff" > Toggle < / b u t t o n >
*
* < style >
* . highlight {
* transition : 0.5 s linear all ;
* }
* . highlight . on - add {
* background : white ;
* }
* . highlight . on {
* background : yellow ;
* }
* . highlight . on - remove {
* background : black ;
* }
* < / s t y l e >
* ` ` `
*
* We can also make use of CSS keyframes by placing them within the CSS classes .
*
*
* # # # CSS Staggering Animations
* A Staggering animation is a collection of animations that are issued with a slight delay in between each successive operation resulting in a
* curtain - like effect . The ngAnimate module ( versions >= 1.2 ) supports staggering animations and the stagger effect can be
* performed by creating a * * ng - EVENT - stagger * * CSS class and attaching that class to the base CSS class used for
* the animation . The style property expected within the stagger class can either be a * * transition - delay * * or an
* * * animation - delay * * property ( or both if your animation contains both transitions and keyframe animations ) .
*
* ` ` ` css
* . my - animation . ng - enter {
* /* standard transition code */
* transition : 1 s linear all ;
* opacity : 0 ;
* }
* . my - animation . ng - enter - stagger {
* /* this will have a 100ms delay between each successive leave animation */
* transition - delay : 0.1 s ;
*
2015-08-31 17:08:41 +02:00
* / & # 4 2 ; A s o f 1 . 4 . 4 , t h i s m u s t a l w a y s b e s e t : i t s i g n a l s n g A n i m a t e
* to not accidentally inherit a delay property from another CSS class & # 42 ; /
2015-08-25 17:46:14 +02:00
* transition - duration : 0 s ;
2017-03-08 15:42:14 +01:00
*
* / & # 4 2 ; i f y o u a r e u s i n g a n i m a t i o n s i n s t e a d o f t r a n s i t i o n s y o u s h o u l d c o n f i g u r e a s f o l l o w s :
* animation - delay : 0.1 s ;
* animation - duration : 0 s ; & # 42 ; /
2015-08-25 17:46:14 +02:00
* }
* . my - animation . ng - enter . ng - enter - active {
* /* standard transition styles */
* opacity : 1 ;
* }
* ` ` `
*
* Staggering animations work by default in ngRepeat ( so long as the CSS class is defined ) . Outside of ngRepeat , to use staggering animations
* on your own , they can be triggered by firing multiple calls to the same event on $animate . However , the restrictions surrounding this
* are that each of the elements must have the same CSS className value as well as the same parent element . A stagger operation
* will also be reset if one or more animation frames have passed since the multiple calls to ` $ animate ` were fired .
*
* The following code will issue the * * ng - leave - stagger * * event on the element provided :
*
* ` ` ` js
* var kids = parent . children ( ) ;
*
* $animate . leave ( kids [ 0 ] ) ; //stagger index=0
* $animate . leave ( kids [ 1 ] ) ; //stagger index=1
* $animate . leave ( kids [ 2 ] ) ; //stagger index=2
* $animate . leave ( kids [ 3 ] ) ; //stagger index=3
* $animate . leave ( kids [ 4 ] ) ; //stagger index=4
*
* window . requestAnimationFrame ( function ( ) {
* //stagger has reset itself
* $animate . leave ( kids [ 5 ] ) ; //stagger index=0
* $animate . leave ( kids [ 6 ] ) ; //stagger index=1
*
* $scope . $digest ( ) ;
* } ) ;
* ` ` `
*
* Stagger animations are currently only supported within CSS - defined animations .
*
* # # # The ` ng-animate ` CSS class
*
* When ngAnimate is animating an element it will apply the ` ng-animate ` CSS class to the element for the duration of the animation .
* This is a temporary CSS class and it will be removed once the animation is over ( for both JavaScript and CSS - based animations ) .
*
* Therefore , animations can be applied to an element using this temporary class directly via CSS .
*
* ` ` ` css
* . zipper . ng - animate {
* transition : 0.5 s linear all ;
* }
* . zipper . ng - enter {
* opacity : 0 ;
* }
* . zipper . ng - enter . ng - enter - active {
* opacity : 1 ;
* }
* . zipper . ng - leave {
* opacity : 1 ;
* }
* . zipper . ng - leave . ng - leave - active {
* opacity : 0 ;
* }
* ` ` `
*
* ( Note that the ` ng-animate ` CSS class is reserved and it cannot be applied on an element directly since ngAnimate will always remove
* the CSS class once an animation has completed . )
*
*
2016-03-17 07:28:59 +01:00
* # # # The ` ng-[event]-prepare ` class
*
* This is a special class that can be used to prevent unwanted flickering / flash of content before
* the actual animation starts . The class is added as soon as an animation is initialized , but removed
* before the actual animation starts ( after waiting for a $digest ) .
* It is also only added for * structural * animations ( ` enter ` , ` move ` , and ` leave ` ) .
*
* In practice , flickering can appear when nesting elements with structural animations such as ` ngIf `
* into elements that have class - based animations such as ` ngClass ` .
*
* ` ` ` html
* < div ng - class = "{red: myProp}" >
* < div ng - class = "{blue: myProp}" >
* < div class = "message" ng - if = "myProp" > < / d i v >
* < / d i v >
* < / d i v >
* ` ` `
*
* It is possible that during the ` enter ` animation , the ` .message ` div will be briefly visible before it starts animating .
* In that case , you can add styles to the CSS that make sure the element stays hidden before the animation starts :
*
* ` ` ` css
* . message . ng - enter - prepare {
* opacity : 0 ;
* }
*
* ` ` `
*
2015-08-25 17:46:14 +02:00
* # # JavaScript - based Animations
*
* ngAnimate also allows for animations to be consumed by JavaScript code . The approach is similar to CSS - based animations ( where there is a shared
* CSS class that is referenced in our HTML code ) but in addition we need to register the JavaScript animation on the module . By making use of the
2016-10-12 08:08:16 +02:00
* ` module.animation() ` module function we can register the animation .
2015-08-25 17:46:14 +02:00
*
* Let ' s see an example of a enter / leave animation using ` ngRepeat ` :
*
* ` ` ` html
* < div ng - repeat = "item in items" class = "slide" >
* { { item } }
* < / d i v >
* ` ` `
*
* See the * * slide * * CSS class ? Let 's use that class to define an animation that we' ll structure in our module code by using ` module.animation ` :
*
* ` ` ` js
* myModule . animation ( '.slide' , [ function ( ) {
* return {
* // make note that other events (like addClass/removeClass)
* // have different function input parameters
* enter : function ( element , doneFn ) {
* jQuery ( element ) . fadeIn ( 1000 , doneFn ) ;
*
* // remember to call doneFn so that angular
* // knows that the animation has concluded
* } ,
*
* move : function ( element , doneFn ) {
* jQuery ( element ) . fadeIn ( 1000 , doneFn ) ;
* } ,
*
* leave : function ( element , doneFn ) {
* jQuery ( element ) . fadeOut ( 1000 , doneFn ) ;
* }
* }
2015-12-04 15:07:07 +01:00
* } ] ) ;
2015-08-25 17:46:14 +02:00
* ` ` `
*
* The nice thing about JS - based animations is that we can inject other services and make use of advanced animation libraries such as
* greensock . js and velocity . js .
*
* If our animation code class - based ( meaning that something like ` ngClass ` , ` ngHide ` and ` ngShow ` triggers it ) then we can still define
* our animations inside of the same registered animation , however , the function input arguments are a bit different :
*
* ` ` ` html
* < div ng - class = "color" class = "colorful" >
* this box is moody
* < / d i v >
* < button ng - click = "color='red'" > Change to red < / b u t t o n >
* < button ng - click = "color='blue'" > Change to blue < / b u t t o n >
* < button ng - click = "color='green'" > Change to green < / b u t t o n >
* ` ` `
*
* ` ` ` js
* myModule . animation ( '.colorful' , [ function ( ) {
* return {
* addClass : function ( element , className , doneFn ) {
* // do some cool animation and call the doneFn
* } ,
* removeClass : function ( element , className , doneFn ) {
* // do some cool animation and call the doneFn
* } ,
* setClass : function ( element , addedClass , removedClass , doneFn ) {
* // do some cool animation and call the doneFn
* }
* }
2015-12-04 15:07:07 +01:00
* } ] ) ;
2015-08-25 17:46:14 +02:00
* ` ` `
*
* # # CSS + JS Animations Together
*
* AngularJS 1.4 and higher has taken steps to make the amalgamation of CSS and JS animations more flexible . However , unlike earlier versions of Angular ,
* defining CSS and JS animations to work off of the same CSS class will not work anymore . Therefore the example below will only result in * * JS animations taking
* charge of the animation * * :
*
* ` ` ` html
* < div ng - if = "bool" class = "slide" >
* Slide in and out
* < / d i v >
* ` ` `
*
* ` ` ` js
* myModule . animation ( '.slide' , [ function ( ) {
* return {
* enter : function ( element , doneFn ) {
* jQuery ( element ) . slideIn ( 1000 , doneFn ) ;
* }
* }
2015-12-04 15:07:07 +01:00
* } ] ) ;
2015-08-25 17:46:14 +02:00
* ` ` `
*
* ` ` ` css
* . slide . ng - enter {
* transition : 0.5 s linear all ;
* transform : translateY ( - 100 px ) ;
* }
* . slide . ng - enter . ng - enter - active {
* transform : translateY ( 0 ) ;
* }
* ` ` `
*
* Does this mean that CSS and JS animations cannot be used together ? Do JS - based animations always have higher priority ? We can make up for the
* lack of CSS animations by using the ` $ animateCss ` service to trigger our own tweaked - out , CSS - based animations directly from
* our own JS - based animation code :
*
* ` ` ` js
* myModule . animation ( '.slide' , [ '$animateCss' , function ( $animateCss ) {
* return {
2015-12-04 15:07:07 +01:00
* enter : function ( element ) {
2015-08-25 17:46:14 +02:00
* // this will trigger `.slide.ng-enter` and `.slide.ng-enter-active`.
2015-12-04 15:07:07 +01:00
* return $animateCss ( element , {
2015-08-25 17:46:14 +02:00
* event : 'enter' ,
* structural : true
2015-12-04 15:07:07 +01:00
* } ) ;
2015-08-25 17:46:14 +02:00
* }
* }
2015-12-04 15:07:07 +01:00
* } ] ) ;
2015-08-25 17:46:14 +02:00
* ` ` `
*
* 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 3 rd - party animation framework .
*
* The ` $ animateCss ` service is very powerful since we can feed in all kinds of extra properties that will be evaluated and fed into a CSS transition or
* keyframe animation . For example if we wanted to animate the height of an element while adding and removing classes then we can do so by providing that
* data into ` $ animateCss ` directly :
*
* ` ` ` js
* myModule . animation ( '.slide' , [ '$animateCss' , function ( $animateCss ) {
* return {
2015-12-04 15:07:07 +01:00
* enter : function ( element ) {
* return $animateCss ( element , {
2015-08-25 17:46:14 +02:00
* event : 'enter' ,
* structural : true ,
* addClass : 'maroon-setting' ,
* from : { height : 0 } ,
* to : { height : 200 }
2015-12-04 15:07:07 +01:00
* } ) ;
2015-08-25 17:46:14 +02:00
* }
* }
2015-12-04 15:07:07 +01:00
* } ] ) ;
2015-08-25 17:46:14 +02:00
* ` ` `
*
* Now we can fill in the rest via our transition CSS code :
*
* ` ` ` css
* /* the transition tells ngAnimate to make the animation happen */
* . slide . ng - enter { transition : 0.5 s linear all ; }
*
* / & # 4 2 ; t h i s e x t r a C S S c l a s s w i l l b e a b s o r b e d i n t o t h e t r a n s i t i o n
* since the $animateCss code is adding the class & # 42 ; /
* . maroon - setting { background : red ; }
* ` ` `
*
* And ` $ animateCss ` will figure out the rest . Just make sure to have the ` done() ` callback fire the ` doneFn ` function to signal when the animation is over .
*
* To learn more about what ' s possible be sure to visit the { @ link ngAnimate . $animateCss $animateCss service } .
*
* # # Animation Anchoring ( via ` ng-animate-ref ` )
*
* ngAnimate in AngularJS 1.4 comes packed with the ability to cross - animate elements between
* structural areas of an application ( like views ) by pairing up elements using an attribute
* called ` ng-animate-ref ` .
*
* Let ' s say for example we have two views that are managed by ` ng-view ` and we want to show
* that there is a relationship between two components situated in within these views . By using the
* ` ng-animate-ref ` attribute we can identify that the two components are paired together and we
* can then attach an animation , which is triggered when the view changes .
*
* Say for example we have the following template code :
*
* ` ` ` html
* <!-- index . html -- >
* < div ng - view class = "view-animation" >
* < / d i v >
*
* <!-- home . html -- >
* < a href = "#/banner-page" >
* < img src = "./banner.jpg" class = "banner" ng - animate - ref = "banner" >
* < / a >
*
* <!-- banner - page . html -- >
* < img src = "./banner.jpg" class = "banner" ng - animate - ref = "banner" >
* ` ` `
*
* Now , when the view changes ( once the link is clicked ) , ngAnimate will examine the
* HTML contents to see if there is a match reference between any components in the view
* that is leaving and the view that is entering . It will scan both the view which is being
* removed ( leave ) and inserted ( enter ) to see if there are any paired DOM elements that
* contain a matching ref value .
*
* The two images match since they share the same ref value . ngAnimate will now create a
* transport element ( which is a clone of the first image element ) and it will then attempt
* to animate to the position of the second image element in the next view . For the animation to
* work a special CSS class called ` ng-anchor ` will be added to the transported element .
*
* We can now attach a transition onto the ` .banner.ng-anchor ` CSS class and then
* ngAnimate will handle the entire transition for us as well as the addition and removal of
* any changes of CSS classes between the elements :
*
* ` ` ` css
* . banner . ng - anchor {
* / & # 4 2 ; t h i s a n i m a t i o n w i l l l a s t f o r 1 s e c o n d s i n c e t h e r e a r e
* two phases to the animation ( an ` in ` and an ` out ` phase ) & # 42 ; /
* transition : 0.5 s linear all ;
* }
* ` ` `
*
* We also * * must * * include animations for the views that are being entered and removed
* ( otherwise anchoring wouldn ' t be possible since the new view would be inserted right away ) .
*
* ` ` ` css
* . view - animation . ng - enter , . view - animation . ng - leave {
* transition : 0.5 s linear all ;
* position : fixed ;
* left : 0 ;
* top : 0 ;
* width : 100 % ;
* }
* . view - animation . ng - enter {
* transform : translateX ( 100 % ) ;
* }
* . view - animation . ng - leave ,
* . view - animation . ng - enter . ng - enter - active {
* transform : translateX ( 0 % ) ;
* }
* . view - animation . ng - leave . ng - leave - active {
* transform : translateX ( - 100 % ) ;
* }
* ` ` `
*
* Now we can jump back to the anchor animation . When the animation happens , there are two stages that occur :
* an ` out ` and an ` in ` stage . The ` out ` stage happens first and that is when the element is animated away
* from its origin . Once that animation is over then the ` in ` stage occurs which animates the
* element to its destination . The reason why there are two animations is to give enough time
* for the enter animation on the new element to be ready .
*
* The example above sets up a transition for both the in and out phases , but we can also target the out or
* in phases directly via ` ng-anchor-out ` and ` ng-anchor-in ` .
*
* ` ` ` css
* . banner . ng - anchor - out {
* transition : 0.5 s linear all ;
*
* / & # 4 2 ; t h e s c a l e w i l l b e a p p l i e d d u r i n g t h e o u t a n i m a t i o n ,
* but will be animated away when the in animation runs & # 42 ; /
* transform : scale ( 1.2 ) ;
* }
*
* . banner . ng - anchor - in {
* transition : 1 s linear all ;
* }
* ` ` `
*
*
*
*
* # # # Anchoring Demo
*
< example module = "anchoringExample"
name = "anchoringExample"
id = "anchoringExample"
deps = "angular-animate.js;angular-route.js"
animations = "true" >
< file name = "index.html" >
2017-01-12 07:31:09 +01:00
< a href = "#!/" > Home < / a >
2015-08-25 17:46:14 +02:00
< hr / >
< div class = "view-container" >
< div ng - view class = "view" > < / d i v >
< / d i v >
< / f i l e >
< file name = "script.js" >
angular . module ( 'anchoringExample' , [ 'ngAnimate' , 'ngRoute' ] )
. config ( [ '$routeProvider' , function ( $routeProvider ) {
$routeProvider . when ( '/' , {
templateUrl : 'home.html' ,
controller : 'HomeController as home'
} ) ;
$routeProvider . when ( '/profile/:id' , {
templateUrl : 'profile.html' ,
controller : 'ProfileController as profile'
} ) ;
} ] )
. run ( [ '$rootScope' , function ( $rootScope ) {
$rootScope . records = [
2016-11-25 08:43:18 +01:00
{ id : 1 , title : 'Miss Beulah Roob' } ,
{ id : 2 , title : 'Trent Morissette' } ,
{ id : 3 , title : 'Miss Ava Pouros' } ,
{ id : 4 , title : 'Rod Pouros' } ,
{ id : 5 , title : 'Abdul Rice' } ,
{ id : 6 , title : 'Laurie Rutherford Sr.' } ,
{ id : 7 , title : 'Nakia McLaughlin' } ,
{ id : 8 , title : 'Jordon Blanda DVM' } ,
{ id : 9 , title : 'Rhoda Hand' } ,
{ id : 10 , title : 'Alexandrea Sauer' }
2015-08-25 17:46:14 +02:00
] ;
} ] )
. controller ( 'HomeController' , [ function ( ) {
//empty
} ] )
2016-11-25 08:43:18 +01:00
. controller ( 'ProfileController' , [ '$rootScope' , '$routeParams' ,
function ProfileController ( $rootScope , $routeParams ) {
2015-08-25 17:46:14 +02:00
var index = parseInt ( $routeParams . id , 10 ) ;
var record = $rootScope . records [ index - 1 ] ;
this . title = record . title ;
this . id = record . id ;
} ] ) ;
< / f i l e >
< file name = "home.html" >
< h2 > Welcome to the home page < / h 1 >
< p > Please click on an element < / p >
< a class = "record"
2017-01-12 07:31:09 +01:00
ng - href = "#!/profile/{{ record.id }}"
2015-08-25 17:46:14 +02:00
ng - animate - ref = "{{ record.id }}"
ng - repeat = "record in records" >
{ { record . title } }
< / a >
< / f i l e >
< file name = "profile.html" >
< div class = "profile record" ng - animate - ref = "{{ profile.id }}" >
{ { profile . title } }
< / d i v >
< / f i l e >
< file name = "animations.css" >
. record {
display : block ;
font - size : 20 px ;
}
. profile {
background : black ;
color : white ;
font - size : 100 px ;
}
. view - container {
position : relative ;
}
. view - container > . view . ng - animate {
position : absolute ;
top : 0 ;
left : 0 ;
width : 100 % ;
min - height : 500 px ;
}
. view . ng - enter , . view . ng - leave ,
. record . ng - anchor {
transition : 0.5 s linear all ;
}
. view . ng - enter {
transform : translateX ( 100 % ) ;
}
. view . ng - enter . ng - enter - active , . view . ng - leave {
transform : translateX ( 0 % ) ;
}
. view . ng - leave . ng - leave - active {
transform : translateX ( - 100 % ) ;
}
. record . ng - anchor - out {
background : red ;
}
< / f i l e >
< / e x a m p l e >
*
* # # # How is the element transported ?
*
* When an anchor animation occurs , ngAnimate will clone the starting element and position it exactly where the starting
* element is located on screen via absolute positioning . The cloned element will be placed inside of the root element
* of the application ( where ng - app was defined ) and all of the CSS classes of the starting element will be applied . The
* element will then animate into the ` out ` and ` in ` animations and will eventually reach the coordinates and match
* the dimensions of the destination element . During the entire animation a CSS class of ` .ng-animate-shim ` will be applied
* to both the starting and destination elements in order to hide them from being visible ( the CSS styling for the class
* is : ` visibility:hidden ` ) . Once the anchor reaches its destination then it will be removed and the destination element
* will become visible since the shim class will be removed .
*
* # # # How is the morphing handled ?
*
* CSS Anchoring relies on transitions and keyframes and the internal code is intelligent enough to figure out
* what CSS classes differ between the starting element and the destination element . These different CSS classes
* will be added / removed on the anchor element and a transition will be applied ( the transition that is provided
* in the anchor class ) . Long story short , ngAnimate will figure out what classes to add and remove which will
* make the transition of the element as smooth and automatic as possible . Be sure to use simple CSS classes that
* do not rely on DOM nesting structure so that the anchor element appears the same as the starting element ( since
* the cloned element is placed inside of root element which is likely close to the body element ) .
*
* Note that if the root element is on the ` <html> ` element then the cloned node will be placed inside of body .
*
*
* # # Using $animate in your directive code
*
* So far we ' ve explored how to feed in animations into an Angular application , but how do we trigger animations within our own directives in our application ?
* By injecting the ` $ animate ` service into our directive code , we can trigger structural and class - based hooks which can then be consumed by animations . Let ' s
* imagine we have a greeting box that shows and hides itself when the data changes
*
* ` ` ` html
* < greeting - box active = "onOrOff" > Hi there < / g r e e t i n g - b o x >
* ` ` `
*
* ` ` ` js
* ngModule . directive ( 'greetingBox' , [ '$animate' , function ( $animate ) {
* return function ( scope , element , attrs ) {
* attrs . $observe ( 'active' , function ( value ) {
* value ? $animate . addClass ( element , 'on' ) : $animate . removeClass ( element , 'on' ) ;
* } ) ;
* } ) ;
* } ] ) ;
* ` ` `
*
* Now the ` on ` CSS class is added and removed on the greeting box component . Now if we add a CSS class on top of the greeting box element
* in our HTML code then we can trigger a CSS or JS animation to happen .
*
* ` ` ` css
* /* normally we would create a CSS class to reference on the element */
* greeting - box . on { transition : 0.5 s linear all ; background : green ; color : white ; }
* ` ` `
*
* The ` $ animate ` service contains a variety of other methods like ` enter ` , ` leave ` , ` animate ` and ` setClass ` . To learn more about what ' s
* possible be sure to visit the { @ link ng . $animate $animate service API page } .
*
*
* # # Callbacks and Promises
*
* When ` $ animate ` is called it returns a promise that can be used to capture when the animation has ended . Therefore if we were to trigger
* an animation ( within our directive code ) then we can continue performing directive and scope related activities after the animation has
* ended by chaining onto the returned promise that animation method returns .
*
* ` ` ` js
* // somewhere within the depths of the directive
* $animate . enter ( element , parent ) . then ( function ( ) {
* //the animation has completed
* } ) ;
* ` ` `
*
* ( Note that earlier versions of Angular prior to v1 . 4 required the promise code to be wrapped using ` $ scope. $ apply(...) ` . This is not the case
* anymore . )
*
* In addition to the animation promise , we can also make use of animation - related callbacks within our directives and controller code by registering
* an event listener using the ` $ animate ` service . Let ' s say for example that an animation was triggered on our view
* routing controller to hook into that :
*
* ` ` ` js
* ngModule . controller ( 'HomePageController' , [ '$animate' , function ( $animate ) {
* $animate . on ( 'enter' , ngViewElement , function ( element ) {
* // the animation for this route has completed
* } ] ) ;
* } ] )
* ` ` `
*
* ( Note that you will need to trigger a digest within the callback to get angular to notice any scope - related changes . )
* /
2016-10-12 08:08:16 +02:00
var copy ;
var extend ;
var forEach ;
var isArray ;
var isDefined ;
var isElement ;
var isFunction ;
var isObject ;
var isString ;
var isUndefined ;
var jqLite ;
var noop ;
2015-08-25 17:46:14 +02:00
/ * *
* @ ngdoc service
* @ name $animate
* @ kind object
*
* @ description
* The ngAnimate ` $ animate ` service documentation is the same for the core ` $ animate ` service .
*
2015-08-31 17:08:41 +02:00
* Click here { @ link ng . $animate to learn more about animations with ` $ animate ` } .
2015-08-25 17:46:14 +02:00
* /
2016-10-12 08:08:16 +02:00
angular . module ( 'ngAnimate' , [ ] , function initAngularHelpers ( ) {
// Access helpers from angular core.
// Do it inside a `config` block to ensure `window.angular` is available.
noop = angular . noop ;
copy = angular . copy ;
extend = angular . extend ;
jqLite = angular . element ;
forEach = angular . forEach ;
isArray = angular . isArray ;
isString = angular . isString ;
isObject = angular . isObject ;
isUndefined = angular . isUndefined ;
isDefined = angular . isDefined ;
isFunction = angular . isFunction ;
isElement = angular . isElement ;
} )
2017-07-04 07:24:12 +02:00
. info ( { angularVersion : '1.6.5' } )
2016-10-12 08:08:16 +02:00
. directive ( 'ngAnimateSwap' , ngAnimateSwapDirective )
2015-08-25 17:46:14 +02:00
. directive ( 'ngAnimateChildren' , $$AnimateChildrenDirective )
2015-08-31 17:08:41 +02:00
. factory ( '$$rAFScheduler' , $$rAFSchedulerFactory )
2015-08-25 17:46:14 +02:00
. provider ( '$$animateQueue' , $$AnimateQueueProvider )
. provider ( '$$animation' , $$AnimationProvider )
. provider ( '$animateCss' , $AnimateCssProvider )
. provider ( '$$animateCssDriver' , $$AnimateCssDriverProvider )
. provider ( '$$animateJs' , $$AnimateJsProvider )
. provider ( '$$animateJsDriver' , $$AnimateJsDriverProvider ) ;
2015-08-07 16:37:36 +02:00
} ) ( window , window . angular ) ;