]> source.dussan.org Git - jquery.git/commitdiff
Effects: 1.8 Animation Rewrite - thanks @mikesherov and @gibson042
authorCorey Frang <gnarf@gnarf.net>
Mon, 23 Apr 2012 19:05:12 +0000 (15:05 -0400)
committerDave Methvin <dave.methvin@gmail.com>
Mon, 23 Apr 2012 19:05:12 +0000 (15:05 -0400)
src/css.js
src/effects.js
test/unit/css.js
test/unit/effects.js

index adfd4fda3501d243f34569dca238a4683f5508df..c3a6f77252e1d27772d816918ff0f44156abca0b 100644 (file)
@@ -1,10 +1,12 @@
 (function( jQuery ) {
 
+jQuery.cssExpand = [ "Top", "Right", "Bottom", "Left" ];
+
 var ralpha = /alpha\([^)]*\)/i,
        ropacity = /opacity=([^)]*)/,
        // fixed for IE9, see #8346
        rupper = /([A-Z]|^ms)/g,
-       rnum = /^[\-+]?(?:\d*\.)?\d+$/i,
+       rnumsplit = /^([\-+]?(?:\d*\.)?\d+)(.*)$/i,
        rnumnonpx = /^-?(?:\d*\.)?\d+(?!px)[^\d\s]+$/i,
        rrelNum = /^([\-+])=([\-+.\de]+)/,
        rmargin = /^margin/,
@@ -12,7 +14,7 @@ var ralpha = /alpha\([^)]*\)/i,
        cssShow = { position: "absolute", visibility: "hidden", display: "block" },
 
        // order is important!
-       cssExpand = [ "Top", "Right", "Bottom", "Left" ],
+       cssExpand = jQuery.cssExpand,
        cssPrefixes = [ "O", "Webkit", "Moz", "ms" ],
 
        curCSS;
@@ -264,6 +266,13 @@ if ( document.defaultView && document.defaultView.getComputedStyle ) {
        };
 }
 
+function setPositiveNumber( elem, value ) {
+       var matches = rnumsplit.exec( value );
+       return matches ?
+                       Math.max( 0, matches[ 1 ] ) + ( matches [ 2 ] || "px" )
+                       : value;
+}
+
 function getWidthOrHeight( elem, name, extra ) {
 
        // Start with offset property, which is equivalent to the border-box value
@@ -348,11 +357,7 @@ jQuery.each([ "height", "width" ], function( i, name ) {
                        }
                },
 
-               set: function( elem, value ) {
-                       return rnum.test( value ) ?
-                               value + "px" :
-                               value;
-               }
+               set: setPositiveNumber
        };
 });
 
@@ -436,7 +441,6 @@ jQuery.each({
        padding: "",
        border: "Width"
 }, function( prefix, suffix ) {
-
        jQuery.cssHooks[ prefix + suffix ] = {
                expand: function( value ) {
                        var i,
@@ -453,6 +457,10 @@ jQuery.each({
                        return expanded;
                }
        };
+
+       if ( !rmargin.test( prefix ) ) {
+               jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber;
+       }
 });
 
 })( jQuery );
