Permalink
Browse files

Dimensions: Include scroll gutter in "padding" box

Fixes gh-3589
Closes gh-3656
  • Loading branch information...
gibson042 committed Jun 19, 2017
1 parent 5bdd1ca commit 80f1c8239e9d5f793f3e54e0cb8d7bd0747e4856
Showing with 151 additions and 46 deletions.
  1. +68 −46 src/css.js
  2. +83 −0 test/unit/dimensions.js
@@ -80,58 +80,77 @@ function setPositiveNumber( elem, value, subtract ) {
value;
}

function augmentWidthOrHeight( elem, name, extra, isBorderBox, styles ) {
var i,
val = 0;

// If we already have the right measurement, avoid augmentation
if ( extra === ( isBorderBox ? "border" : "content" ) ) {
i = 4;

// Otherwise initialize for horizontal or vertical properties
} else {
i = name === "width" ? 1 : 0;
function boxModelAdjustment( elem, dimension, box, isBorderBox, styles, computedVal ) {
var i = dimension === "width" ? 1 : 0,
extra = 0,
delta = 0;

// Adjustment may not be necessary
if ( box === ( isBorderBox ? "border" : "content" ) ) {
return 0;
}

for ( ; i < 4; i += 2 ) {

// Both box models exclude margin, so add it if we want it
if ( extra === "margin" ) {
val += jQuery.css( elem, extra + cssExpand[ i ], true, styles );
// Both box models exclude margin
if ( box === "margin" ) {
delta += jQuery.css( elem, box + cssExpand[ i ], true, styles );
}

if ( isBorderBox ) {
// If we get here with a content-box, we're seeking "padding" or "border" or "margin"
if ( !isBorderBox ) {

// border-box includes padding, so remove it if we want content
if ( extra === "content" ) {
val -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles );
}
// Add padding
delta += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles );

// At this point, extra isn't border nor margin, so remove border
if ( extra !== "margin" ) {
val -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles );
// For "border" or "margin", add border
if ( box !== "padding" ) {
delta += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles );

// But still keep track of it otherwise
} else {
extra += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles );
}

// If we get here with a border-box (content + padding + border), we're seeking "content" or
// "padding" or "margin"
} else {

// At this point, extra isn't content, so add padding
val += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles );
// For "content", subtract padding
if ( box === "content" ) {
delta -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles );
}

// At this point, extra isn't content nor padding, so add border
if ( extra !== "padding" ) {
val += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles );
// For "content" or "padding", subtract border
if ( box !== "margin" ) {
delta -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles );
}
}
}

return val;
// Account for positive content-box scroll gutter when requested by providing computedVal
if ( !isBorderBox && computedVal >= 0 ) {

// offsetWidth/offsetHeight is a rounded sum of content, padding, scroll gutter, and border
// Assuming integer scroll gutter, subtract the rest and round down
delta += Math.max( 0, Math.ceil(
elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] -
computedVal -
delta -
extra -
0.5
) );
}

return delta;
}

