CSS: Correctly detect scrollbox support with non-default zoom
[jquery.git] / src / css.js
blobe936c8f30ec7655371b5b9e9f79fc31a3e01e91a
1 define( [
2         "./core",
3         "./var/pnum",
4         "./core/access",
5         "./core/camelCase",
6         "./var/document",
7         "./var/rcssNum",
8         "./css/var/rnumnonpx",
9         "./css/var/cssExpand",
10         "./css/var/getStyles",
11         "./css/var/swap",
12         "./css/curCSS",
13         "./css/adjustCSS",
14         "./css/addGetHookIf",
15         "./css/support",
16         "./css/finalPropName",
18         "./core/init",
19         "./core/ready",
20         "./selector" // contains
21 ], function( jQuery, pnum, access, camelCase, document, rcssNum, rnumnonpx, cssExpand,
22         getStyles, swap, curCSS, adjustCSS, addGetHookIf, support, finalPropName ) {
24 "use strict";
26 var
28         // Swappable if display is none or starts with table
29         // except "table", "table-cell", or "table-caption"
30         // See here for display values: https://developer.mozilla.org/en-US/docs/CSS/display
31         rdisplayswap = /^(none|table(?!-c[ea]).+)/,
32         rcustomProp = /^--/,
33         cssShow = { position: "absolute", visibility: "hidden", display: "block" },
34         cssNormalTransform = {
35                 letterSpacing: "0",
36                 fontWeight: "400"
37         };
39 function setPositiveNumber( elem, value, subtract ) {
41         // Any relative (+/-) values have already been
42         // normalized at this point
43         var matches = rcssNum.exec( value );
44         return matches ?
46                 // Guard against undefined "subtract", e.g., when used as in cssHooks
47                 Math.max( 0, matches[ 2 ] - ( subtract || 0 ) ) + ( matches[ 3 ] || "px" ) :
48                 value;
51 function boxModelAdjustment( elem, dimension, box, isBorderBox, styles, computedVal ) {
52         var i = dimension === "width" ? 1 : 0,
53                 extra = 0,
54                 delta = 0;
56         // Adjustment may not be necessary
57         if ( box === ( isBorderBox ? "border" : "content" ) ) {
58                 return 0;
59         }
61         for ( ; i < 4; i += 2 ) {
63                 // Both box models exclude margin
64                 if ( box === "margin" ) {
65                         delta += jQuery.css( elem, box + cssExpand[ i ], true, styles );
66                 }
68                 // If we get here with a content-box, we're seeking "padding" or "border" or "margin"
69                 if ( !isBorderBox ) {
71                         // Add padding
72                         delta += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles );
74                         // For "border" or "margin", add border
75                         if ( box !== "padding" ) {
76                                 delta += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles );
78                         // But still keep track of it otherwise
79                         } else {
80                                 extra += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles );
81                         }
83                 // If we get here with a border-box (content + padding + border), we're seeking "content" or
84                 // "padding" or "margin"
85                 } else {
87                         // For "content", subtract padding
88                         if ( box === "content" ) {
89                                 delta -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles );
90                         }
92                         // For "content" or "padding", subtract border
93                         if ( box !== "margin" ) {
94                                 delta -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles );
95                         }
96                 }
97         }
99         // Account for positive content-box scroll gutter when requested by providing computedVal
100         if ( !isBorderBox && computedVal >= 0 ) {
102                 // offsetWidth/offsetHeight is a rounded sum of content, padding, scroll gutter, and border
103                 // Assuming integer scroll gutter, subtract the rest and round down
104                 delta += Math.max( 0, Math.ceil(
105                         elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] -
106                         computedVal -
107                         delta -
108                         extra -
109                         0.5
110                 ) );
111         }
113         return delta;
116 function getWidthOrHeight( elem, dimension, extra ) {
118         // Start with computed style
119         var styles = getStyles( elem ),
120                 val = curCSS( elem, dimension, styles ),
121                 isBorderBox = jQuery.css( elem, "boxSizing", false, styles ) === "border-box",
122                 valueIsBorderBox = isBorderBox;
124         // Support: Firefox <=54
125         // Return a confounding non-pixel value or feign ignorance, as appropriate.
126         if ( rnumnonpx.test( val ) ) {
127                 if ( !extra ) {
128                         return val;
129                 }
130                 val = "auto";
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 = valueIsBorderBox &&
136                 ( support.boxSizingReliable() || val === elem.style[ dimension ] );
138         // Fall back to offsetWidth/offsetHeight when value is "auto"
139         // This happens for inline elements with no explicit setting (gh-3571)
140         // Support: Android <=4.1 - 4.3 only
141         // Also use offsetWidth/offsetHeight for misreported inline dimensions (gh-3602)
142         if ( val === "auto" ||
143                 !parseFloat( val ) && jQuery.css( elem, "display", false, styles ) === "inline" ) {
145                 val = elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ];
147                 // offsetWidth/offsetHeight provide border-box values
148                 valueIsBorderBox = true;
149         }
151         // Normalize "" and auto
152         val = parseFloat( val ) || 0;
154         // Adjust for the element's box model
155         return ( val +
156                 boxModelAdjustment(
157                         elem,
158                         dimension,
159                         extra || ( isBorderBox ? "border" : "content" ),
160                         valueIsBorderBox,
161                         styles,
163                         // Provide the current computed size to request scroll gutter calculation (gh-3589)
164                         val
165                 )
166         ) + "px";
169 jQuery.extend( {
171         // Add in style property hooks for overriding the default
172         // behavior of getting and setting a style property
173         cssHooks: {
174                 opacity: {
175                         get: function( elem, computed ) {
176                                 if ( computed ) {
178                                         // We should always get a number back from opacity
179                                         var ret = curCSS( elem, "opacity" );
180                                         return ret === "" ? "1" : ret;
181                                 }
182                         }
183                 }
184         },
186         // Don't automatically add "px" to these possibly-unitless properties
187         cssNumber: {
188                 "animationIterationCount": true,
189                 "columnCount": true,
190                 "fillOpacity": true,
191                 "flexGrow": true,
192                 "flexShrink": true,
193                 "fontWeight": true,
194                 "lineHeight": true,
195                 "opacity": true,
196                 "order": true,
197                 "orphans": true,
198                 "widows": true,
199                 "zIndex": true,
200                 "zoom": true
201         },
203         // Add in properties whose names you wish to fix before
204         // setting or getting the value
205         cssProps: {},
207         // Get and set the style property on a DOM Node
208         style: function( elem, name, value, extra ) {
210                 // Don't set styles on text and comment nodes
211                 if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) {
212                         return;
213                 }
215                 // Make sure that we're working with the right name
216                 var ret, type, hooks,
217                         origName = camelCase( name ),
218                         isCustomProp = rcustomProp.test( name ),
219                         style = elem.style;
221                 // Make sure that we're working with the right name. We don't
222                 // want to query the value if it is a CSS custom property
223                 // since they are user-defined.
224                 if ( !isCustomProp ) {
225                         name = finalPropName( origName );
226                 }
228                 // Gets hook for the prefixed version, then unprefixed version
229                 hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];
231                 // Check if we're setting a value
232                 if ( value !== undefined ) {
233                         type = typeof value;
235                         // Convert "+=" or "-=" to relative numbers (#7345)
236                         if ( type === "string" && ( ret = rcssNum.exec( value ) ) && ret[ 1 ] ) {
237                                 value = adjustCSS( elem, name, ret );
239                                 // Fixes bug #9237
240                                 type = "number";
241                         }
243                         // Make sure that null and NaN values aren't set (#7116)
244                         if ( value == null || value !== value ) {
245                                 return;
246                         }
248                         // If a number was passed in, add the unit (except for certain CSS properties)
249                         if ( type === "number" ) {
250                                 value += ret && ret[ 3 ] || ( jQuery.cssNumber[ origName ] ? "" : "px" );
251                         }
253                         // background-* props affect original clone's values
254                         if ( !support.clearCloneStyle && value === "" && name.indexOf( "background" ) === 0 ) {
255                                 style[ name ] = "inherit";
256                         }
258                         // If a hook was provided, use that value, otherwise just set the specified value
259                         if ( !hooks || !( "set" in hooks ) ||
260                                 ( value = hooks.set( elem, value, extra ) ) !== undefined ) {
262                                 if ( isCustomProp ) {
263                                         style.setProperty( name, value );
264                                 } else {
265                                         style[ name ] = value;
266                                 }
267                         }
269                 } else {
271                         // If a hook was provided get the non-computed value from there
272                         if ( hooks && "get" in hooks &&
273                                 ( ret = hooks.get( elem, false, extra ) ) !== undefined ) {
275                                 return ret;
276                         }
278                         // Otherwise just get the value from the style object
279                         return style[ name ];
280                 }
281         },
283         css: function( elem, name, extra, styles ) {
284                 var val, num, hooks,
285                         origName = camelCase( name ),
286                         isCustomProp = rcustomProp.test( name );
288                 // Make sure that we're working with the right name. We don't
289                 // want to modify the value if it is a CSS custom property
290                 // since they are user-defined.
291                 if ( !isCustomProp ) {
292                         name = finalPropName( origName );
293                 }
295                 // Try prefixed name followed by the unprefixed name
296                 hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];
298                 // If a hook was provided get the computed value from there
299                 if ( hooks && "get" in hooks ) {
300                         val = hooks.get( elem, true, extra );
301                 }
303                 // Otherwise, if a way to get the computed value exists, use that
304                 if ( val === undefined ) {
305                         val = curCSS( elem, name, styles );
306                 }
308                 // Convert "normal" to computed value
309                 if ( val === "normal" && name in cssNormalTransform ) {
310                         val = cssNormalTransform[ name ];
311                 }
313                 // Make numeric if forced or a qualifier was provided and val looks numeric
314                 if ( extra === "" || extra ) {
315                         num = parseFloat( val );
316                         return extra === true || isFinite( num ) ? num || 0 : val;
317                 }
319                 return val;
320         }
321 } );
323 jQuery.each( [ "height", "width" ], function( i, dimension ) {
324         jQuery.cssHooks[ dimension ] = {
325                 get: function( elem, computed, extra ) {
326                         if ( computed ) {
328                                 // Certain elements can have dimension info if we invisibly show them
329                                 // but it must have a current display style that would benefit
330                                 return rdisplayswap.test( jQuery.css( elem, "display" ) ) &&
332                                         // Support: Safari 8+
333                                         // Table columns in Safari have non-zero offsetWidth & zero
334                                         // getBoundingClientRect().width unless display is changed.
335                                         // Support: IE <=11 only
336                                         // Running getBoundingClientRect on a disconnected node
337                                         // in IE throws an error.
338                                         ( !elem.getClientRects().length || !elem.getBoundingClientRect().width ) ?
339                                                 swap( elem, cssShow, function() {
340                                                         return getWidthOrHeight( elem, dimension, extra );
341                                                 } ) :
342                                                 getWidthOrHeight( elem, dimension, extra );
343                         }
344                 },
346                 set: function( elem, value, extra ) {
347                         var matches,
348                                 styles = getStyles( elem ),
349                                 scrollBoxSize = support.scrollboxSize() === styles.position,
351                                 // To avoid forcing a reflow, only fetch boxSizing if we need it (gh-3991)
352                                 boxSizingNeeded = scrollBoxSize || extra,
353                                 isBorderBox = boxSizingNeeded &&
354                                         jQuery.css( elem, "boxSizing", false, styles ) === "border-box",
355                                 subtract = extra ?
356                                         boxModelAdjustment(
357                                                 elem,
358                                                 dimension,
359                                                 extra,
360                                                 isBorderBox,
361                                                 styles
362                                         ) :
363                                         0;
365                         // Account for unreliable border-box dimensions by comparing offset* to computed and
366                         // faking a content-box to get border and padding (gh-3699)
367                         if ( isBorderBox && scrollBoxSize ) {
368                                 subtract -= Math.ceil(
369                                         elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] -
370                                         parseFloat( styles[ dimension ] ) -
371                                         boxModelAdjustment( elem, dimension, "border", false, styles ) -
372                                         0.5
373                                 );
374                         }
376                         // Convert to pixels if value adjustment is needed
377                         if ( subtract && ( matches = rcssNum.exec( value ) ) &&
378                                 ( matches[ 3 ] || "px" ) !== "px" ) {
380                                 elem.style[ dimension ] = value;
381                                 value = jQuery.css( elem, dimension );
382                         }
384                         return setPositiveNumber( elem, value, subtract );
385                 }
386         };
387 } );
389 jQuery.cssHooks.marginLeft = addGetHookIf( support.reliableMarginLeft,
390         function( elem, computed ) {
391                 if ( computed ) {
392                         return ( parseFloat( curCSS( elem, "marginLeft" ) ) ||
393                                 elem.getBoundingClientRect().left -
394                                         swap( elem, { marginLeft: 0 }, function() {
395                                                 return elem.getBoundingClientRect().left;
396                                         } )
397                                 ) + "px";
398                 }
399         }
402 // These hooks are used by animate to expand properties
403 jQuery.each( {
404         margin: "",
405         padding: "",
406         border: "Width"
407 }, function( prefix, suffix ) {
408         jQuery.cssHooks[ prefix + suffix ] = {
409                 expand: function( value ) {
410                         var i = 0,
411                                 expanded = {},
413                                 // Assumes a single number if not a string
414                                 parts = typeof value === "string" ? value.split( " " ) : [ value ];
416                         for ( ; i < 4; i++ ) {
417                                 expanded[ prefix + cssExpand[ i ] + suffix ] =
418                                         parts[ i ] || parts[ i - 2 ] || parts[ 0 ];
419                         }
421                         return expanded;
422                 }
423         };
425         if ( prefix !== "margin" ) {
426                 jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber;
427         }
428 } );
430 jQuery.fn.extend( {
431         css: function( name, value ) {
432                 return access( this, function( elem, name, value ) {
433                         var styles, len,
434                                 map = {},
435                                 i = 0;
437                         if ( Array.isArray( name ) ) {
438                                 styles = getStyles( elem );
439                                 len = name.length;
441                                 for ( ; i < len; i++ ) {
442                                         map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles );
443                                 }
445                                 return map;
446                         }
448                         return value !== undefined ?
449                                 jQuery.style( elem, name, value ) :
450                                 jQuery.css( elem, name );
451                 }, name, value, arguments.length > 1 );
452         }
453 } );
455 return jQuery;
456 } );