Tests: Add custom attribute getter tests to the selector module
[jquery.git] / src / css.js
blob72a807bfca80218351d619364f1a023a68e0ac46
1 import { jQuery } from "./core.js";
2 import { access } from "./core/access.js";
3 import { nodeName } from "./core/nodeName.js";
4 import { rcssNum } from "./var/rcssNum.js";
5 import { isIE } from "./var/isIE.js";
6 import { rnumnonpx } from "./css/var/rnumnonpx.js";
7 import { rcustomProp } from "./css/var/rcustomProp.js";
8 import { cssExpand } from "./css/var/cssExpand.js";
9 import { isAutoPx } from "./css/isAutoPx.js";
10 import { cssCamelCase } from "./css/cssCamelCase.js";
11 import { getStyles } from "./css/var/getStyles.js";
12 import { swap } from "./css/var/swap.js";
13 import { curCSS } from "./css/curCSS.js";
14 import { adjustCSS } from "./css/adjustCSS.js";
15 import { finalPropName } from "./css/finalPropName.js";
16 import { support } from "./css/support.js";
18 import "./core/init.js";
19 import "./core/ready.js";
21 var
23         // Swappable if display is none or starts with table
24         // except "table", "table-cell", or "table-caption"
25         // See here for display values: https://developer.mozilla.org/en-US/docs/CSS/display
26         rdisplayswap = /^(none|table(?!-c[ea]).+)/,
27         cssShow = { position: "absolute", visibility: "hidden", display: "block" },
28         cssNormalTransform = {
29                 letterSpacing: "0",
30                 fontWeight: "400"
31         };
33 function setPositiveNumber( _elem, value, subtract ) {
35         // Any relative (+/-) values have already been
36         // normalized at this point
37         var matches = rcssNum.exec( value );
38         return matches ?
40                 // Guard against undefined "subtract", e.g., when used as in cssHooks
41                 Math.max( 0, matches[ 2 ] - ( subtract || 0 ) ) + ( matches[ 3 ] || "px" ) :
42                 value;
45 function boxModelAdjustment( elem, dimension, box, isBorderBox, styles, computedVal ) {
46         var i = dimension === "width" ? 1 : 0,
47                 extra = 0,
48                 delta = 0,
49                 marginDelta = 0;
51         // Adjustment may not be necessary
52         if ( box === ( isBorderBox ? "border" : "content" ) ) {
53                 return 0;
54         }
56         for ( ; i < 4; i += 2 ) {
58                 // Both box models exclude margin
59                 // Count margin delta separately to only add it after scroll gutter adjustment.
60                 // This is needed to make negative margins work with `outerHeight( true )` (gh-3982).
61                 if ( box === "margin" ) {
62                         marginDelta += jQuery.css( elem, box + cssExpand[ i ], true, styles );
63                 }
65                 // If we get here with a content-box, we're seeking "padding" or "border" or "margin"
66                 if ( !isBorderBox ) {
68                         // Add padding
69                         delta += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles );
71                         // For "border" or "margin", add border
72                         if ( box !== "padding" ) {
73                                 delta += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles );
75                         // But still keep track of it otherwise
76                         } else {
77                                 extra += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles );
78                         }
80                 // If we get here with a border-box (content + padding + border), we're seeking "content" or
81                 // "padding" or "margin"
82                 } else {
84                         // For "content", subtract padding
85                         if ( box === "content" ) {
86                                 delta -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles );
87                         }
89                         // For "content" or "padding", subtract border
90                         if ( box !== "margin" ) {
91                                 delta -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles );
92                         }
93                 }
94         }
96         // Account for positive content-box scroll gutter when requested by providing computedVal
97         if ( !isBorderBox && computedVal >= 0 ) {
99                 // offsetWidth/offsetHeight is a rounded sum of content, padding, scroll gutter, and border
100                 // Assuming integer scroll gutter, subtract the rest and round down
101                 delta += Math.max( 0, Math.ceil(
102                         elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] -
103                         computedVal -
104                         delta -
105                         extra -
106                         0.5
108                 // If offsetWidth/offsetHeight is unknown, then we can't determine content-box scroll gutter
109                 // Use an explicit zero to avoid NaN (gh-3964)
110                 ) ) || 0;
111         }
113         return delta + marginDelta;
116 function getWidthOrHeight( elem, dimension, extra ) {
118         // Start with computed style
119         var styles = getStyles( elem ),
121                 // To avoid forcing a reflow, only fetch boxSizing if we need it (gh-4322).
122                 // Fake content-box until we know it's needed to know the true value.
123                 boxSizingNeeded = isIE || extra,
124                 isBorderBox = boxSizingNeeded &&
125                         jQuery.css( elem, "boxSizing", false, styles ) === "border-box",
126                 valueIsBorderBox = isBorderBox,
128                 val = curCSS( elem, dimension, styles ),
129                 offsetProp = "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 );
131         // Return a confounding non-pixel value or feign ignorance, as appropriate.
132         if ( rnumnonpx.test( val ) ) {
133                 if ( !extra ) {
134                         return val;
135                 }
136                 val = "auto";
137         }
140         if ( (
142                 // Fall back to offsetWidth/offsetHeight when value is "auto"
143                 // This happens for inline elements with no explicit setting (gh-3571)
144                 val === "auto" ||
146                 // Support: IE 9 - 11+
147                 // Use offsetWidth/offsetHeight for when box sizing is unreliable.
148                 // In those cases, the computed value can be trusted to be border-box.
149                 ( isIE && isBorderBox ) ||
151                 // Support: IE 10 - 11+
152                 // IE misreports `getComputedStyle` of table rows with width/height
153                 // set in CSS while `offset*` properties report correct values.
154                 // Support: Firefox 70+
155                 // Firefox includes border widths
156                 // in computed dimensions for table rows. (gh-4529)
157                 ( !support.reliableTrDimensions() && nodeName( elem, "tr" ) ) ) &&
159                 // Make sure the element is visible & connected
160                 elem.getClientRects().length ) {
162                 isBorderBox = jQuery.css( elem, "boxSizing", false, styles ) === "border-box";
164                 // Where available, offsetWidth/offsetHeight approximate border box dimensions.
165                 // Where not available (e.g., SVG), assume unreliable box-sizing and interpret the
166                 // retrieved value as a content box dimension.
167                 valueIsBorderBox = offsetProp in elem;
168                 if ( valueIsBorderBox ) {
169                         val = elem[ offsetProp ];
170                 }
171         }
173         // Normalize "" and auto
174         val = parseFloat( val ) || 0;
176         // Adjust for the element's box model
177         return ( val +
178                 boxModelAdjustment(
179                         elem,
180                         dimension,
181                         extra || ( isBorderBox ? "border" : "content" ),
182                         valueIsBorderBox,
183                         styles,
185                         // Provide the current computed size to request scroll gutter calculation (gh-3589)
186                         val
187                 )
188         ) + "px";
191 jQuery.extend( {
193         // Add in style property hooks for overriding the default
194         // behavior of getting and setting a style property
195         cssHooks: {},
197         // Get and set the style property on a DOM Node
198         style: function( elem, name, value, extra ) {
200                 // Don't set styles on text and comment nodes
201                 if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) {
202                         return;
203                 }
205                 // Make sure that we're working with the right name
206                 var ret, type, hooks,
207                         origName = cssCamelCase( name ),
208                         isCustomProp = rcustomProp.test( name ),
209                         style = elem.style;
211                 // Make sure that we're working with the right name. We don't
212                 // want to query the value if it is a CSS custom property
213                 // since they are user-defined.
214                 if ( !isCustomProp ) {
215                         name = finalPropName( origName );
216                 }
218                 // Gets hook for the prefixed version, then unprefixed version
219                 hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];
221                 // Check if we're setting a value
222                 if ( value !== undefined ) {
223                         type = typeof value;
225                         // Convert "+=" or "-=" to relative numbers (trac-7345)
226                         if ( type === "string" && ( ret = rcssNum.exec( value ) ) && ret[ 1 ] ) {
227                                 value = adjustCSS( elem, name, ret );
229                                 // Fixes bug trac-9237
230                                 type = "number";
231                         }
233                         // Make sure that null and NaN values aren't set (trac-7116)
234                         if ( value == null || value !== value ) {
235                                 return;
236                         }
238                         // If the value is a number, add `px` for certain CSS properties
239                         if ( type === "number" ) {
240                                 value += ret && ret[ 3 ] || ( isAutoPx( origName ) ? "px" : "" );
241                         }
243                         // Support: IE <=9 - 11+
244                         // background-* props of a cloned element affect the source element (trac-8908)
245                         if ( isIE && value === "" && name.indexOf( "background" ) === 0 ) {
246                                 style[ name ] = "inherit";
247                         }
249                         // If a hook was provided, use that value, otherwise just set the specified value
250                         if ( !hooks || !( "set" in hooks ) ||
251                                 ( value = hooks.set( elem, value, extra ) ) !== undefined ) {
253                                 if ( isCustomProp ) {
254                                         style.setProperty( name, value );
255                                 } else {
256                                         style[ name ] = value;
257                                 }
258                         }
260                 } else {
262                         // If a hook was provided get the non-computed value from there
263                         if ( hooks && "get" in hooks &&
264                                 ( ret = hooks.get( elem, false, extra ) ) !== undefined ) {
266                                 return ret;
267                         }
269                         // Otherwise just get the value from the style object
270                         return style[ name ];
271                 }
272         },
274         css: function( elem, name, extra, styles ) {
275                 var val, num, hooks,
276                         origName = cssCamelCase( name ),
277                         isCustomProp = rcustomProp.test( name );
279                 // Make sure that we're working with the right name. We don't
280                 // want to modify the value if it is a CSS custom property
281                 // since they are user-defined.
282                 if ( !isCustomProp ) {
283                         name = finalPropName( origName );
284                 }
286                 // Try prefixed name followed by the unprefixed name
287                 hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];
289                 // If a hook was provided get the computed value from there
290                 if ( hooks && "get" in hooks ) {
291                         val = hooks.get( elem, true, extra );
292                 }
294                 // Otherwise, if a way to get the computed value exists, use that
295                 if ( val === undefined ) {
296                         val = curCSS( elem, name, styles );
297                 }
299                 // Convert "normal" to computed value
300                 if ( val === "normal" && name in cssNormalTransform ) {
301                         val = cssNormalTransform[ name ];
302                 }
304                 // Make numeric if forced or a qualifier was provided and val looks numeric
305                 if ( extra === "" || extra ) {
306                         num = parseFloat( val );
307                         return extra === true || isFinite( num ) ? num || 0 : val;
308                 }
310                 return val;
311         }
312 } );
314 jQuery.each( [ "height", "width" ], function( _i, dimension ) {
315         jQuery.cssHooks[ dimension ] = {
316                 get: function( elem, computed, extra ) {
317                         if ( computed ) {
319                                 // Certain elements can have dimension info if we invisibly show them
320                                 // but it must have a current display style that would benefit
321                                 return rdisplayswap.test( jQuery.css( elem, "display" ) ) &&
323                                         // Support: Safari <=8 - 12+, Chrome <=73+
324                                         // Table columns in WebKit/Blink have non-zero offsetWidth & zero
325                                         // getBoundingClientRect().width unless display is changed.
326                                         // Support: IE <=11+
327                                         // Running getBoundingClientRect on a disconnected node
328                                         // in IE throws an error.
329                                         ( !elem.getClientRects().length || !elem.getBoundingClientRect().width ) ?
330                                         swap( elem, cssShow, function() {
331                                                 return getWidthOrHeight( elem, dimension, extra );
332                                         } ) :
333                                         getWidthOrHeight( elem, dimension, extra );
334                         }
335                 },
337                 set: function( elem, value, extra ) {
338                         var matches,
339                                 styles = getStyles( elem ),
341                                 // To avoid forcing a reflow, only fetch boxSizing if we need it (gh-3991)
342                                 isBorderBox = extra &&
343                                         jQuery.css( elem, "boxSizing", false, styles ) === "border-box",
344                                 subtract = extra ?
345                                         boxModelAdjustment(
346                                                 elem,
347                                                 dimension,
348                                                 extra,
349                                                 isBorderBox,
350                                                 styles
351                                         ) :
352                                         0;
354                         // Convert to pixels if value adjustment is needed
355                         if ( subtract && ( matches = rcssNum.exec( value ) ) &&
356                                 ( matches[ 3 ] || "px" ) !== "px" ) {
358                                 elem.style[ dimension ] = value;
359                                 value = jQuery.css( elem, dimension );
360                         }
362                         return setPositiveNumber( elem, value, subtract );
363                 }
364         };
365 } );
367 // These hooks are used by animate to expand properties
368 jQuery.each( {
369         margin: "",
370         padding: "",
371         border: "Width"
372 }, function( prefix, suffix ) {
373         jQuery.cssHooks[ prefix + suffix ] = {
374                 expand: function( value ) {
375                         var i = 0,
376                                 expanded = {},
378                                 // Assumes a single number if not a string
379                                 parts = typeof value === "string" ? value.split( " " ) : [ value ];
381                         for ( ; i < 4; i++ ) {
382                                 expanded[ prefix + cssExpand[ i ] + suffix ] =
383                                         parts[ i ] || parts[ i - 2 ] || parts[ 0 ];
384                         }
386                         return expanded;
387                 }
388         };
390         if ( prefix !== "margin" ) {
391                 jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber;
392         }
393 } );
395 jQuery.fn.extend( {
396         css: function( name, value ) {
397                 return access( this, function( elem, name, value ) {
398                         var styles, len,
399                                 map = {},
400                                 i = 0;
402                         if ( Array.isArray( name ) ) {
403                                 styles = getStyles( elem );
404                                 len = name.length;
406                                 for ( ; i < len; i++ ) {
407                                         map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles );
408                                 }
410                                 return map;
411                         }
413                         return value !== undefined ?
414                                 jQuery.style( elem, name, value ) :
415                                 jQuery.css( elem, name );
416                 }, name, value, arguments.length > 1 );
417         }
418 } );
420 export { jQuery, jQuery as $ };