Fix #11543: .has should work on detached elements.
[jquery.git] / src / css.js
blobc1775b7abca1cfa4e5003dd61c64390b4d464e25
1 (function( jQuery ) {
3 var ralpha = /alpha\([^)]*\)/i,
4         ropacity = /opacity=([^)]*)/,
5         // fixed for IE9, see #8346
6         rupper = /([A-Z]|^ms)/g,
7         rnum = /^[\-+]?(?:\d*\.)?\d+$/i,
8         rnumnonpx = /^-?(?:\d*\.)?\d+(?!px)[^\d\s]+$/i,
9         rrelNum = /^([\-+])=([\-+.\de]+)/,
10         rmargin = /^margin/,
12         cssShow = { position: "absolute", visibility: "hidden", display: "block" },
14         // order is important!
15         cssExpand = [ "Top", "Right", "Bottom", "Left" ],
17         curCSS,
19         getComputedStyle,
20         currentStyle;
22 jQuery.fn.css = function( name, value ) {
23         return jQuery.access( this, function( elem, name, value ) {
24                 return value !== undefined ?
25                         jQuery.style( elem, name, value ) :
26                         jQuery.css( elem, name );
27         }, name, value, arguments.length > 1 );
30 jQuery.extend({
31         // Add in style property hooks for overriding the default
32         // behavior of getting and setting a style property
33         cssHooks: {
34                 opacity: {
35                         get: function( elem, computed ) {
36                                 if ( computed ) {
37                                         // We should always get a number back from opacity
38                                         var ret = curCSS( elem, "opacity" );
39                                         return ret === "" ? "1" : ret;
41                                 } else {
42                                         return elem.style.opacity;
43                                 }
44                         }
45                 }
46         },
48         // Exclude the following css properties to add px
49         cssNumber: {
50                 "fillOpacity": true,
51                 "fontWeight": true,
52                 "lineHeight": true,
53                 "opacity": true,
54                 "orphans": true,
55                 "widows": true,
56                 "zIndex": true,
57                 "zoom": true
58         },
60         // Add in properties whose names you wish to fix before
61         // setting or getting the value
62         cssProps: {
63                 // normalize float css property
64                 "float": jQuery.support.cssFloat ? "cssFloat" : "styleFloat"
65         },
67         // Get and set the style property on a DOM Node
68         style: function( elem, name, value, extra ) {
69                 // Don't set styles on text and comment nodes
70                 if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) {
71                         return;
72                 }
74                 // Make sure that we're working with the right name
75                 var ret, type, origName = jQuery.camelCase( name ),
76                         style = elem.style, hooks = jQuery.cssHooks[ origName ];
78                 name = jQuery.cssProps[ origName ] || origName;
80                 // Check if we're setting a value
81                 if ( value !== undefined ) {
82                         type = typeof value;
84                         // convert relative number strings (+= or -=) to relative numbers. #7345
85                         if ( type === "string" && (ret = rrelNum.exec( value )) ) {
86                                 value = ( +( ret[1] + 1) * +ret[2] ) + parseFloat( jQuery.css( elem, name ) );
87                                 // Fixes bug #9237
88                                 type = "number";
89                         }
91                         // Make sure that NaN and null values aren't set. See: #7116
92                         if ( value == null || type === "number" && isNaN( value ) ) {
93                                 return;
94                         }
96                         // If a number was passed in, add 'px' to the (except for certain CSS properties)
97                         if ( type === "number" && !jQuery.cssNumber[ origName ] ) {
98                                 value += "px";
99                         }
101                         // If a hook was provided, use that value, otherwise just set the specified value
102                         if ( !hooks || !("set" in hooks) || (value = hooks.set( elem, value )) !== undefined ) {
103                                 // Wrapped to prevent IE from throwing errors when 'invalid' values are provided
104                                 // Fixes bug #5509
105                                 try {
106                                         style[ name ] = value;
107                                 } catch(e) {}
108                         }
110                 } else {
111                         // If a hook was provided get the non-computed value from there
112                         if ( hooks && "get" in hooks && (ret = hooks.get( elem, false, extra )) !== undefined ) {
113                                 return ret;
114                         }
116                         // Otherwise just get the value from the style object
117                         return style[ name ];
118                 }
119         },
121         css: function( elem, name, extra ) {
122                 var ret, hooks;
124                 // Make sure that we're working with the right name
125                 name = jQuery.camelCase( name );
126                 hooks = jQuery.cssHooks[ name ];
127                 name = jQuery.cssProps[ name ] || name;
129                 // cssFloat needs a special treatment
130                 if ( name === "cssFloat" ) {
131                         name = "float";
132                 }
134                 // If a hook was provided get the computed value from there
135                 if ( hooks && "get" in hooks && (ret = hooks.get( elem, true, extra )) !== undefined ) {
136                         return ret;
138                 // Otherwise, if a way to get the computed value exists, use that
139                 } else if ( curCSS ) {
140                         return curCSS( elem, name );
141                 }
142         },
144         // A method for quickly swapping in/out CSS properties to get correct calculations
145         swap: function( elem, options, callback ) {
146                 var old = {},
147                         ret, name;
149                 // Remember the old values, and insert the new ones
150                 for ( name in options ) {
151                         old[ name ] = elem.style[ name ];
152                         elem.style[ name ] = options[ name ];
153                 }
155                 ret = callback.call( elem );
157                 // Revert the old values
158                 for ( name in options ) {
159                         elem.style[ name ] = old[ name ];
160                 }
162                 return ret;
163         }
166 // DEPRECATED in 1.3, Use jQuery.css() instead
167 jQuery.curCSS = jQuery.css;
169 if ( document.defaultView && document.defaultView.getComputedStyle ) {
170         getComputedStyle = function( elem, name ) {
171                 var ret, defaultView, computedStyle, width,
172                         style = elem.style;
174                 name = name.replace( rupper, "-$1" ).toLowerCase();
176                 if ( (defaultView = elem.ownerDocument.defaultView) &&
177                                 (computedStyle = defaultView.getComputedStyle( elem, null )) ) {
179                         ret = computedStyle.getPropertyValue( name );
180                         if ( ret === "" && !jQuery.contains( elem.ownerDocument.documentElement, elem ) ) {
181                                 ret = jQuery.style( elem, name );
182                         }
183                 }
185                 // A tribute to the "awesome hack by Dean Edwards"
186                 // WebKit uses "computed value (percentage if specified)" instead of "used value" for margins
187                 // which is against the CSSOM draft spec: http://dev.w3.org/csswg/cssom/#resolved-values
188                 if ( !jQuery.support.pixelMargin && computedStyle && rmargin.test( name ) && rnumnonpx.test( ret ) ) {
189                         width = style.width;
190                         style.width = ret;
191                         ret = computedStyle.width;
192                         style.width = width;
193                 }
195                 return ret;
196         };
199 if ( document.documentElement.currentStyle ) {
200         currentStyle = function( elem, name ) {
201                 var left, rsLeft, uncomputed,
202                         ret = elem.currentStyle && elem.currentStyle[ name ],
203                         style = elem.style;
205                 // Avoid setting ret to empty string here
206                 // so we don't default to auto
207                 if ( ret == null && style && (uncomputed = style[ name ]) ) {
208                         ret = uncomputed;
209                 }
211                 // From the awesome hack by Dean Edwards
212                 // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291
214                 // If we're not dealing with a regular pixel number
215                 // but a number that has a weird ending, we need to convert it to pixels
216                 if ( rnumnonpx.test( ret ) ) {
218                         // Remember the original values
219                         left = style.left;
220                         rsLeft = elem.runtimeStyle && elem.runtimeStyle.left;
222                         // Put in the new values to get a computed value out
223                         if ( rsLeft ) {
224                                 elem.runtimeStyle.left = elem.currentStyle.left;
225                         }
226                         style.left = name === "fontSize" ? "1em" : ret;
227                         ret = style.pixelLeft + "px";
229                         // Revert the changed values
230                         style.left = left;
231                         if ( rsLeft ) {
232                                 elem.runtimeStyle.left = rsLeft;
233                         }
234                 }
236                 return ret === "" ? "auto" : ret;
237         };
240 curCSS = getComputedStyle || currentStyle;
242 function getWidthOrHeight( elem, name, extra ) {
244         // Start with offset property
245         var val = name === "width" ? elem.offsetWidth : elem.offsetHeight,
246                 i = name === "width" ? 1 : 0,
247                 len = 4;
249         if ( val > 0 ) {
250                 if ( extra !== "border" ) {
251                         for ( ; i < len; i += 2 ) {
252                                 if ( !extra ) {
253                                         val -= parseFloat( jQuery.css( elem, "padding" + cssExpand[ i ] ) ) || 0;
254                                 }
255                                 if ( extra === "margin" ) {
256                                         val += parseFloat( jQuery.css( elem, extra + cssExpand[ i ] ) ) || 0;
257                                 } else {
258                                         val -= parseFloat( jQuery.css( elem, "border" + cssExpand[ i ] + "Width" ) ) || 0;
259                                 }
260                         }
261                 }
263                 return val + "px";
264         }
266         // Fall back to computed then uncomputed css if necessary
267         val = curCSS( elem, name );
268         if ( val < 0 || val == null ) {
269                 val = elem.style[ name ];
270         }
272         // Computed unit is not pixels. Stop here and return.
273         if ( rnumnonpx.test(val) ) {
274                 return val;
275         }
277         // Normalize "", auto, and prepare for extra
278         val = parseFloat( val ) || 0;
280         // Add padding, border, margin
281         if ( extra ) {
282                 for ( ; i < len; i += 2 ) {
283                         val += parseFloat( jQuery.css( elem, "padding" + cssExpand[ i ] ) ) || 0;
284                         if ( extra !== "padding" ) {
285                                 val += parseFloat( jQuery.css( elem, "border" + cssExpand[ i ] + "Width" ) ) || 0;
286                         }
287                         if ( extra === "margin" ) {
288                                 val += parseFloat( jQuery.css( elem, extra + cssExpand[ i ]) ) || 0;
289                         }
290                 }
291         }
293         return val + "px";
296 jQuery.each([ "height", "width" ], function( i, name ) {
297         jQuery.cssHooks[ name ] = {
298                 get: function( elem, computed, extra ) {
299                         if ( computed ) {
300                                 if ( elem.offsetWidth !== 0 ) {
301                                         return getWidthOrHeight( elem, name, extra );
302                                 } else {
303                                         return jQuery.swap( elem, cssShow, function() {
304                                                 return getWidthOrHeight( elem, name, extra );
305                                         });
306                                 }
307                         }
308                 },
310                 set: function( elem, value ) {
311                         return rnum.test( value ) ?
312                                 value + "px" :
313                                 value;
314                 }
315         };
318 if ( !jQuery.support.opacity ) {
319         jQuery.cssHooks.opacity = {
320                 get: function( elem, computed ) {
321                         // IE uses filters for opacity
322                         return ropacity.test( (computed && elem.currentStyle ? elem.currentStyle.filter : elem.style.filter) || "" ) ?
323                                 ( 0.01 * parseFloat( RegExp.$1 ) ) + "" :
324                                 computed ? "1" : "";
325                 },
327                 set: function( elem, value ) {
328                         var style = elem.style,
329                                 currentStyle = elem.currentStyle,
330                                 opacity = jQuery.isNumeric( value ) ? "alpha(opacity=" + value * 100 + ")" : "",
331                                 filter = currentStyle && currentStyle.filter || style.filter || "";
333                         // IE has trouble with opacity if it does not have layout
334                         // Force it by setting the zoom level
335                         style.zoom = 1;
337                         // if setting opacity to 1, and no other filters exist - attempt to remove filter attribute #6652
338                         if ( value >= 1 && jQuery.trim( filter.replace( ralpha, "" ) ) === "" ) {
340                                 // Setting style.filter to null, "" & " " still leave "filter:" in the cssText
341                                 // if "filter:" is present at all, clearType is disabled, we want to avoid this
342                                 // style.removeAttribute is IE Only, but so apparently is this code path...
343                                 style.removeAttribute( "filter" );
345                                 // if there there is no filter style applied in a css rule, we are done
346                                 if ( currentStyle && !currentStyle.filter ) {
347                                         return;
348                                 }
349                         }
351                         // otherwise, set new filter values
352                         style.filter = ralpha.test( filter ) ?
353                                 filter.replace( ralpha, opacity ) :
354                                 filter + " " + opacity;
355                 }
356         };
359 jQuery(function() {
360         // This hook cannot be added until DOM ready because the support test
361         // for it is not run until after DOM ready
362         if ( !jQuery.support.reliableMarginRight ) {
363                 jQuery.cssHooks.marginRight = {
364                         get: function( elem, computed ) {
365                                 // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right
366                                 // Work around by temporarily setting element display to inline-block
367                                 return jQuery.swap( elem, { "display": "inline-block" }, function() {
368                                         if ( computed ) {
369                                                 return curCSS( elem, "margin-right" );
370                                         } else {
371                                                 return elem.style.marginRight;
372                                         }
373                                 });
374                         }
375                 };
376         }
379 if ( jQuery.expr && jQuery.expr.filters ) {
380         jQuery.expr.filters.hidden = function( elem ) {
381                 var width = elem.offsetWidth,
382                         height = elem.offsetHeight;
384                 return ( width === 0 && height === 0 ) || (!jQuery.support.reliableHiddenOffsets && ((elem.style && elem.style.display) || jQuery.css( elem, "display" )) === "none");
385         };
387         jQuery.expr.filters.visible = function( elem ) {
388                 return !jQuery.expr.filters.hidden( elem );
389         };
392 // These hooks are used by animate to expand properties
393 jQuery.each({
394         margin: "",
395         padding: "",
396         border: "Width"
397 }, function( prefix, suffix ) {
399         jQuery.cssHooks[ prefix + suffix ] = {
400                 expand: function( value ) {
401                         var i,
403                                 // assumes a single number if not a string
404                                 parts = typeof value === "string" ? value.split(" ") : [ value ],
405                                 expanded = {};
407                         for ( i = 0; i < 4; i++ ) {
408                                 expanded[ prefix + cssExpand[ i ] + suffix ] =
409                                         parts[ i ] || parts[ i - 2 ] || parts[ 0 ];
410                         }
412                         return expanded;
413                 }
414         };
417 })( jQuery );