diff --git a/.stylelintrc b/.stylelintrc index 50a9473ce34f069058362356f4ac3109bc4a316f..93af80b7de5cbb4474e61012537ce1a50be4cc08 100644 --- a/.stylelintrc +++ b/.stylelintrc @@ -10,6 +10,7 @@ "border-bottom-right-radius", "border-bottom-left-radius", "transition" - ] + ], + "function-blacklist": ["calc"] } } diff --git a/scss/_custom-forms.scss b/scss/_custom-forms.scss index 8b3a74bc0b6027d3a82c20f33d36d7ff15ef75d2..7df1688fec9c2c29f6e2223e14583e82fecc0bc3 100644 --- a/scss/_custom-forms.scss +++ b/scss/_custom-forms.scss @@ -183,8 +183,8 @@ } &::after { - top: calc(#{(($font-size-base * $line-height-base - $custom-control-indicator-size) / 2)} + #{$custom-control-indicator-border-width * 2}); - left: calc(#{-($custom-switch-width + $custom-control-gutter)} + #{$custom-control-indicator-border-width * 2}); + top: add(($font-size-base * $line-height-base - $custom-control-indicator-size) / 2, $custom-control-indicator-border-width * 2); + left: add(-($custom-switch-width + $custom-control-gutter), $custom-control-indicator-border-width * 2); width: $custom-switch-indicator-size; height: $custom-switch-indicator-size; background-color: $custom-control-indicator-border-color; @@ -378,7 +378,7 @@ .custom-range { width: 100%; - height: calc(#{$custom-range-thumb-height} + #{$custom-range-thumb-focus-box-shadow-width * 2}); + height: add($custom-range-thumb-height, $custom-range-thumb-focus-box-shadow-width * 2); padding: 0; // Need to reset padding background-color: transparent; appearance: none; diff --git a/scss/_forms.scss b/scss/_forms.scss index 7a9a653e4553fae1924cb22006631c392fbc85f6..f91789d656289bb8fcefac54ad6446e7a7e7e7f7 100644 --- a/scss/_forms.scss +++ b/scss/_forms.scss @@ -86,23 +86,23 @@ select.form-control { // For use with horizontal and inline forms, when you need the label (or legend) // text to align with the form controls. .col-form-label { - padding-top: calc(#{$input-padding-y} + #{$input-border-width}); - padding-bottom: calc(#{$input-padding-y} + #{$input-border-width}); + padding-top: add($input-padding-y, $input-border-width); + padding-bottom: add($input-padding-y, $input-border-width); margin-bottom: 0; // Override the `<label>/<legend>` default @include font-size(inherit); // Override the `<legend>` default line-height: $input-line-height; } .col-form-label-lg { - padding-top: calc(#{$input-padding-y-lg} + #{$input-border-width}); - padding-bottom: calc(#{$input-padding-y-lg} + #{$input-border-width}); + padding-top: add($input-padding-y-lg, $input-border-width); + padding-bottom: add($input-padding-y-lg, $input-border-width); @include font-size($input-font-size-lg); line-height: $input-line-height-lg; } .col-form-label-sm { - padding-top: calc(#{$input-padding-y-sm} + #{$input-border-width}); - padding-bottom: calc(#{$input-padding-y-sm} + #{$input-border-width}); + padding-top: add($input-padding-y-sm, $input-border-width); + padding-bottom: add($input-padding-y-sm, $input-border-width); @include font-size($input-font-size-sm); line-height: $input-line-height-sm; } diff --git a/scss/_functions.scss b/scss/_functions.scss index 930b7f8972e2fb9f1f5a778a41e4a698378b699f..fc789ac258bcb6e127060db3445f2363f7b52c1e 100644 --- a/scss/_functions.scss +++ b/scss/_functions.scss @@ -95,3 +95,40 @@ @return mix($color-base, $color, $level * $theme-color-interval); } + +// Return valid calc +@function add($value1, $value2, $return-calc: true) { + @if $value1 == null { + @return $value2; + } + + @if $value2 == null { + @return $value1; + } + + @if type-of($value1) == number and type-of($value2) == number and comparable($value1, $value2) { + @return $value1 + $value2; + } + + @return if($return-calc == true, calc(#{$value1} + #{$value2}), #{$value1} + #{$value2}); +} + +@function subtract($value1, $value2, $return-calc: true) { + @if $value1 == null and $value2 == null { + @return null; + } + + @if $value1 == null { + @return -$value2; + } + + @if $value2 == null { + @return $value1; + } + + @if type-of($value1) == number and type-of($value2) == number and comparable($value1, $value2) { + @return $value1 - $value2; + } + + @return if($return-calc == true, calc(#{$value1} - #{$value2}), #{$value1} - #{$value2}); +} diff --git a/scss/_modal.scss b/scss/_modal.scss index bc08617c95cfa5a1d5720d425306fb804eb9e53d..9a036883a07a1f647b0968fa3aa296694ebfeb6c 100644 --- a/scss/_modal.scss +++ b/scss/_modal.scss @@ -52,10 +52,10 @@ .modal-dialog-scrollable { display: flex; // IE10/11 - max-height: calc(100% - #{$modal-dialog-margin * 2}); + max-height: subtract(100%, $modal-dialog-margin * 2); .modal-content { - max-height: calc(100vh - #{$modal-dialog-margin * 2}); // IE10/11 + max-height: subtract(100vh, $modal-dialog-margin * 2); // IE10/11 overflow: hidden; } @@ -72,12 +72,12 @@ .modal-dialog-centered { display: flex; align-items: center; - min-height: calc(100% - #{$modal-dialog-margin * 2}); + min-height: subtract(100%, $modal-dialog-margin * 2); // Ensure `modal-dialog-centered` extends the full height of the view (IE10/11) &::before { display: block; // IE10 - height: calc(100vh - #{$modal-dialog-margin * 2}); + height: subtract(100vh, $modal-dialog-margin * 2); content: ""; } @@ -200,18 +200,18 @@ } .modal-dialog-scrollable { - max-height: calc(100% - #{$modal-dialog-margin-y-sm-up * 2}); + max-height: subtract(100%, $modal-dialog-margin-y-sm-up * 2); .modal-content { - max-height: calc(100vh - #{$modal-dialog-margin-y-sm-up * 2}); + max-height: subtract(100vh, $modal-dialog-margin-y-sm-up * 2); } } .modal-dialog-centered { - min-height: calc(100% - #{$modal-dialog-margin-y-sm-up * 2}); + min-height: subtract(100%, $modal-dialog-margin-y-sm-up * 2); &::before { - height: calc(100vh - #{$modal-dialog-margin-y-sm-up * 2}); + height: subtract(100vh, $modal-dialog-margin-y-sm-up * 2); } } diff --git a/scss/_popover.scss b/scss/_popover.scss index c9b11e94e89e5b62396f4a8184ea64c893b0032e..0ad76af3eeb6fa6619fcd3a794aaf78b9f61c045 100644 --- a/scss/_popover.scss +++ b/scss/_popover.scss @@ -39,7 +39,7 @@ margin-bottom: $popover-arrow-height; > .arrow { - bottom: calc((#{$popover-arrow-height} + #{$popover-border-width}) * -1); + bottom: subtract(-$popover-arrow-height, $popover-border-width); &::before { bottom: 0; @@ -59,7 +59,7 @@ margin-left: $popover-arrow-height; > .arrow { - left: calc((#{$popover-arrow-height} + #{$popover-border-width}) * -1); + left: subtract(-$popover-arrow-height, $popover-border-width); width: $popover-arrow-height; height: $popover-arrow-width; margin: $popover-border-radius 0; // make sure the arrow does not touch the popover's rounded corners @@ -82,7 +82,7 @@ margin-top: $popover-arrow-height; > .arrow { - top: calc((#{$popover-arrow-height} + #{$popover-border-width}) * -1); + top: subtract(-$popover-arrow-height, $popover-border-width); &::before { top: 0; @@ -114,7 +114,7 @@ margin-right: $popover-arrow-height; > .arrow { - right: calc((#{$popover-arrow-height} + #{$popover-border-width}) * -1); + right: subtract(-$popover-arrow-height, $popover-border-width); width: $popover-arrow-height; height: $popover-arrow-width; margin: $popover-border-radius 0; // make sure the arrow does not touch the popover's rounded corners diff --git a/scss/_variables.scss b/scss/_variables.scss index be3a0f7985797d69e4ac63eff0f3726c958071d4..eabe960df895c106dcdf0a3394bc80f8624e24a7 100644 --- a/scss/_variables.scss +++ b/scss/_variables.scss @@ -495,13 +495,13 @@ $input-plaintext-color: $body-color !default; $input-height-border: $input-border-width * 2 !default; -$input-height-inner: calc(#{$input-line-height * 1em} + #{$input-padding-y * 2}) !default; -$input-height-inner-half: calc(#{$input-line-height * .5em} + #{$input-padding-y}) !default; -$input-height-inner-quarter: calc(#{$input-line-height * .25em} + #{$input-padding-y / 2}) !default; +$input-height-inner: add($input-line-height * 1em, $input-padding-y * 2) !default; +$input-height-inner-half: add($input-line-height * .5em, $input-padding-y) !default; +$input-height-inner-quarter: add($input-line-height * .25em, $input-padding-y / 2) !default; -$input-height: calc(#{$input-line-height * 1em} + #{$input-padding-y * 2} + #{$input-height-border}) !default; -$input-height-sm: calc(#{$input-line-height-sm * 1em} + #{$input-btn-padding-y-sm * 2} + #{$input-height-border}) !default; -$input-height-lg: calc(#{$input-line-height-lg * 1em} + #{$input-btn-padding-y-lg * 2} + #{$input-height-border}) !default; +$input-height: add($input-line-height * 1em, add($input-padding-y * 2, $input-height-border, false)) !default; +$input-height-sm: add($input-line-height-sm * 1em, add($input-btn-padding-y-sm * 2, $input-height-border, false)) !default; +$input-height-lg: add($input-line-height-lg * 1em, add($input-btn-padding-y-lg * 2, $input-height-border, false)) !default; $input-transition: border-color .15s ease-in-out, box-shadow .15s ease-in-out !default; @@ -567,7 +567,7 @@ $custom-radio-indicator-icon-checked: url("data:image/svg+xml,<svg xml $custom-switch-width: $custom-control-indicator-size * 1.75 !default; $custom-switch-indicator-border-radius: $custom-control-indicator-size / 2 !default; -$custom-switch-indicator-size: calc(#{$custom-control-indicator-size} - #{$custom-control-indicator-border-width * 4}) !default; +$custom-switch-indicator-size: subtract($custom-control-indicator-size, $custom-control-indicator-border-width * 4) !default; $custom-select-padding-y: $input-padding-y !default; $custom-select-padding-x: $input-padding-x !default; @@ -586,7 +586,7 @@ $custom-select-indicator-color: $gray-800 !default; $custom-select-indicator: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'><path fill='#{$custom-select-indicator-color}' d='M2 0L0 2h4zm0 5L0 3h4z'/></svg>") !default; $custom-select-background: escape-svg($custom-select-indicator) no-repeat right $custom-select-padding-x center / $custom-select-bg-size !default; // Used so we can have multiple background elements (e.g., arrow and feedback icon) -$custom-select-feedback-icon-padding-right: calc((1em + #{2 * $custom-select-padding-y}) * 3 / 4 + #{$custom-select-padding-x + $custom-select-indicator-padding}) !default; +$custom-select-feedback-icon-padding-right: add(1em * .75, (2 * $custom-select-padding-y * .75) + $custom-select-padding-x + $custom-select-indicator-padding) !default; $custom-select-feedback-icon-position: center right ($custom-select-padding-x + $custom-select-indicator-padding) !default; $custom-select-feedback-icon-size: $input-height-inner-half $input-height-inner-half !default; @@ -766,7 +766,7 @@ $dropdown-bg: $white !default; $dropdown-border-color: rgba($black, .15) !default; $dropdown-border-radius: $border-radius !default; $dropdown-border-width: $border-width !default; -$dropdown-inner-border-radius: calc(#{$dropdown-border-radius} - #{$dropdown-border-width}) !default; +$dropdown-inner-border-radius: subtract($dropdown-border-radius, $dropdown-border-width) !default; $dropdown-divider-bg: $gray-200 !default; $dropdown-divider-margin-y: $nav-divider-margin-y !default; $dropdown-box-shadow: 0 .5rem 1rem rgba($black, .175) !default; @@ -831,7 +831,7 @@ $card-spacer-x: 1.25rem !default; $card-border-width: $border-width !default; $card-border-radius: $border-radius !default; $card-border-color: rgba($black, .125) !default; -$card-inner-border-radius: calc(#{$card-border-radius} - #{$card-border-width}) !default; +$card-inner-border-radius: subtract($card-border-radius, $card-border-width) !default; $card-cap-bg: rgba($black, .03) !default; $card-cap-color: null !default; $card-height: null !default; @@ -881,7 +881,7 @@ $popover-max-width: 276px !default; $popover-border-width: $border-width !default; $popover-border-color: rgba($black, .2) !default; $popover-border-radius: $border-radius-lg !default; -$popover-inner-border-radius: calc(#{$popover-border-radius} - #{$popover-border-width}) !default; +$popover-inner-border-radius: subtract($popover-border-radius, $popover-border-width) !default; $popover-box-shadow: 0 .25rem .5rem rgba($black, .2) !default; $popover-header-bg: darken($popover-bg, 3%) !default; @@ -953,7 +953,7 @@ $modal-content-bg: $white !default; $modal-content-border-color: rgba($black, .2) !default; $modal-content-border-width: $border-width !default; $modal-content-border-radius: $border-radius-lg !default; -$modal-content-inner-border-radius: calc(#{$modal-content-border-radius} - #{$modal-content-border-width}) !default; +$modal-content-inner-border-radius: subtract($modal-content-border-radius, $modal-content-border-width) !default; $modal-content-box-shadow-xs: 0 .25rem .5rem rgba($black, .5) !default; $modal-content-box-shadow-sm-up: 0 .5rem 1rem rgba($black, .5) !default; diff --git a/site/docs/4.3/assets/scss/_sidebar.scss b/site/docs/4.3/assets/scss/_sidebar.scss index b302b22fea5cc69a436123c38a7bd5ba5fc13cbb..e5e04be746349f4c4b6375bf0ba70c37866da712 100644 --- a/site/docs/4.3/assets/scss/_sidebar.scss +++ b/site/docs/4.3/assets/scss/_sidebar.scss @@ -8,7 +8,7 @@ @supports (position: sticky) { position: sticky; top: 4rem; - height: calc(100vh - 4rem); + height: subtract(100vh, 4rem); overflow-y: auto; } order: 2; @@ -55,7 +55,7 @@ position: sticky; top: 4rem; z-index: 1000; - height: calc(100vh - 4rem); + height: subtract(100vh, 4rem); } border-right: 1px solid rgba(0, 0, 0, .1); } @@ -73,7 +73,7 @@ @include media-breakpoint-up(md) { @supports (position: sticky) { - max-height: calc(100vh - 9rem); + max-height: subtract(100vh, 9rem); overflow-y: auto; } } diff --git a/site/docs/4.3/getting-started/theming.md b/site/docs/4.3/getting-started/theming.md index 198a55da3a61594e473cc4c69d7e22ef3b72e3a7..99c5066c5872997ef88b359724908be85193be7d 100644 --- a/site/docs/4.3/getting-started/theming.md +++ b/site/docs/4.3/getting-started/theming.md @@ -227,6 +227,44 @@ You can also specify a base color with our color map functions: We use the `escape-svg` function to escape the `<`, `>` and `#` characters for SVG background images. These characters need to be escaped to properly render the background images in IE. +## Add and Subtract functions + +We use the `add` and `subtract` functions to wrap the CSS `calc` function. The primary purpose of these functions is to avoid errors when a "unitless" `0` value is passed into a `calc` expression. Expressions like `calc(10px - 0)` will return an error in all browsers, despite being mathematically correct. + +Example where the calc is valid: + +{% highlight scss %} +$border-radius: .25rem; +$border-width: 1px; + +.element { + // Output calc(.25rem - 1px) is valid + border-radius: calc($border-radius - $border-width); +} + +.element { + // Output the same calc(.25rem - 1px) as above + border-radius: subtract($border-radius, $border-width); +} +{% endhighlight %} + +Example where the calc is invalid: + +{% highlight scss %} +$border-radius: .25rem; +$border-width: 0; + +.element { + // Output calc(.25rem - 0) is invalid + border-radius: calc($border-radius - $border-width); +} + +.element { + // Output .25rem + border-radius: subtract($border-radius, $border-width); +} +{% endhighlight %} + ## Sass options Customize Bootstrap 4 with our built-in custom variables file and easily toggle global CSS preferences with new `$enable-*` Sass variables. Override a variable's value and recompile with `npm run test` as needed.