Offset: allow offset setter to throw for disconnected elements
[jquery.git] / src / css.js
blob286eef64bf380356c4d1b4a5e035be354ef4a833
1 define([
2         "./core",
3         "./var/pnum",
4         "./core/access",
5         "./css/var/rmargin",
6         "./var/rcssNum",
7         "./css/var/rnumnonpx",
8         "./css/var/cssExpand",
9         "./css/var/isHidden",
10         "./css/var/getStyles",
11         "./css/var/swap",
12         "./css/curCSS",
13         "./css/adjustCSS",
14         "./css/defaultDisplay",
15         "./css/addGetHookIf",
16         "./css/support",
17         "./data/var/dataPriv",
19         "./core/init",
20         "./core/ready",
21         "./selector" // contains
22 ], function( jQuery, pnum, access, rmargin, rcssNum, rnumnonpx, cssExpand, isHidden,
23         getStyles, swap, curCSS, adjustCSS, defaultDisplay, addGetHookIf, support, dataPriv ) {
25 var
26         // Swappable if display is none or starts with table
27         // except "table", "table-cell", or "table-caption"
28         // See here for display values: https://developer.mozilla.org/en-US/docs/CSS/display
29         rdisplayswap = /^(none|table(?!-c[ea]).+)/,
30         rnumsplit = new RegExp( "^(" + pnum + ")(.*)$", "i" ),
32         cssShow = { position: "absolute", visibility: "hidden", display: "block" },
33         cssNormalTransform = {
34                 letterSpacing: "0",
35                 fontWeight: "400"
36         },
38         cssPrefixes = [ "Webkit", "Moz", "ms" ];
40 // Return a css property mapped to a potentially vendor prefixed property
41 function vendorPropName( style, name ) {
43         // Shortcut for names that are not vendor prefixed
44         if ( name in style ) {
45                 return name;
46         }
48         // Check for vendor prefixed names
49         var capName = name[0].toUpperCase() + name.slice(1),
50                 origName = name,
51                 i = cssPrefixes.length;
53         while ( i-- ) {
54                 name = cssPrefixes[ i ] + capName;
55                 if ( name in style ) {
56                         return name;
57                 }
58         }
60         return origName;
63 function setPositiveNumber( elem, value, subtract ) {
64         var matches = rnumsplit.exec( value );
65         return matches ?
66                 // Guard against undefined "subtract", e.g., when used as in cssHooks
67                 Math.max( 0, matches[ 1 ] - ( subtract || 0 ) ) + ( matches[ 2 ] || "px" ) :
68                 value;
71 function augmentWidthOrHeight( elem, name, extra, isBorderBox, styles ) {
72         var i = extra === ( isBorderBox ? "border" : "content" ) ?
73                 // If we already have the right measurement, avoid augmentation
74                 4 :
75                 // Otherwise initialize for horizontal or vertical properties
76                 name === "width" ? 1 : 0,
78                 val = 0;
80         for ( ; i < 4; i += 2 ) {
81                 // Both box models exclude margin, so add it if we want it
82                 if ( extra === "margin" ) {
83                         val += jQuery.css( elem, extra + cssExpand[ i ], true, styles );
84                 }
86                 if ( isBorderBox ) {
87                         // border-box includes padding, so remove it if we want content
88                         if ( extra === "content" ) {
89                                 val -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles );
90                         }
92                         // At this point, extra isn't border nor margin, so remove border
93                         if ( extra !== "margin" ) {
94                                 val -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles );
95                         }
96                 } else {
97                         // At this point, extra isn't content, so add padding
98                         val += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles );
100                         // At this point, extra isn't content nor padding, so add border
101                         if ( extra !== "padding" ) {
102                                 val += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles );
103                         }
104                 }
105         }
107         return val;
110 function getWidthOrHeight( elem, name, extra ) {
112         // Start with offset property, which is equivalent to the border-box value
113         var valueIsBorderBox = true,
114                 val = name === "width" ? elem.offsetWidth : elem.offsetHeight,
115                 styles = getStyles( elem ),
116                 isBorderBox = jQuery.css( elem, "boxSizing", false, styles ) === "border-box";
118         // Some non-html elements return undefined for offsetWidth, so check for null/undefined
119         // svg - https://bugzilla.mozilla.org/show_bug.cgi?id=649285
120         // MathML - https://bugzilla.mozilla.org/show_bug.cgi?id=491668
121         if ( val <= 0 || val == null ) {
122                 // Fall back to computed then uncomputed css if necessary
123                 val = curCSS( elem, name, styles );
124                 if ( val < 0 || val == null ) {
125                         val = elem.style[ name ];
126                 }
128                 // Computed unit is not pixels. Stop here and return.
129                 if ( rnumnonpx.test(val) ) {
130                         return val;
131                 }
133                 // Check for style in case a browser which returns unreliable values
134                 // for getComputedStyle silently falls back to the reliable elem.style
135                 valueIsBorderBox = isBorderBox &&
136                         ( support.boxSizingReliable() || val === elem.style[ name ] );
138                 // Normalize "", auto, and prepare for extra
139                 val = parseFloat( val ) || 0;
140         }
142         // Use the active box-sizing model to add/subtract irrelevant styles
143         return ( val +
144                 augmentWidthOrHeight(
145                         elem,
146                         name,
147                         extra || ( isBorderBox ? "border" : "content" ),
148                         valueIsBorderBox,
149                         styles
150                 )
151         ) + "px";
154 function showHide( elements, show ) {
155         var display, elem, hidden,
156                 values = [],
157                 index = 0,
158                 length = elements.length;
160         for ( ; index < length; index++ ) {
161                 elem = elements[ index ];
162                 if ( !elem.style ) {
163                         continue;
164                 }
166                 values[ index ] = dataPriv.get( elem, "olddisplay" );
167                 display = elem.style.display;
168                 if ( show ) {
169                         // Reset the inline display of this element to learn if it is
170                         // being hidden by cascaded rules or not
171                         if ( !values[ index ] && display === "none" ) {
172                                 elem.style.display = "";
173                         }
175                         // Set elements which have been overridden with display: none
176                         // in a stylesheet to whatever the default browser style is
177                         // for such an element
178                         if ( elem.style.display === "" && isHidden( elem ) ) {
179                                 values[ index ] = dataPriv.access(
180                                         elem,
181                                         "olddisplay",
182                                         defaultDisplay(elem.nodeName)
183                                 );
184                         }
185                 } else {
186                         hidden = isHidden( elem );
188                         if ( display !== "none" || !hidden ) {
189                                 dataPriv.set(
190                                         elem,
191                                         "olddisplay",
192                                         hidden ? display : jQuery.css( elem, "display" )
193                                 );
194                         }
195                 }
196         }
198         // Set the display of most of the elements in a second loop
199         // to avoid the constant reflow
200         for ( index = 0; index < length; index++ ) {
201                 elem = elements[ index ];
202                 if ( !elem.style ) {
203                         continue;
204                 }
205                 if ( !show || elem.style.display === "none" || elem.style.display === "" ) {
206                         elem.style.display = show ? values[ index ] || "" : "none";
207                 }
208         }
210         return elements;
213 jQuery.extend({
215         // Add in style property hooks for overriding the default
216         // behavior of getting and setting a style property
217         cssHooks: {
218                 opacity: {
219                         get: function( elem, computed ) {
220                                 if ( computed ) {
222                                         // We should always get a number back from opacity
223                                         var ret = curCSS( elem, "opacity" );
224                                         return ret === "" ? "1" : ret;
225                                 }
226                         }
227                 }
228         },
230         // Don't automatically add "px" to these possibly-unitless properties
231         cssNumber: {
232                 "columnCount": true,
233                 "fillOpacity": true,
234                 "flexGrow": true,
235                 "flexShrink": true,
236                 "fontWeight": true,
237                 "lineHeight": true,
238                 "opacity": true,
239                 "order": true,
240                 "orphans": true,
241                 "widows": true,
242                 "zIndex": true,
243                 "zoom": true
244         },
246         // Add in properties whose names you wish to fix before
247         // setting or getting the value
248         cssProps: {
249                 "float": "cssFloat"
250         },
252         // Get and set the style property on a DOM Node
253         style: function( elem, name, value, extra ) {
255                 // Don't set styles on text and comment nodes
256                 if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) {
257                         return;
258                 }
260                 // Make sure that we're working with the right name
261                 var ret, type, hooks,
262                         origName = jQuery.camelCase( name ),
263                         style = elem.style;
265                 name = jQuery.cssProps[ origName ] ||
266                         ( jQuery.cssProps[ origName ] = vendorPropName( style, origName ) );
268                 // Gets hook for the prefixed version, then unprefixed version
269                 hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];
271                 // Check if we're setting a value
272                 if ( value !== undefined ) {
273                         type = typeof value;
275                         // Convert "+=" or "-=" to relative numbers (#7345)
276                         if ( type === "string" && (ret = rcssNum.exec( value )) && ret[ 1 ] ) {
277                                 value = adjustCSS( elem, name, ret );
278                                 // Fixes bug #9237
279                                 type = "number";
280                         }
282                         // Make sure that null and NaN values aren't set (#7116)
283                         if ( value == null || value !== value ) {
284                                 return;
285                         }
287                         // If a number was passed in, add the unit (except for certain CSS properties)
288                         if ( type === "number" ) {
289                                 value += ret && ret[ 3 ] || ( jQuery.cssNumber[ origName ] ? "" : "px" );
290                         }
292                         // Support: IE9-11+
293                         // background-* props affect original clone's values
294                         if ( !support.clearCloneStyle && value === "" && name.indexOf( "background" ) === 0 ) {
295                                 style[ name ] = "inherit";
296                         }
298                         // If a hook was provided, use that value, otherwise just set the specified value
299                         if ( !hooks || !("set" in hooks) ||
300                                 (value = hooks.set( elem, value, extra )) !== undefined ) {
302                                 style[ name ] = value;
303                         }
305                 } else {
306                         // If a hook was provided get the non-computed value from there
307                         if ( hooks && "get" in hooks &&
308                                 (ret = hooks.get( elem, false, extra )) !== undefined ) {
310                                 return ret;
311                         }
313                         // Otherwise just get the value from the style object
314                         return style[ name ];
315                 }
316         },
318         css: function( elem, name, extra, styles ) {
319                 var val, num, hooks,
320                         origName = jQuery.camelCase( name );
322                 // Make sure that we're working with the right name
323                 name = jQuery.cssProps[ origName ] ||
324                         ( jQuery.cssProps[ origName ] = vendorPropName( elem.style, origName ) );
326                 // Try prefixed name followed by the unprefixed name
327                 hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];
329                 // If a hook was provided get the computed value from there
330                 if ( hooks && "get" in hooks ) {
331                         val = hooks.get( elem, true, extra );
332                 }
334                 // Otherwise, if a way to get the computed value exists, use that
335                 if ( val === undefined ) {
336                         val = curCSS( elem, name, styles );
337                 }
339                 // Convert "normal" to computed value
340                 if ( val === "normal" && name in cssNormalTransform ) {
341                         val = cssNormalTransform[ name ];
342                 }
344                 // Make numeric if forced or a qualifier was provided and val looks numeric
345                 if ( extra === "" || extra ) {
346                         num = parseFloat( val );
347                         return extra === true || jQuery.isNumeric( num ) ? num || 0 : val;
348                 }
349                 return val;
350         }
353 jQuery.each([ "height", "width" ], function( i, name ) {
354         jQuery.cssHooks[ name ] = {
355                 get: function( elem, computed, extra ) {
356                         if ( computed ) {
358                                 // Certain elements can have dimension info if we invisibly show them
359                                 // but it must have a current display style that would benefit
360                                 return rdisplayswap.test( jQuery.css( elem, "display" ) ) &&
361                                         elem.offsetWidth === 0 ?
362                                                 swap( elem, cssShow, function() {
363                                                         return getWidthOrHeight( elem, name, extra );
364                                                 }) :
365                                                 getWidthOrHeight( elem, name, extra );
366                         }
367                 },
369                 set: function( elem, value, extra ) {
370                         var styles = extra && getStyles( elem );
371                         return setPositiveNumber( elem, value, extra ?
372                                 augmentWidthOrHeight(
373                                         elem,
374                                         name,
375                                         extra,
376                                         jQuery.css( elem, "boxSizing", false, styles ) === "border-box",
377                                         styles
378                                 ) : 0
379                         );
380                 }
381         };
384 // Support: Android 2.3
385 jQuery.cssHooks.marginRight = addGetHookIf( support.reliableMarginRight,
386         function( elem, computed ) {
387                 if ( computed ) {
388                         return swap( elem, { "display": "inline-block" },
389                                 curCSS, [ elem, "marginRight" ] );
390                 }
391         }
394 // These hooks are used by animate to expand properties
395 jQuery.each({
396         margin: "",
397         padding: "",
398         border: "Width"
399 }, function( prefix, suffix ) {
400         jQuery.cssHooks[ prefix + suffix ] = {
401                 expand: function( value ) {
402                         var i = 0,
403                                 expanded = {},
405                                 // Assumes a single number if not a string
406                                 parts = typeof value === "string" ? value.split(" ") : [ value ];
408                         for ( ; i < 4; i++ ) {
409                                 expanded[ prefix + cssExpand[ i ] + suffix ] =
410                                         parts[ i ] || parts[ i - 2 ] || parts[ 0 ];
411                         }
413                         return expanded;
414                 }
415         };
417         if ( !rmargin.test( prefix ) ) {
418                 jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber;
419         }
422 jQuery.fn.extend({
423         css: function( name, value ) {
424                 return access( this, function( elem, name, value ) {
425                         var styles, len,
426                                 map = {},
427                                 i = 0;
429                         if ( jQuery.isArray( name ) ) {
430                                 styles = getStyles( elem );
431                                 len = name.length;
433                                 for ( ; i < len; i++ ) {
434                                         map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles );
435                                 }
437                                 return map;
438                         }
440                         return value !== undefined ?
441                                 jQuery.style( elem, name, value ) :
442                                 jQuery.css( elem, name );
443                 }, name, value, arguments.length > 1 );
444         },
445         show: function() {
446                 return showHide( this, true );
447         },
448         hide: function() {
449                 return showHide( this );
450         },
451         toggle: function( state ) {
452                 if ( typeof state === "boolean" ) {
453                         return state ? this.show() : this.hide();
454                 }
456                 return this.each(function() {
457                         if ( isHidden( this ) ) {
458                                 jQuery( this ).show();
459                         } else {
460                                 jQuery( this ).hide();
461                         }
462                 });
463         }
466 return jQuery;