function getWidthOrHeight( elem, name, extra ) {
function getWidthOrHeight( elem, dimension, extra ) {

// Start with computed style
var valueIsBorderBox,
styles = getStyles( elem ),
val = curCSS( elem, name, styles ),
val = curCSS( elem, dimension, styles ),
isBorderBox = jQuery.css( elem, "boxSizing", false, styles ) === "border-box";

// Computed unit is not pixels. Stop here and return.
@@ -142,25 +161,28 @@ function getWidthOrHeight( elem, name, extra ) {
// Check for style in case a browser which returns unreliable values
// for getComputedStyle silently falls back to the reliable elem.style
valueIsBorderBox = isBorderBox &&
( support.boxSizingReliable() || val === elem.style[ name ] );
( support.boxSizingReliable() || val === elem.style[ dimension ] );

// Fall back to offsetWidth/Height when value is "auto"
// This happens for inline elements with no explicit setting (gh-3571)
if ( val === "auto" ) {
val = elem[ "offset" + name[ 0 ].toUpperCase() + name.slice( 1 ) ];
val = elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ];
}

// Normalize "", auto, and prepare for extra
// Normalize "" and auto
val = parseFloat( val ) || 0;

// Use the active box-sizing model to add/subtract irrelevant styles
// Adjust for the element's box model
return ( val +
augmentWidthOrHeight(
boxModelAdjustment(
elem,
name,
dimension,
extra || ( isBorderBox ? "border" : "content" ),
valueIsBorderBox,
styles
styles,

// Provide the current computed size to request scroll gutter calculation (gh-3589)
val
)
) + "px";
}
@@ -319,8 +341,8 @@ jQuery.extend( {
}
} );

jQuery.each( [ "height", "width" ], function( i, name ) {
jQuery.cssHooks[ name ] = {
jQuery.each( [ "height", "width" ], function( i, dimension ) {
jQuery.cssHooks[ dimension ] = {
get: function( elem, computed, extra ) {
if ( computed ) {

@@ -336,18 +358,18 @@ jQuery.each( [ "height", "width" ], function( i, name ) {
// in IE throws an error.
( !elem.getClientRects().length || !elem.getBoundingClientRect().width ) ?
swap( elem, cssShow, function() {
return getWidthOrHeight( elem, name, extra );
return getWidthOrHeight( elem, dimension, extra );
} ) :
getWidthOrHeight( elem, name, extra );
getWidthOrHeight( elem, dimension, extra );
}
},

set: function( elem, value, extra ) {
var matches,
styles = extra && getStyles( elem ),
subtract = extra && augmentWidthOrHeight(
styles = getStyles( elem ),
subtract = extra && boxModelAdjustment(
elem,
name,
dimension,
extra,
jQuery.css( elem, "boxSizing", false, styles ) === "border-box",
styles
@@ -357,8 +379,8 @@ jQuery.each( [ "height", "width" ], function( i, name ) {
if ( subtract && ( matches = rcssNum.exec( value ) ) &&
( matches[ 3 ] || "px" ) !== "px" ) {

elem.style[ name ] = value;
value = jQuery.css( elem, name );
elem.style[ dimension ] = value;
value = jQuery.css( elem, dimension );
}

return setPositiveNumber( elem, value, subtract );
@@ -544,4 +544,87 @@ QUnit.test( "width/height on an inline element with no explicitly-set dimensions
} );
} );

QUnit.test( "interaction with scrollbars (gh-3589)", function( assert ) {
assert.expect( 36 );

var i,
suffix = "",
updater = function( adjustment ) {
return function( i, old ) {
return old + adjustment;
};
},
parent = jQuery( "<div/>" )
.css( { position: "absolute", width: "1000px", height: "1000px" } )
.appendTo( "#qunit-fixture" ),
fraction = jQuery( "<div style='width:4.5px;'/>" ).appendTo( parent ).width() % 1,
borderWidth = 1,
padding = 2,
size = 100 + fraction,
scrollBox = {
position: "absolute",
overflow: "scroll",
width: size + "px",
height: size + "px"
},
borderBox = {
border: borderWidth + "px solid blue",
padding: padding + "px"
},
plainContentBox = jQuery( "<div />" )
.css( scrollBox )
.css( { "box-sizing": "content-box" } )
.appendTo( parent ),
contentBox = jQuery( "<div />" )
.css( scrollBox )
.css( borderBox )
.css( { "box-sizing": "content-box" } )
.appendTo( parent ),
borderBox = jQuery( "<div />" )
.css( scrollBox )
.css( borderBox )
.css( { "box-sizing": "border-box" } )
.appendTo( parent ),
$boxes = jQuery( [ plainContentBox[ 0 ], contentBox[ 0 ], borderBox[ 0 ] ] );

for ( i = 0; i < 3; i++ ) {
if ( i === 1 ) {
suffix = " after increasing inner* by " + i;
size += i;
$boxes.innerWidth( updater( i ) ).innerHeight( updater( i ) );
} else if ( i === 2 ) {
suffix = " after increasing outer* by " + i;
size += i;
$boxes.outerWidth( updater( i ) ).outerHeight( updater( i ) );
}

assert.equal( plainContentBox.innerWidth(), size,
"plain content-box innerWidth includes scroll gutter" + suffix );
assert.equal( plainContentBox.innerHeight(), size,
"plain content-box innerHeight includes scroll gutter" + suffix );
assert.equal( plainContentBox.outerWidth(), size,
"plain content-box outerWidth includes scroll gutter" + suffix );
assert.equal( plainContentBox.outerHeight(), size,
"plain content-box outerHeight includes scroll gutter" + suffix );

assert.equal( contentBox.innerWidth(), size + 2 * padding,
"content-box innerWidth includes scroll gutter" + suffix );
assert.equal( contentBox.innerHeight(), size + 2 * padding,
"content-box innerHeight includes scroll gutter" + suffix );
assert.equal( contentBox.outerWidth(), size + 2 * padding + 2 * borderWidth,
"content-box outerWidth includes scroll gutter" + suffix );
assert.equal( contentBox.outerHeight(), size + 2 * padding + 2 * borderWidth,
"content-box outerHeight includes scroll gutter" + suffix );

assert.equal( borderBox.innerWidth(), size - 2 * borderWidth,
"border-box innerWidth includes scroll gutter" + suffix );
assert.equal( borderBox.innerHeight(), size - 2 * borderWidth,
"border-box innerHeight includes scroll gutter" + suffix );
assert.equal( borderBox.outerWidth(), size,
"border-box outerWidth includes scroll gutter" + suffix );
assert.equal( borderBox.outerHeight(), size,
"border-box outerHeight includes scroll gutter" + suffix );
}
} );

} )();

0 comments on commit 80f1c82

Please sign in to comment.