index 24d3d34a8d21c8254d18502cb37d2c23f4dfedf1..b6a374fb455e1f90045ac7962ca084dda544c39d 100644 (file)
 (function( jQuery ) {
 
-var elemdisplay = {},
-       iframe, iframeDoc,
+var fxNow, timerId, iframe, iframeDoc,
+       elemdisplay = {},
        rfxtypes = /^(?:toggle|show|hide)$/,
-       rfxnum = /^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i,
-       rMarginProp = /^margin/,
-       timerId,
-       fxAttrs = [
-               // height animations
-               [ "height", "marginTop", "marginBottom", "paddingTop", "paddingBottom" ],
-               // width animations
-               [ "width", "marginLeft", "marginRight", "paddingLeft", "paddingRight" ],
-               // opacity animations
-               [ "opacity" ]
-       ],
-       fxNow;
-
-jQuery.fn.extend({
-       show: function( speed, easing, callback ) {
-               var elem, display;
+       rfxnum = /^([\-+]=)?((?:\d*\.)?\d+)([a-z%]*)$/i,
+       rrun = /\.run$/,
+       animationPrefilters = [],
+       tweeners = {
+               "*": [function( prop, value ) {
+                       var end, unit,
+                               tween = this.createTween( prop, value ),
+                               parts = rfxnum.exec( value ),
+                               start = tween.cur();
+
+                       if ( parts ) {
+                               end = +parts[2];
+                               unit = parts[3] || ( jQuery.cssNumber[ prop ] ? "" : "px" );
+
+                               // We need to compute starting value
+                               if ( unit !== "px" ) {
+                                       jQuery.style( this, prop, (end || 1) + unit);
+                                       start = start * (end || 1) / tween.cur() || 0;
+                                       jQuery.style( this, prop, start + unit);
+                               }
 
-               if ( speed || speed === 0 ) {
-                       return this.animate( genFx("show", 3), speed, easing, callback );
+                               tween.unit = unit;
+                               tween.start = start;
+                               // If a +=/-= token was provided, we're doing a relative animation
+                               tween.end = parts[1] ? start + end * ( parts[1] === "-=" ? -1 : 1 ) : end;
+                       }
+                       return tween;
+               }]
+       },
+       oldToggle = jQuery.fn.toggle;
 
-               } else {
-                       for ( var i = 0, j = this.length; i < j; i++ ) {
-                               elem = this[ i ];
+// Animations created synchronously will run synchronously
+function createFxNow() {
+       setTimeout(function() {
+               fxNow = undefined;
+       }, 0 );
+       return ( fxNow = jQuery.now() );
+}
 
-                               if ( elem.style ) {
-                                       display = elem.style.display;
+function callTweeners( animation, props ) {
+       jQuery.each( props, function( prop, value ) {
+               var collection = ( tweeners[ prop ] || [] ).concat( tweeners[ "*" ] ),
+                       index = 0,
+                       length = collection.length;
+               for ( ; index < length; index++ ) {
+                       if ( collection[ index ].call( animation, prop, value ) ) {
+                               // we're done with this property
+                               return;
+                       }
+               }
+       });
+}
 
-                                       // Reset the inline display of this element to learn if it is
-                                       // being hidden by cascaded rules or not
-                                       if ( !jQuery._data(elem, "olddisplay") && display === "none" ) {
-                                               display = elem.style.display = "";
-                                       }
+function Animation( elem, properties, options ) {
+       var result,
+               index = 0,
+               tweenerIndex = 0,
+               length = animationPrefilters.length,
+               finished = jQuery.Deferred(),
+               deferred = jQuery.Deferred().always(function( ended ) {
+                       // remove cirular reference
+                       delete animation.tick;
+
+                       if ( deferred.state() === "resolved" || ended ) {
+                               // fire callbacks
+                               finished.resolveWith( this );
+                       }
+               }),
+               animation = deferred.promise({
+                       elem: elem,
+                       originalProperties: properties,
+                       originalOptions: options,
+                       props: jQuery.extend( {}, properties ),
+                       opts: jQuery.extend( {}, options ),
+                       startTime: fxNow || createFxNow(),
+                       duration: options.duration,
+                       finish: finished.done,
+                       tweens: [],
+                       createTween: function( prop, end, easing ) {
+                               var tween = jQuery.Tween( elem, animation.opts, prop, end,
+                                               animation.opts.specialEasing[ prop ] || animation.opts.easing );
+                               animation.tweens.push( tween );
+                               return tween;
+                       },
+                       tick: function() {
+                               var currentTime = fxNow || createFxNow(),
+                                       remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ),
+                                       percent = 1 - ( remaining / animation.duration || 0 ),
+                                       index = 0,
+                                       length = animation.tweens.length;
+
+                               for ( ; index < length ; index++ ) {
+                                       animation.tweens[ index ].run( percent );
+                               }
 
-                                       // Set elements which have been overridden with display: none
-                                       // in a stylesheet to whatever the default browser style is
-                                       // for such an element
-                                       if ( (display === "" && jQuery.css(elem, "display") === "none") ||
-                                               !jQuery.contains( elem.ownerDocument.documentElement, elem ) ) {
-                                               jQuery._data( elem, "olddisplay", defaultDisplay(elem.nodeName) );
-                                       }
+                               if ( percent < 1 && length ) {
+                                       return remaining;
+                               } else {
+                                       deferred.resolveWith( elem, [ currentTime ] );
+                                       return false;
+                               }
+                       },
+                       stop: function( gotoEnd ) {
+                               var index = 0,
+                                       // if we are going to the end, we want to run all the tweens
+                                       // otherwise we skip this part
+                                       length = gotoEnd ? animation.tweens.length : 0;
+
+                               for ( ; index < length ; index++ ) {
+                                       animation.tweens[ index ].run( 1 );
                                }
+                               deferred.rejectWith( elem, [ gotoEnd ] );
+                               return this;
                        }
+               }),
+               props = animation.props;
 
-                       // Set the display of most of the elements in a second loop
-                       // to avoid the constant reflow
-                       for ( i = 0; i < j; i++ ) {
-                               elem = this[ i ];
+       propFilter( props );
 
-                               if ( elem.style ) {
-                                       display = elem.style.display;
+       for ( ; index < length ; index++ ) {
+               result = animationPrefilters[ index ].call( animation,
+                       elem, props, animation.opts );
+               if ( result ) {
+                       return result;
+               }
+       }
 
-                                       if ( display === "" || display === "none" ) {
-                                               elem.style.display = jQuery._data( elem, "olddisplay" ) || "";
-                                       }
-                               }
-                       }
+       callTweeners( animation, props );
 
-                       return this;
-               }
-       },
+       jQuery.extend( animation.tick, {
+               anim: animation,
+               queue: animation.opts.queue,
+               elem: elem
+       });
 
-       hide: function( speed, easing, callback ) {
-               if ( speed || speed === 0 ) {
-                       return this.animate( genFx("hide", 3), speed, easing, callback);
+       jQuery.fx.timer( animation.tick );
+       return animation;
+}
 
-               } else {
-                       var elem, display,
-                               i = 0,
-                               j = this.length;
+function propFilter( props ) {
+       var index, name, hooks, replace;
 
-                       for ( ; i < j; i++ ) {
-                               elem = this[i];
-                               if ( elem.style ) {
-                                       display = jQuery.css( elem, "display" );
+       // camelCase and expand cssHook pass
+       for ( index in props ) {
+               name = jQuery.camelCase( index );
+               if ( index !== name ) {
+                       props[ name ] = props[ index ];
+                       delete props[ index ];
+               }
 
-                                       if ( display !== "none" && !jQuery._data( elem, "olddisplay" ) ) {
-                                               jQuery._data( elem, "olddisplay", display );
-                                       }
-                               }
-                       }
+               hooks = jQuery.cssHooks[ name ];
+               if ( hooks && "expand" in hooks ) {
+                       replace = hooks.expand( props[ name ] );
+                       delete props[ name ];
 
-                       // Set the display of the elements in a second loop
-                       // to avoid the constant reflow
-                       for ( i = 0; i < j; i++ ) {
-                               if ( this[i].style ) {
-                                       this[i].style.display = "none";
+                       // not quite $.extend, this wont overwrite keys already present.
+                       // also - reusing 'index' from above because we have the correct "name"
+                       for ( index in replace ) {
+                               if ( index in props ) {
+                                       continue;
                                }
+                               props[ index ] = replace[ index ];
                        }
+               }
+       }
+}
 
-                       return this;
+jQuery.Animation = jQuery.extend( Animation, {
+
+       tweener: function( props, callback ) {
+               if ( jQuery.isFunction( props ) ) {
+                       callback = props;
+                       props = [ "*" ];
+               } else {
+                       props = props.split(" ");
+               }
+
+               var prop,
+                       index = 0,
+                       length = props.length;
+
+               for ( ; index < length ; index++ ) {
+                       prop = props[ index ];
+                       tweeners[ prop ] = tweeners[ prop ] || [];
+                       tweeners[ prop ].unshift( callback );
                }
        },
 
-       // Save the old toggle function
-       _toggle: jQuery.fn.toggle,
+       prefilter: function( callback, prepend ) {
+               if ( prepend ) {
+                       animationPrefilters.unshift( callback );
+               } else {
+                       animationPrefilters.push( callback );
+               }
+       }
+});
 
-       toggle: function( fn, fn2, callback ) {
-               var bool = typeof fn === "boolean";
+Animation.prefilter(function( elem, props, opts ) {
+       var index, value,
+               style = elem.style;
+
+       // custom easing pass
+       opts.specialEasing = opts.specialEasing || {};
+       for ( index in props ) {
+               value = props[ index ];
+               if ( jQuery.isArray( value ) ) {
+                       opts.specialEasing[ index ] = value[ 1 ];
+                       value = props[ index ] = value[ 0 ];
+               }
+       }
 
-               if ( jQuery.isFunction(fn) && jQuery.isFunction(fn2) ) {
-                       this._toggle.apply( this, arguments );
+       // height/width overflow pass
+       if ( elem.nodeType === 1 && ( props.height || props.width ) ) {
+               // Make sure that nothing sneaks out
+               // Record all 3 overflow attributes because IE does not
+               // change the overflow attribute when overflowX and
+               // overflowY are set to the same value
+               opts.overflow = [ style.overflow, style.overflowX, style.overflowY ];
 
-               } else if ( fn == null || bool ) {
-                       this.each(function() {
-                               var state = bool ? fn : jQuery(this).is(":hidden");
-                               jQuery(this)[ state ? "show" : "hide" ]();
-                       });
+               // Set display property to inline-block for height/width
+               // animations on inline elements that are having width/height animated
+               if ( jQuery.css( elem, "display" ) === "inline" &&
+                               jQuery.css( elem, "float" ) === "none" ) {
+
+                       // inline-level elements accept inline-block;
+                       // block-level elements need to be inline with layout
+                       if ( !jQuery.support.inlineBlockNeedsLayout || defaultDisplay( elem.nodeName ) === "inline" ) {
+                               style.display = "inline-block";
+
+                       } else {
+                               style.zoom = 1;
+                       }
+               }
+       }
+
+       if ( opts.overflow ) {
+               style.overflow = "hidden";
+               this.finish(function() {
+                       style.overflow = opts.overflow[ 0 ];
+                       style.overflowX = opts.overflow[ 1 ];
+                       style.overflowY = opts.overflow[ 2 ];
+               });
+       }
+});
 
+// special case show/hide prefilter
+Animation.prefilter(function( elem, props, opts ) {
+       var index, prop, value, length, dataShow, tween,
+               orig = {},
+               handled = [],
+               hidden = jQuery( elem ).is(":hidden");
+
+       for ( index in props ) {
+               value = props[ index ];
+               if ( rfxtypes.exec( value ) ) {
+                       delete props[ index ];
+                       if ( value === ( hidden ? "hide" : "show" ) ) {
+                               continue;
+                       }
+                       handled.push( index );
+               }
+       }
+
+       length = handled.length;
+       if ( length ) {
+               dataShow = jQuery._data( elem, "fxshow" ) || jQuery._data( elem, "fxshow", {} );
+               if ( hidden ) {
+                       showHide([ elem ], true );
                } else {
-                       this.animate(genFx("toggle", 3), fn, fn2, callback);
+                       this.finish(function() {
+                               showHide([ elem ]);
+                       });
                }
+               this.finish(function() {
+                       var prop;
+                       jQuery.removeData( elem, "fxshow", true );
+                       for ( prop in orig ) {
+                               jQuery.style( elem, prop, orig[ prop ] );
+                       }
+               });
+               for ( index = 0 ; index < length ; index++ ) {
+                       prop = handled[ index ];
+                       tween = this.createTween( prop, hidden ? dataShow[ prop ] : 0 );
+                       orig[ prop ] = dataShow[ prop ] || jQuery.style( elem, prop );
+
+                       if ( !( prop in dataShow ) ) {
+                               dataShow[ prop ] = tween.start;
+                               if ( hidden ) {
+                                       tween.end = tween.start;
+                                       tween.start = prop === "width" || prop === "height" ? 1 : 0;
+                               }
+                       }
+               }
+       }
+});
 
-               return this;
+function Tween( elem, options, prop, end, easing ) {
+       return new Tween.prototype.init( elem, options, prop, end, easing );
+}
+jQuery.Tween = Tween;
+
+Tween.prototype = {
+       constructor: Tween,
+       init: function( elem, options, prop, end, easing, unit ) {
+               this.elem = elem;
+               this.prop = prop;
+               this.easing = easing || "swing";
+               this.options = options;
+               this.start = this.now = this.cur();
+               this.end = end;
+               this.unit = unit || ( jQuery.cssNumber[ prop ] ? "" : "px" );
        },
+       cur: function() {
+               var hooks = Tween.propHooks[ this.prop ];
 
-       fadeTo: function( speed, to, easing, callback ) {
-               return this.filter(":hidden").css("opacity", 0).show().end()
-                                       .animate({opacity: to}, speed, easing, callback);
+               return hooks && hooks.get ?
+                       hooks.get( this ) :
+                       Tween.propHooks._default.get( this );
        },
+       run: function( percent ) {
+               var eased,
+                       hooks = Tween.propHooks[ this.prop ];
 
-       animate: function( prop, speed, easing, callback ) {
-               var optall = jQuery.speed( speed, easing, callback );
+               this.pos = eased = jQuery.easing[ this.easing ]( percent, this.options.duration * percent, 0, 1, this.options.duration );
+               this.now = ( this.end - this.start ) * eased + this.start;
 
-               if ( jQuery.isEmptyObject( prop ) ) {
-                       return this.each( optall.complete, [ false ] );
+               if ( this.options.step ) {
+                       this.options.step.call( this.elem, this.now, this );
                }
 
-               // Do not change referenced properties as per-property easing will be lost
-               prop = jQuery.extend( {}, prop );
-
-               function doAnimation() {
-                       // XXX 'this' does not always have a nodeName when running the
-                       // test suite
-
-                       if ( optall.queue === false ) {
-                               jQuery._mark( this );
-                       }
+               if ( hooks && hooks.set ) {
+                       hooks.set( this );
+               } else {
+                       Tween.propHooks._default.set( this );
+               }
+               return this;
+       }
+};
 
-                       var opt = jQuery.extend( {}, optall ),
-                               isElement = this.nodeType === 1,
-                               hidden = isElement && jQuery(this).is(":hidden"),
-                               name, val, p, e, hooks, replace,
-                               parts, start, end, unit,
-                               method;
-
-                       // will store per property easing and be used to determine when an animation is complete
-                       opt.animatedProperties = {};
-
-                       // first pass over propertys to expand / normalize
-                       for ( p in prop ) {
-                               name = jQuery.camelCase( p );
-                               if ( p !== name ) {
-                                       prop[ name ] = prop[ p ];
-                                       delete prop[ p ];
-                               }
+Tween.prototype.init.prototype = Tween.prototype;
 
-                               if ( ( hooks = jQuery.cssHooks[ name ] ) && "expand" in hooks ) {
-                                       replace = hooks.expand( prop[ name ] );
-                                       delete prop[ name ];
+Tween.propHooks = {
+       _default: {
+               get: function( tween ) {
+                       var parsed, result;
 
-                                       // not quite $.extend, this wont overwrite keys already present.
-                                       // also - reusing 'p' from above because we have the correct "name"
-                                       for ( p in replace ) {
-                                               if ( ! ( p in prop ) ) {
-                                                       prop[ p ] = replace[ p ];
-                                               }
-                                       }
-                               }
+                       if ( tween.elem[ tween.prop ] != null &&
+                               (!tween.elem.style || tween.elem.style[ tween.prop ] == null) ) {
+                               return tween.elem[ tween.prop ];
                        }
 
-                       for ( name in prop ) {
-                               val = prop[ name ];
-                               // easing resolution: per property > opt.specialEasing > opt.easing > 'swing' (default)
-                               if ( jQuery.isArray( val ) ) {
-                                       opt.animatedProperties[ name ] = val[ 1 ];
-                                       val = prop[ name ] = val[ 0 ];
-                               } else {
-                                       opt.animatedProperties[ name ] = opt.specialEasing && opt.specialEasing[ name ] || opt.easing || 'swing';
-                               }
+                       result = jQuery.css( tween.elem, tween.prop );
+                       // Empty strings, null, undefined and "auto" are converted to 0,
+                       // complex values such as "rotate(1rad)" are returned as is,
+                       // simple values such as "10px" are parsed to Float.
+                       return isNaN( parsed = parseFloat( result ) ) ?
+                               !result || result === "auto" ? 0 : result : parsed;
+               },
+               set: function( tween ) {
+                       // use step hook for back compat - use cssHook if its there - use .style if its
+                       // available and use plain properties where available
+                       if ( jQuery.fx.step[ tween.prop ] ) {
+                               jQuery.fx.step[ tween.prop ]( tween );
+                       } else if ( tween.elem.style && ( tween.elem.style[ jQuery.cssProps[ tween.prop ] ] != null || jQuery.cssHooks[ tween.prop ] ) ) {
+                               jQuery.style( tween.elem, tween.prop, tween.now + tween.unit );
+                       } else {
+                               tween.elem[ tween.prop ] = tween.now;
+                       }
+               }
+       }
+};
 
-                               if ( val === "hide" && hidden || val === "show" && !hidden ) {
-                                       return opt.complete.call( this );
-                               }
+function showHide( elements, show ) {
+       var elem, display,
+               values = [],
+               index = 0,
+               length = elements.length;
 
-                               if ( isElement && ( name === "height" || name === "width" ) ) {
-                                       // Make sure that nothing sneaks out
-                                       // Record all 3 overflow attributes because IE does not
-                                       // change the overflow attribute when overflowX and
-                                       // overflowY are set to the same value
-                                       opt.overflow = [ this.style.overflow, this.style.overflowX, this.style.overflowY ];
-
-                                       // Set display property to inline-block for height/width
-                                       // animations on inline elements that are having width/height animated
-                                       if ( jQuery.css( this, "display" ) === "inline" &&
-                                                       jQuery.css( this, "float" ) === "none" ) {
-
-                                               // inline-level elements accept inline-block;
-                                               // block-level elements need to be inline with layout
-                                               if ( !jQuery.support.inlineBlockNeedsLayout || defaultDisplay( this.nodeName ) === "inline" ) {
-                                                       this.style.display = "inline-block";
-
-                                               } else {
-                                                       this.style.zoom = 1;
-                                               }
-                                       }
-                               }
+       for ( ; index < length; index++ ) {
+               elem = elements[ index ];
+               if ( !elem.style ) {
+                       continue;
+               }
+               values[ index ] = jQuery._data( elem, "olddisplay" );
+               if ( show ) {
+                       // Reset the inline display of this element to learn if it is
+                       // being hidden by cascaded rules or not
+                       if ( !values[ index ] && elem.style.display === "none" ) {
+                               elem.style.display = "";
                        }
 
-                       if ( opt.overflow != null ) {
-                               this.style.overflow = "hidden";
+                       // Set elements which have been overridden with display: none
+                       // in a stylesheet to whatever the default browser style is
+                       // for such an element
+                       if ( (elem.style.display === "" && jQuery.css( elem, "display" ) === "none") ||
+                               !jQuery.contains( elem.ownerDocument.documentElement, elem ) ) {
+                               values[ index ] = jQuery._data( elem, "olddisplay", defaultDisplay(elem.nodeName) );
                        }
+               } else {
+                       display = jQuery.css( elem, "display" );
 
-                       for ( p in prop ) {
-                               e = new jQuery.fx( this, opt, p );
-                               val = prop[ p ];
-
-                               if ( rfxtypes.test( val ) ) {
+                       if ( !values[ index ] && display !== "none" ) {
+                               jQuery._data( elem, "olddisplay", display );
+                       }
+               }
+       }
 
-                                       // Tracks whether to show or hide based on private
-                                       // data attached to the element
-                                       method = jQuery._data( this, "toggle" + p ) || ( val === "toggle" ? hidden ? "show" : "hide" : 0 );
-                                       if ( method ) {
-                                               jQuery._data( this, "toggle" + p, method === "show" ? "hide" : "show" );
-                                               e[ method ]();
-                                       } else {
-                                               e[ val ]();
-                                       }
+       // Set the display of most of the elements in a second loop
+       // to avoid the constant reflow
+       for ( index = 0; index < length; index++ ) {
+               elem = elements[ index ];
+               if ( !elem.style ) {
+                       continue;
+               }
+               if ( !show || elem.style.display === "none" || elem.style.display === "" ) {
+                       elem.style.display = show ? values[ index ] || "" : "none";
+               }
+       }
 
-                               } else {
-                                       parts = rfxnum.exec( val );
-                                       start = e.cur();
+       return elements;
+}
 
-                                       if ( parts ) {
-                                               end = parseFloat( parts[2] );
-                                               unit = parts[3] || ( jQuery.cssNumber[ p ] ? "" : "px" );
+jQuery.fn.extend({
+       show: function( speed, easing, callback ) {
+               return speed || speed === 0 ?
+                       this.animate( genFx( "show", true ), speed, easing, callback ) :
+                       showHide( this, true );
+       },
+       hide: function( speed, easing, callback ) {
+               return speed || speed === 0 ?
+                       this.animate( genFx( "hide", true ), speed, easing, callback ) :
+                       showHide( this );
+       },
+       toggle: function( fn, fn2, callback ) {
+               var bool = typeof fn === "boolean";
 
-                                               // We need to compute starting value
-                                               if ( unit !== "px" ) {
-                                                       jQuery.style( this, p, (end || 1) + unit);
-                                                       start = ( (end || 1) / e.cur() ) * start;
-                                                       jQuery.style( this, p, start + unit);
-                                               }
+               if ( jQuery.isFunction( fn ) && jQuery.isFunction( fn2 ) ) {
+                       oldToggle.apply( this, arguments );
 
-                                               // If a +=/-= token was provided, we're doing a relative animation
-                                               if ( parts[1] ) {
-                                                       end = ( (parts[ 1 ] === "-=" ? -1 : 1) * end ) + start;
-                                               }
+               } else if ( fn == null || bool ) {
+                       this.each(function() {
+                               var state = bool ? fn : jQuery( this ).is(":hidden");
+                               showHide([ this ], state );
+                       });
 
-                                               e.custom( start, end, unit );
+               } else {
+                       this.animate( genFx( "toggle", true ), fn, fn2, callback );
+               }
 
-                                       } else {
-                                               e.custom( start, val, "" );
-                                       }
-                               }
-                       }
+               return this;
+       },
+       fadeTo: function( speed, to, easing, callback ) {
+               return this.filter(":hidden").css("opacity", 0).show().end()
+                                       .animate({opacity: to}, speed, easing, callback);
+       },
+       animate: function( prop, speed, easing, callback ) {
+               var optall = jQuery.speed( speed, easing, callback ),
+                       doAnimation = function() {
+                               Animation( this, prop, optall ).finish( optall.complete );
+                       };
 
-                       // For JS strict compliance
-                       return true;
+               if ( jQuery.isEmptyObject( prop ) ) {
+                       return this.each( optall.complete, [ false ] );
                }
 
+               // Do not change referenced properties as per-property easing will be lost
+               prop = jQuery.extend( {}, prop );
+
                return optall.queue === false ?
                        this.each( doAnimation ) :
                        this.queue( optall.queue, doAnimation );
        },
-
        stop: function( type, clearQueue, gotoEnd ) {
+               var stopQueue = function( elem, data, index ) {
+                       var hooks = data[ index ];
+                       jQuery.removeData( elem, index, true );
+                       hooks.stop( gotoEnd );
+               };
+
                if ( typeof type !== "string" ) {
                        gotoEnd = clearQueue;
                        clearQueue = type;
@@ -289,15 +502,9 @@ jQuery.fn.extend({
                                jQuery._unmark( true, this );
                        }
 
-                       function stopQueue( elem, data, index ) {
-                               var hooks = data[ index ];
-                               jQuery.removeData( elem, index, true );
-                               hooks.stop( gotoEnd );
-                       }
-
                        if ( type == null ) {
                                for ( index in data ) {
-                                       if ( data[ index ] && data[ index ].stop && index.indexOf(".run") === index.length - 4 ) {
+                                       if ( data[ index ] && data[ index ].stop && rrun.test( index ) ) {
                                                stopQueue( this, data, index );
                                        }
                                }
@@ -307,13 +514,7 @@ jQuery.fn.extend({
 
                        for ( index = timers.length; index--; ) {
                                if ( timers[ index ].elem === this && (type == null || timers[ index ].queue === type) ) {
-                                       if ( gotoEnd ) {
-
-                                               // force the next step to be the last
-                                               timers[ index ]( true );
-                                       } else {
-                                               timers[ index ].saveState();
-                                       }
+                                       timers[ index ].anim.stop( gotoEnd );
                                        hadTimers = true;
                                        timers.splice( index, 1 );
                                }
@@ -327,35 +528,33 @@ jQuery.fn.extend({
                        }
                });
        }
-
 });
 
-// Animations created synchronously will run synchronously
-function createFxNow() {
-       setTimeout( clearFxNow, 0 );
-       return ( fxNow = jQuery.now() );
-}
-
-function clearFxNow() {
-       fxNow = undefined;
-}
-
 // Generate parameters to create a standard animation
-function genFx( type, num ) {
-       var obj = {};
+function genFx( type, includeWidth ) {
+       var which,
+               attrs = { height: type },
+               i = 0;
+
+       // if we include width, step value is 1 to do all cssExpand values,
+       // if we don't include width, step value is 2 to skip over Left and Right
+       for( ; i < 4 ; i += 2 - includeWidth ) {
+               which = jQuery.cssExpand[ i ];
+               attrs[ "margin" + which ] = attrs[ "padding" + which ] = type;
+       }
 
-       jQuery.each( fxAttrs.concat.apply([], fxAttrs.slice( 0, num )), function() {
-               obj[ this ] = type;
-       });
+       if ( includeWidth ) {
+               attrs.opacity = attrs.width = type;
+       }
 
-       return obj;
+       return attrs;
 }
 
 // Generate shortcuts for custom animations
 jQuery.each({
-       slideDown: genFx( "show", 1 ),
-       slideUp: genFx( "hide", 1 ),
-       slideToggle: genFx( "toggle", 1 ),
+       slideDown: genFx("show"),
+       slideUp: genFx("hide"),
+       slideToggle: genFx("toggle"),
        fadeIn: { opacity: "show" },
        fadeOut: { opacity: "hide" },
        fadeToggle: { opacity: "toggle" }
@@ -365,285 +564,91 @@ jQuery.each({
        };
 });
 
-jQuery.extend({
-       speed: function( speed, easing, fn ) {
-               var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : {
-                       complete: fn || !fn && easing ||
-                               jQuery.isFunction( speed ) && speed,
-                       duration: speed,
-                       easing: fn && easing || easing && !jQuery.isFunction( easing ) && easing
-               };
-
-               opt.duration = jQuery.fx.off ? 0 : typeof opt.duration === "number" ? opt.duration :
-                       opt.duration in jQuery.fx.speeds ? jQuery.fx.speeds[ opt.duration ] : jQuery.fx.speeds._default;
-
-               // normalize opt.queue - true/undefined/null -> "fx"
-               if ( opt.queue == null || opt.queue === true ) {
-                       opt.queue = "fx";
-               }
-
-               // Queueing
-               opt.old = opt.complete;
-
-               opt.complete = function( noUnmark ) {
-                       if ( jQuery.isFunction( opt.old ) ) {
-                               opt.old.call( this );
-                       }
-
-                       if ( opt.queue ) {
-                               jQuery.dequeue( this, opt.queue );
-                       } else if ( noUnmark !== false ) {
-                               jQuery._unmark( this );
-                       }
-               };
-
-               return opt;
-       },
-
-       easing: {
-               linear: function( p ) {
-                       return p;
-               },
-               swing: function( p ) {
-                       return ( -Math.cos( p*Math.PI ) / 2 ) + 0.5;
-               }
-       },
+jQuery.speed = function( speed, easing, fn ) {
+       var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : {
+               complete: fn || !fn && easing ||
+                       jQuery.isFunction( speed ) && speed,
+               duration: speed,
+               easing: fn && easing || easing && !jQuery.isFunction( easing ) && easing
+       };
 
-       timers: [],
+       opt.duration = jQuery.fx.off ? 0 : typeof opt.duration === "number" ? opt.duration :
+               opt.duration in jQuery.fx.speeds ? jQuery.fx.speeds[ opt.duration ] : jQuery.fx.speeds._default;
 
-       fx: function( elem, options, prop ) {
-               this.options = options;
-               this.elem = elem;
-               this.prop = prop;
-
-               options.orig = options.orig || {};
+       // normalize opt.queue - true/undefined/null -> "fx"
+       if ( opt.queue == null || opt.queue === true ) {
+               opt.queue = "fx";
        }
 
-});
-
-jQuery.fx.prototype = {
-       // Simple function for setting a style value
-       update: function() {
-               if ( this.options.step ) {
-                       this.options.step.call( this.elem, this.now, this );
-               }
-
-               ( jQuery.fx.step[ this.prop ] || jQuery.fx.step._default )( this );
-       },
-
-       // Get the current size
-       cur: function() {
-               if ( this.elem[ this.prop ] != null && (!this.elem.style || this.elem.style[ this.prop ] == null) ) {
-                       return this.elem[ this.prop ];
-               }
-
-               var parsed,
-                       r = jQuery.css( this.elem, this.prop );
-               // Empty strings, null, undefined and "auto" are converted to 0,
-               // complex values such as "rotate(1rad)" are returned as is,
-               // simple values such as "10px" are parsed to Float.
-               return isNaN( parsed = parseFloat( r ) ) ? !r || r === "auto" ? 0 : r : parsed;
-       },
-
-       // Start an animation from one number to another
-       custom: function( from, to, unit ) {
-               var self = this,
-                       fx = jQuery.fx;
-
-               this.startTime = fxNow || createFxNow();
-               this.end = to;
-               this.now = this.start = from;
-               this.pos = this.state = 0;
-               this.unit = unit || this.unit || ( jQuery.cssNumber[ this.prop ] ? "" : "px" );
-
-               function t( gotoEnd ) {
-                       return self.step( gotoEnd );
-               }
-
-               t.queue = this.options.queue;
-               t.elem = this.elem;
-               t.saveState = function() {
-                       if ( jQuery._data( self.elem, "fxshow" + self.prop ) === undefined ) {
-                               if ( self.options.hide ) {
-                                       jQuery._data( self.elem, "fxshow" + self.prop, self.start );
-                               } else if ( self.options.show ) {
-                                       jQuery._data( self.elem, "fxshow" + self.prop, self.end );
-                               }
-                       }
-               };
+       // Queueing
+       opt.old = opt.complete;
 
-               if ( t() && jQuery.timers.push(t) && !timerId ) {
-                       timerId = setInterval( fx.tick, fx.interval );
+       opt.complete = function( noUnmark ) {
+               if ( jQuery.isFunction( opt.old ) ) {
+                       opt.old.call( this );
                }
-       },
-
-       // Simple 'show' function
-       show: function() {
-               var dataShow = jQuery._data( this.elem, "fxshow" + this.prop );
 
-               // Remember where we started, so that we can go back to it later
-               this.options.orig[ this.prop ] = dataShow || jQuery.style( this.elem, this.prop );
-               this.options.show = true;
-
-               // Begin the animation
-               // Make sure that we start at a small width/height to avoid any flash of content
-               if ( dataShow !== undefined ) {
-                       // This show is picking up where a previous hide or show left off
-                       this.custom( this.cur(), dataShow );
-               } else {
-                       this.custom( this.prop === "width" || this.prop === "height" ? 1 : 0, this.cur() );
+               if ( opt.queue ) {
+                       jQuery.dequeue( this, opt.queue );
+               } else if ( noUnmark !== false ) {
+                       jQuery._unmark( this );
                }
+       };
 
-               // Start by showing the element
-               jQuery( this.elem ).show();
-       },
-
-       // Simple 'hide' function
-       hide: function() {
-               // Remember where we started, so that we can go back to it later
-               this.options.orig[ this.prop ] = jQuery._data( this.elem, "fxshow" + this.prop ) || jQuery.style( this.elem, this.prop );
-               this.options.hide = true;
+       return opt;
+};
 
-               // Begin the animation
-               this.custom( this.cur(), 0 );
+jQuery.easing = {
+       linear: function( p ) {
+               return p;
        },
-
-       // Each step of an animation
-       step: function( gotoEnd ) {
-               var p, n, complete,
-                       t = fxNow || createFxNow(),
-                       done = true,
-                       elem = this.elem,
-                       options = this.options;
-
-               if ( gotoEnd || t >= options.duration + this.startTime ) {
-                       this.now = this.end;
-                       this.pos = this.state = 1;
-                       this.update();
-
-                       options.animatedProperties[ this.prop ] = true;
-
-                       for ( p in options.animatedProperties ) {
-                               if ( options.animatedProperties[ p ] !== true ) {
-                                       done = false;
-                               }
-                       }
-
-                       if ( done ) {
-                               // Reset the overflow
-                               if ( options.overflow != null && !jQuery.support.shrinkWrapBlocks ) {
-
-                                       jQuery.each( [ "", "X", "Y" ], function( index, value ) {
-                                               elem.style[ "overflow" + value ] = options.overflow[ index ];
-                                       });
-                               }
-
-                               // Hide the element if the "hide" operation was done
-                               if ( options.hide ) {
-                                       jQuery( elem ).hide();
-                               }
-
-                               // Reset the properties, if the item has been hidden or shown
-                               if ( options.hide || options.show ) {
-                                       for ( p in options.animatedProperties ) {
-                                               jQuery.style( elem, p, options.orig[ p ] );
-                                               jQuery.removeData( elem, "fxshow" + p, true );
-                                               // Toggle data is no longer needed
-                                               jQuery.removeData( elem, "toggle" + p, true );
-                                       }
-                               }
-
-                               // Execute the complete function
-                               // in the event that the complete function throws an exception
-                               // we must ensure it won't be called twice. #5684
-
-                               complete = options.complete;
-                               if ( complete ) {
-
-                                       options.complete = false;
-                                       complete.call( elem );
-                               }
-                       }
-
-                       return false;
-
-               } else {
-                       // classical easing cannot be used with an Infinity duration
-                       if ( options.duration == Infinity ) {
-                               this.now = t;
-                       } else {
-                               n = t - this.startTime;
-                               this.state = n / options.duration;
-
-                               // Perform the easing function, defaults to swing
-                               this.pos = jQuery.easing[ options.animatedProperties[this.prop] ]( this.state, n, 0, 1, options.duration );
-                               this.now = this.start + ( (this.end - this.start) * this.pos );
-                       }
-                       // Perform the next step of the animation
-                       this.update();
-               }
-
-               return true;
+       swing: function( p ) {
+               return 0.5 - Math.cos( p*Math.PI ) / 2;
        }
 };
 
-jQuery.extend( jQuery.fx, {
-       tick: function() {
-               var timer,
-                       timers = jQuery.timers,
-                       i = 0;
-
-               for ( ; i < timers.length; i++ ) {
-                       timer = timers[ i ];
-                       // Checks the timer has not already been removed
-                       if ( !timer() && timers[ i ] === timer ) {
-                               timers.splice( i--, 1 );
-                       }
-               }
-
-               if ( !timers.length ) {
-                       jQuery.fx.stop();
+jQuery.timers = [];
+jQuery.fx = Tween.prototype.init;
+jQuery.fx.tick = function() {
+       var timer,
+               timers = jQuery.timers,
+               i = 0;
+
+       for ( ; i < timers.length; i++ ) {
+               timer = timers[ i ];
+               // Checks the timer has not already been removed
+               if ( !timer() && timers[ i ] === timer ) {
+                       timers.splice( i--, 1 );
                }
-       },
+       }
 
-       interval: 13,
+       if ( !timers.length ) {
+               jQuery.fx.stop();
+       }
+};
 
-       stop: function() {
-               clearInterval( timerId );
-               timerId = null;
-       },
+jQuery.fx.timer = function( timer ) {
+       if ( timer() && jQuery.timers.push( timer ) && !timerId ) {
+               timerId = setInterval( jQuery.fx.tick, jQuery.fx.interval );
+       }
+};
 
-       speeds: {
-               slow: 600,
-               fast: 200,
-               // Default speed
-               _default: 400
-       },
+jQuery.fx.interval = 13;
 
-       step: {
-               opacity: function( fx ) {
-                       jQuery.style( fx.elem, "opacity", fx.now );
-               },
+jQuery.fx.stop = function() {
+       clearInterval( timerId );
+       timerId = null;
+};
 
-               _default: function( fx ) {
-                       if ( fx.elem.style && fx.elem.style[ fx.prop ] != null ) {
-                               fx.elem.style[ fx.prop ] = fx.now + fx.unit;
-                       } else {
-                               fx.elem[ fx.prop ] = fx.now;
-                       }
-               }
-       }
-});
+jQuery.fx.speeds = {
+       slow: 600,
+       fast: 200,
+       // Default speed
+       _default: 400
+};
 
-// Ensure props that can't be negative don't go there on undershoot easing
-jQuery.each( fxAttrs.concat.apply( [], fxAttrs ), function( i, prop ) {
-       // Exclude marginTop, marginLeft, marginBottom and marginRight from this list
-       if ( !rMarginProp.test( prop ) ) {
-               jQuery.fx.step[ prop ] = function( fx ) {
-                       jQuery.style( fx.elem, prop, Math.max(0, fx.now) + fx.unit );
-               };
-       }
-});
+// Back Compat <1.8 extension point
+jQuery.fx.step = {};
 
 if ( jQuery.expr && jQuery.expr.filters ) {
        jQuery.expr.filters.animated = function( elem ) {
@@ -655,47 +660,45 @@ if ( jQuery.expr && jQuery.expr.filters ) {
 
 // Try to restore the default display value of an element
 function defaultDisplay( nodeName ) {
+       if ( elemdisplay[ nodeName ] ) {
+               return elemdisplay[ nodeName ];
+       }
 
-       if ( !elemdisplay[ nodeName ] ) {
-
-               var body = document.body,
-                       elem = jQuery( "<" + nodeName + ">" ).appendTo( body ),
-                       display = elem.css( "display" );
-               elem.remove();
-
-               // If the simple way fails,
-               // get element's real default display by attaching it to a temp iframe
-               if ( display === "none" || display === "" ) {
-                       // No iframe to use yet, so create it
-                       if ( !iframe ) {
-                               iframe = document.createElement( "iframe" );
-                               iframe.frameBorder = iframe.width = iframe.height = 0;
-                       }
-
-                       body.appendChild( iframe );
-
-                       // Create a cacheable copy of the iframe document on first call.
-                       // IE and Opera will allow us to reuse the iframeDoc without re-writing the fake HTML
-                       // document to it; WebKit & Firefox won't allow reusing the iframe document.
-                       if ( !iframeDoc || !iframe.createElement ) {
-                               iframeDoc = ( iframe.contentWindow || iframe.contentDocument ).document;
-                               iframeDoc.write( ( jQuery.support.boxModel ? "<!doctype html>" : "" ) + "<html><body>" );
-                               iframeDoc.close();
-                       }
-
-                       elem = iframeDoc.createElement( nodeName );
-
-                       iframeDoc.body.appendChild( elem );
-
-                       display = jQuery.css( elem, "display" );
-                       body.removeChild( iframe );
+       var elem = jQuery( "<" + nodeName + ">" ).appendTo( document.body ),
+               display = elem.css("display");
+       elem.remove();
+
+       // If the simple way fails,
+       // get element's real default display by attaching it to a temp iframe
+       if ( display === "none" || display === "" ) {
+               // Use the already-created iframe if possible
+               iframe = document.body.appendChild(
+                       iframe || jQuery.extend( document.createElement("iframe"), {
+                               frameBorder: 0,
+                               width: 0,
+                               height: 0
+                       })
+               );
+
+               // Create a cacheable copy of the iframe document on first call.
+               // IE and Opera will allow us to reuse the iframeDoc without re-writing the fake HTML
+               // document to it; WebKit & Firefox won't allow reusing the iframe document.
+               if ( !iframeDoc || !iframe.createElement ) {
+                       iframeDoc = ( iframe.contentWindow || iframe.contentDocument ).document;
+                       iframeDoc.write( ( jQuery.support.boxModel ? "<!doctype html>" : "" ) + "<html><body>" );
+                       iframeDoc.close();
                }
 
-               // Store the correct default display
-               elemdisplay[ nodeName ] = display;
+               elem = iframeDoc.body.appendChild( iframeDoc.createElement(nodeName) );
+
+               display = jQuery.css( elem, "display" );
+               document.body.removeChild( iframe );
        }
 
-       return elemdisplay[ nodeName ];
+       // Store the correct default display
+       elemdisplay[ nodeName ] = display;
+
+       return display;
 }
 
 })( jQuery );
index 6d0b39f812226201db13c23125f58d636cd02edb..a192c1b5b05ca507b3318e047feaec8da79de3ce 100644 (file)
@@ -37,13 +37,13 @@ test("css(String|Hash)", function() {
 
        div2.remove();
 
-       // handle negative numbers by ignoring #1599, #4216
+       // handle negative numbers by setting to zero #11604
        jQuery("#nothiddendiv").css( {width: 1, height: 1} );
 
        var width = parseFloat(jQuery("#nothiddendiv").css("width")), height = parseFloat(jQuery("#nothiddendiv").css("height"));
        jQuery("#nothiddendiv").css({ width: -1, height: -1 });
-       equal( parseFloat(jQuery("#nothiddendiv").css("width")), width, "Test negative width ignored");
-       equal( parseFloat(jQuery("#nothiddendiv").css("height")), height, "Test negative height ignored");
+       equal( parseFloat(jQuery("#nothiddendiv").css("width")), 0, "Test negative width set to 0");
+       equal( parseFloat(jQuery("#nothiddendiv").css("height")), 0, "Test negative height set to 0");
 
        equal( jQuery("<div style='display: none;'>").css("display"), "none", "Styles on disconnected nodes");
 
index 25c9834e44a556440532f3aa83831ceacfa6321c..74f83e7c4f953f3b129668b07c8bcc4077acd2b4 100644 (file)
@@ -792,7 +792,7 @@ jQuery.checkOverflowDisplay = function(){
        start();
 };
 
-test( "jQuery.fx.prototype.cur()", 6, function() {
+test( "jQuery.fx.prototype.cur() - <1.8 Back Compat", 7, function() {
        var div = jQuery( "<div></div>" ).appendTo( "#qunit-fixture" ).css({
                        color: "#ABC",
                        border: "5px solid black",
@@ -814,6 +814,8 @@ test( "jQuery.fx.prototype.cur()", 6, function() {
 
        // backgroundPosition actually returns 0% 0% in most browser
        // this fakes a "" return
+       // hook now gets called twice because Tween will grab the current
+       // value as it is being newed
        jQuery.cssHooks.backgroundPosition = {
                get: function() {
                        ok( true, "hook used" );
@@ -1387,3 +1389,62 @@ test("animate will scale margin properties individually", function() {
        });
        start();
 });
+
+// Start 1.8 Animation tests
+asyncTest( "jQuery.Animation( object, props, opts )", 1, function() {
+       var testObject = {
+                       foo: 0,
+                       bar: 1,
+                       width: 100
+               },
+               testDest = {
+                       foo: 1,
+                       bar: 0,
+                       width: 200
+               };
+
+       jQuery.Animation( testObject, testDest, { duration: 1 })
+               .done( function() {
+                       deepEqual( testObject, testDest, "Animated foo and bar" );
+                       start();
+               });
+});
+
+asyncTest( "Animate Option: step: function( percent, tween )", 1, function() {
+       var counter = {};
+       jQuery( "#foo" ).animate({
+               prop1: 1,
+               prop2: 2,
+               prop3: 3
+       }, {
+               duration: 1,
+               step: function( value, tween ) {
+                       calls = counter[ tween.prop ] = counter[ tween.prop ] || [];
+                       calls.push( value );
+               }
+       }).queue( function( next ) {
+               deepEqual( counter, {
+                       prop1: [0, 1],
+                       prop2: [0, 2],
+                       prop3: [0, 3]
+               }, "Step function was called once at 0% and once at 100% for each property");
+               next();
+               start();
+       });
+});
+
+
+asyncTest( "Animate callbacks have correct context", 2, function() {
+       var foo = jQuery( "#foo" );
+       foo.animate({
+               height: 10
+       }, 10, function() {
+               equal( foo[ 0 ], this, "Complete callback after stop(true) `this` is element" );
+       }).stop( true, true );
+       foo.animate({
+               height: 100
+       }, 10, function() {
+               equal( foo[ 0 ], this, "Complete callback `this` is element" );
+               start();
+       });
+});