AMD-ify jQuery sourcegit s! Woo! Fixes #14113, #14163.
[jquery.git] / src / effects.js
blobb936019fa5ff7a4bfd5e462c198dd8dd691cce19
1 define([
2         "./core",
3         "./var/pnum",
4         "./css/var/cssExpand",
5         "./css/var/isHidden",
6         "./effects/Tween",
7         "./queue",
8         "./css",
9         "./deferred",
10         "./traversing"
11 ], function( jQuery, pnum, cssExpand, isHidden, Tween ) {
13 var fxNow, timerId,
14         rfxtypes = /^(?:toggle|show|hide)$/,
15         rfxnum = new RegExp( "^(?:([+-])=|)(" + pnum + ")([a-z%]*)$", "i" ),
16         rrun = /queueHooks$/,
17         animationPrefilters = [ defaultPrefilter ],
18         tweeners = {
19                 "*": [function( prop, value ) {
20                         var tween = this.createTween( prop, value ),
21                                 target = tween.cur(),
22                                 parts = rfxnum.exec( value ),
23                                 unit = parts && parts[ 3 ] || ( jQuery.cssNumber[ prop ] ? "" : "px" ),
25                                 // Starting value computation is required for potential unit mismatches
26                                 start = ( jQuery.cssNumber[ prop ] || unit !== "px" && +target ) &&
27                                         rfxnum.exec( jQuery.css( tween.elem, prop ) ),
28                                 scale = 1,
29                                 maxIterations = 20;
31                         if ( start && start[ 3 ] !== unit ) {
32                                 // Trust units reported by jQuery.css
33                                 unit = unit || start[ 3 ];
35                                 // Make sure we update the tween properties later on
36                                 parts = parts || [];
38                                 // Iteratively approximate from a nonzero starting point
39                                 start = +target || 1;
41                                 do {
42                                         // If previous iteration zeroed out, double until we get *something*
43                                         // Use a string for doubling factor so we don't accidentally see scale as unchanged below
44                                         scale = scale || ".5";
46                                         // Adjust and apply
47                                         start = start / scale;
48                                         jQuery.style( tween.elem, prop, start + unit );
50                                 // Update scale, tolerating zero or NaN from tween.cur()
51                                 // And breaking the loop if scale is unchanged or perfect, or if we've just had enough
52                                 } while ( scale !== (scale = tween.cur() / target) && scale !== 1 && --maxIterations );
53                         }
55                         // Update tween properties
56                         if ( parts ) {
57                                 start = tween.start = +start || +target || 0;
58                                 tween.unit = unit;
59                                 // If a +=/-= token was provided, we're doing a relative animation
60                                 tween.end = parts[ 1 ] ?
61                                         start + ( parts[ 1 ] + 1 ) * parts[ 2 ] :
62                                         +parts[ 2 ];
63                         }
65                         return tween;
66                 }]
67         };
69 // Animations created synchronously will run synchronously
70 function createFxNow() {
71         setTimeout(function() {
72                 fxNow = undefined;
73         });
74         return ( fxNow = jQuery.now() );
77 function createTween( value, prop, animation ) {
78         var tween,
79                 collection = ( tweeners[ prop ] || [] ).concat( tweeners[ "*" ] ),
80                 index = 0,
81                 length = collection.length;
82         for ( ; index < length; index++ ) {
83                 if ( (tween = collection[ index ].call( animation, prop, value )) ) {
85                         // we're done with this property
86                         return tween;
87                 }
88         }
91 function Animation( elem, properties, options ) {
92         var result,
93                 stopped,
94                 index = 0,
95                 length = animationPrefilters.length,
96                 deferred = jQuery.Deferred().always( function() {
97                         // don't match elem in the :animated selector
98                         delete tick.elem;
99                 }),
100                 tick = function() {
101                         if ( stopped ) {
102                                 return false;
103                         }
104                         var currentTime = fxNow || createFxNow(),
105                                 remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ),
106                                 // archaic crash bug won't allow us to use 1 - ( 0.5 || 0 ) (#12497)
107                                 temp = remaining / animation.duration || 0,
108                                 percent = 1 - temp,
109                                 index = 0,
110                                 length = animation.tweens.length;
112                         for ( ; index < length ; index++ ) {
113                                 animation.tweens[ index ].run( percent );
114                         }
116                         deferred.notifyWith( elem, [ animation, percent, remaining ]);
118                         if ( percent < 1 && length ) {
119                                 return remaining;
120                         } else {
121                                 deferred.resolveWith( elem, [ animation ] );
122                                 return false;
123                         }
124                 },
125                 animation = deferred.promise({
126                         elem: elem,
127                         props: jQuery.extend( {}, properties ),
128                         opts: jQuery.extend( true, { specialEasing: {} }, options ),
129                         originalProperties: properties,
130                         originalOptions: options,
131                         startTime: fxNow || createFxNow(),
132                         duration: options.duration,
133                         tweens: [],
134                         createTween: function( prop, end ) {
135                                 var tween = jQuery.Tween( elem, animation.opts, prop, end,
136                                                 animation.opts.specialEasing[ prop ] || animation.opts.easing );
137                                 animation.tweens.push( tween );
138                                 return tween;
139                         },
140                         stop: function( gotoEnd ) {
141                                 var index = 0,
142                                         // if we are going to the end, we want to run all the tweens
143                                         // otherwise we skip this part
144                                         length = gotoEnd ? animation.tweens.length : 0;
145                                 if ( stopped ) {
146                                         return this;
147                                 }
148                                 stopped = true;
149                                 for ( ; index < length ; index++ ) {
150                                         animation.tweens[ index ].run( 1 );
151                                 }
153                                 // resolve when we played the last frame
154                                 // otherwise, reject
155                                 if ( gotoEnd ) {
156                                         deferred.resolveWith( elem, [ animation, gotoEnd ] );
157                                 } else {
158                                         deferred.rejectWith( elem, [ animation, gotoEnd ] );
159                                 }
160                                 return this;
161                         }
162                 }),
163                 props = animation.props;
165         propFilter( props, animation.opts.specialEasing );
167         for ( ; index < length ; index++ ) {
168                 result = animationPrefilters[ index ].call( animation, elem, props, animation.opts );
169                 if ( result ) {
170                         return result;
171                 }
172         }
174         jQuery.map( props, createTween, animation );
176         if ( jQuery.isFunction( animation.opts.start ) ) {
177                 animation.opts.start.call( elem, animation );
178         }
180         jQuery.fx.timer(
181                 jQuery.extend( tick, {
182                         elem: elem,
183                         anim: animation,
184                         queue: animation.opts.queue
185                 })
186         );
188         // attach callbacks from options
189         return animation.progress( animation.opts.progress )
190                 .done( animation.opts.done, animation.opts.complete )
191                 .fail( animation.opts.fail )
192                 .always( animation.opts.always );
195 function propFilter( props, specialEasing ) {
196         var index, name, easing, value, hooks;
198         // camelCase, specialEasing and expand cssHook pass
199         for ( index in props ) {
200                 name = jQuery.camelCase( index );
201                 easing = specialEasing[ name ];
202                 value = props[ index ];
203                 if ( jQuery.isArray( value ) ) {
204                         easing = value[ 1 ];
205                         value = props[ index ] = value[ 0 ];
206                 }
208                 if ( index !== name ) {
209                         props[ name ] = value;
210                         delete props[ index ];
211                 }
213                 hooks = jQuery.cssHooks[ name ];
214                 if ( hooks && "expand" in hooks ) {
215                         value = hooks.expand( value );
216                         delete props[ name ];
218                         // not quite $.extend, this wont overwrite keys already present.
219                         // also - reusing 'index' from above because we have the correct "name"
220                         for ( index in value ) {
221                                 if ( !( index in props ) ) {
222                                         props[ index ] = value[ index ];
223                                         specialEasing[ index ] = easing;
224                                 }
225                         }
226                 } else {
227                         specialEasing[ name ] = easing;
228                 }
229         }
232 jQuery.Animation = jQuery.extend( Animation, {
234         tweener: function( props, callback ) {
235                 if ( jQuery.isFunction( props ) ) {
236                         callback = props;
237                         props = [ "*" ];
238                 } else {
239                         props = props.split(" ");
240                 }
242                 var prop,
243                         index = 0,
244                         length = props.length;
246                 for ( ; index < length ; index++ ) {
247                         prop = props[ index ];
248                         tweeners[ prop ] = tweeners[ prop ] || [];
249                         tweeners[ prop ].unshift( callback );
250                 }
251         },
253         prefilter: function( callback, prepend ) {
254                 if ( prepend ) {
255                         animationPrefilters.unshift( callback );
256                 } else {
257                         animationPrefilters.push( callback );
258                 }
259         }
262 function defaultPrefilter( elem, props, opts ) {
263         /* jshint validthis: true */
264         var prop, value, toggle, tween, hooks, oldfire,
265                 anim = this,
266                 orig = {},
267                 style = elem.style,
268                 hidden = elem.nodeType && isHidden( elem ),
269                 dataShow = jQuery._data( elem, "fxshow" );
271         // handle queue: false promises
272         if ( !opts.queue ) {
273                 hooks = jQuery._queueHooks( elem, "fx" );
274                 if ( hooks.unqueued == null ) {
275                         hooks.unqueued = 0;
276                         oldfire = hooks.empty.fire;
277                         hooks.empty.fire = function() {
278                                 if ( !hooks.unqueued ) {
279                                         oldfire();
280                                 }
281                         };
282                 }
283                 hooks.unqueued++;
285                 anim.always(function() {
286                         // doing this makes sure that the complete handler will be called
287                         // before this completes
288                         anim.always(function() {
289                                 hooks.unqueued--;
290                                 if ( !jQuery.queue( elem, "fx" ).length ) {
291                                         hooks.empty.fire();
292                                 }
293                         });
294                 });
295         }
297         // height/width overflow pass
298         if ( elem.nodeType === 1 && ( "height" in props || "width" in props ) ) {
299                 // Make sure that nothing sneaks out
300                 // Record all 3 overflow attributes because IE does not
301                 // change the overflow attribute when overflowX and
302                 // overflowY are set to the same value
303                 opts.overflow = [ style.overflow, style.overflowX, style.overflowY ];
305                 // Set display property to inline-block for height/width
306                 // animations on inline elements that are having width/height animated
307                 if ( jQuery.css( elem, "display" ) === "inline" &&
308                                 jQuery.css( elem, "float" ) === "none" ) {
310                         // inline-level elements accept inline-block;
311                         // block-level elements need to be inline with layout
312                         if ( !jQuery.support.inlineBlockNeedsLayout || defaultDisplay( elem.nodeName ) === "inline" ) {
313                                 style.display = "inline-block";
315                         } else {
316                                 style.zoom = 1;
317                         }
318                 }
319         }
321         if ( opts.overflow ) {
322                 style.overflow = "hidden";
323                 if ( !jQuery.support.shrinkWrapBlocks ) {
324                         anim.always(function() {
325                                 style.overflow = opts.overflow[ 0 ];
326                                 style.overflowX = opts.overflow[ 1 ];
327                                 style.overflowY = opts.overflow[ 2 ];
328                         });
329                 }
330         }
333         // show/hide pass
334         for ( prop in props ) {
335                 value = props[ prop ];
336                 if ( rfxtypes.exec( value ) ) {
337                         delete props[ prop ];
338                         toggle = toggle || value === "toggle";
339                         if ( value === ( hidden ? "hide" : "show" ) ) {
340                                 continue;
341                         }
342                         orig[ prop ] = dataShow && dataShow[ prop ] || jQuery.style( elem, prop );
343                 }
344         }
346         if ( !jQuery.isEmptyObject( orig ) ) {
347                 if ( dataShow ) {
348                         if ( "hidden" in dataShow ) {
349                                 hidden = dataShow.hidden;
350                         }
351                 } else {
352                         dataShow = jQuery._data( elem, "fxshow", {} );
353                 }
355                 // store state if its toggle - enables .stop().toggle() to "reverse"
356                 if ( toggle ) {
357                         dataShow.hidden = !hidden;
358                 }
359                 if ( hidden ) {
360                         jQuery( elem ).show();
361                 } else {
362                         anim.done(function() {
363                                 jQuery( elem ).hide();
364                         });
365                 }
366                 anim.done(function() {
367                         var prop;
368                         jQuery._removeData( elem, "fxshow" );
369                         for ( prop in orig ) {
370                                 jQuery.style( elem, prop, orig[ prop ] );
371                         }
372                 });
373                 for ( prop in orig ) {
374                         tween = createTween( hidden ? dataShow[ prop ] : 0, prop, anim );
376                         if ( !( prop in dataShow ) ) {
377                                 dataShow[ prop ] = tween.start;
378                                 if ( hidden ) {
379                                         tween.end = tween.start;
380                                         tween.start = prop === "width" || prop === "height" ? 1 : 0;
381                                 }
382                         }
383                 }
384         }
387 jQuery.each([ "toggle", "show", "hide" ], function( i, name ) {
388         var cssFn = jQuery.fn[ name ];
389         jQuery.fn[ name ] = function( speed, easing, callback ) {
390                 return speed == null || typeof speed === "boolean" ?
391                         cssFn.apply( this, arguments ) :
392                         this.animate( genFx( name, true ), speed, easing, callback );
393         };
396 jQuery.fn.extend({
397         fadeTo: function( speed, to, easing, callback ) {
399                 // show any hidden elements after setting opacity to 0
400                 return this.filter( isHidden ).css( "opacity", 0 ).show()
402                         // animate to the value specified
403                         .end().animate({ opacity: to }, speed, easing, callback );
404         },
405         animate: function( prop, speed, easing, callback ) {
406                 var empty = jQuery.isEmptyObject( prop ),
407                         optall = jQuery.speed( speed, easing, callback ),
408                         doAnimation = function() {
409                                 // Operate on a copy of prop so per-property easing won't be lost
410                                 var anim = Animation( this, jQuery.extend( {}, prop ), optall );
412                                 // Empty animations, or finishing resolves immediately
413                                 if ( empty || jQuery._data( this, "finish" ) ) {
414                                         anim.stop( true );
415                                 }
416                         };
417                         doAnimation.finish = doAnimation;
419                 return empty || optall.queue === false ?
420                         this.each( doAnimation ) :
421                         this.queue( optall.queue, doAnimation );
422         },
423         stop: function( type, clearQueue, gotoEnd ) {
424                 var stopQueue = function( hooks ) {
425                         var stop = hooks.stop;
426                         delete hooks.stop;
427                         stop( gotoEnd );
428                 };
430                 if ( typeof type !== "string" ) {
431                         gotoEnd = clearQueue;
432                         clearQueue = type;
433                         type = undefined;
434                 }
435                 if ( clearQueue && type !== false ) {
436                         this.queue( type || "fx", [] );
437                 }
439                 return this.each(function() {
440                         var dequeue = true,
441                                 index = type != null && type + "queueHooks",
442                                 timers = jQuery.timers,
443                                 data = jQuery._data( this );
445                         if ( index ) {
446                                 if ( data[ index ] && data[ index ].stop ) {
447                                         stopQueue( data[ index ] );
448                                 }
449                         } else {
450                                 for ( index in data ) {
451                                         if ( data[ index ] && data[ index ].stop && rrun.test( index ) ) {
452                                                 stopQueue( data[ index ] );
453                                         }
454                                 }
455                         }
457                         for ( index = timers.length; index--; ) {
458                                 if ( timers[ index ].elem === this && (type == null || timers[ index ].queue === type) ) {
459                                         timers[ index ].anim.stop( gotoEnd );
460                                         dequeue = false;
461                                         timers.splice( index, 1 );
462                                 }
463                         }
465                         // start the next in the queue if the last step wasn't forced
466                         // timers currently will call their complete callbacks, which will dequeue
467                         // but only if they were gotoEnd
468                         if ( dequeue || !gotoEnd ) {
469                                 jQuery.dequeue( this, type );
470                         }
471                 });
472         },
473         finish: function( type ) {
474                 if ( type !== false ) {
475                         type = type || "fx";
476                 }
477                 return this.each(function() {
478                         var index,
479                                 data = jQuery._data( this ),
480                                 queue = data[ type + "queue" ],
481                                 hooks = data[ type + "queueHooks" ],
482                                 timers = jQuery.timers,
483                                 length = queue ? queue.length : 0;
485                         // enable finishing flag on private data
486                         data.finish = true;
488                         // empty the queue first
489                         jQuery.queue( this, type, [] );
491                         if ( hooks && hooks.stop ) {
492                                 hooks.stop.call( this, true );
493                         }
495                         // look for any active animations, and finish them
496                         for ( index = timers.length; index--; ) {
497                                 if ( timers[ index ].elem === this && timers[ index ].queue === type ) {
498                                         timers[ index ].anim.stop( true );
499                                         timers.splice( index, 1 );
500                                 }
501                         }
503                         // look for any animations in the old queue and finish them
504                         for ( index = 0; index < length; index++ ) {
505                                 if ( queue[ index ] && queue[ index ].finish ) {
506                                         queue[ index ].finish.call( this );
507                                 }
508                         }
510                         // turn off finishing flag
511                         delete data.finish;
512                 });
513         }
516 // Generate parameters to create a standard animation
517 function genFx( type, includeWidth ) {
518         var which,
519                 attrs = { height: type },
520                 i = 0;
522         // if we include width, step value is 1 to do all cssExpand values,
523         // if we don't include width, step value is 2 to skip over Left and Right
524         includeWidth = includeWidth? 1 : 0;
525         for( ; i < 4 ; i += 2 - includeWidth ) {
526                 which = cssExpand[ i ];
527                 attrs[ "margin" + which ] = attrs[ "padding" + which ] = type;
528         }
530         if ( includeWidth ) {
531                 attrs.opacity = attrs.width = type;
532         }
534         return attrs;
537 // Generate shortcuts for custom animations
538 jQuery.each({
539         slideDown: genFx("show"),
540         slideUp: genFx("hide"),
541         slideToggle: genFx("toggle"),
542         fadeIn: { opacity: "show" },
543         fadeOut: { opacity: "hide" },
544         fadeToggle: { opacity: "toggle" }
545 }, function( name, props ) {
546         jQuery.fn[ name ] = function( speed, easing, callback ) {
547                 return this.animate( props, speed, easing, callback );
548         };
551 jQuery.speed = function( speed, easing, fn ) {
552         var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : {
553                 complete: fn || !fn && easing ||
554                         jQuery.isFunction( speed ) && speed,
555                 duration: speed,
556                 easing: fn && easing || easing && !jQuery.isFunction( easing ) && easing
557         };
559         opt.duration = jQuery.fx.off ? 0 : typeof opt.duration === "number" ? opt.duration :
560                 opt.duration in jQuery.fx.speeds ? jQuery.fx.speeds[ opt.duration ] : jQuery.fx.speeds._default;
562         // normalize opt.queue - true/undefined/null -> "fx"
563         if ( opt.queue == null || opt.queue === true ) {
564                 opt.queue = "fx";
565         }
567         // Queueing
568         opt.old = opt.complete;
570         opt.complete = function() {
571                 if ( jQuery.isFunction( opt.old ) ) {
572                         opt.old.call( this );
573                 }
575                 if ( opt.queue ) {
576                         jQuery.dequeue( this, opt.queue );
577                 }
578         };
580         return opt;
583 jQuery.timers = [];
584 jQuery.fx.tick = function() {
585         var timer,
586                 timers = jQuery.timers,
587                 i = 0;
589         fxNow = jQuery.now();
591         for ( ; i < timers.length; i++ ) {
592                 timer = timers[ i ];
593                 // Checks the timer has not already been removed
594                 if ( !timer() && timers[ i ] === timer ) {
595                         timers.splice( i--, 1 );
596                 }
597         }
599         if ( !timers.length ) {
600                 jQuery.fx.stop();
601         }
602         fxNow = undefined;
605 jQuery.fx.timer = function( timer ) {
606         if ( timer() && jQuery.timers.push( timer ) ) {
607                 jQuery.fx.start();
608         }
611 jQuery.fx.interval = 13;
613 jQuery.fx.start = function() {
614         if ( !timerId ) {
615                 timerId = setInterval( jQuery.fx.tick, jQuery.fx.interval );
616         }
619 jQuery.fx.stop = function() {
620         clearInterval( timerId );
621         timerId = null;
624 jQuery.fx.speeds = {
625         slow: 600,
626         fast: 200,
627         // Default speed
628         _default: 400