Skip to content

Commit

Permalink
Re-add new calculation function documentation (sass#831)
Browse files Browse the repository at this point in the history
  • Loading branch information
Israel-4Ever authored Sep 14, 2023
1 parent c0c5599 commit 0eab25a
Show file tree
Hide file tree
Showing 2 changed files with 243 additions and 53 deletions.
28 changes: 14 additions & 14 deletions source/documentation/at-rules/function.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,30 +18,30 @@ Functions are called using the normal CSS function syntax.
[`@return` at-rule]: #return

{% codeExample 'functions' %}
@function pow($base, $exponent) {
$result: 1;
@for $_ from 1 through $exponent {
$result: $result * $base;
@function fibonacci($n) {
$sequence: 0 1;
@for $_ from 1 through $n {
$new: nth($sequence, length($sequence)) + nth($sequence, length($sequence) - 1);
$sequence: append($sequence, $new);
}
@return $result;
@return nth($sequence, length($sequence));
}

.sidebar {
float: left;
margin-left: pow(4, 3) * 1px;
margin-left: fibonacci(4) * 1px;
}
===
@function pow($base, $exponent)
$result: 1
@for $_ from 1 through $exponent
$result: $result * $base

@return $result

@function fibonacci($n)
$sequence: 0 1
@for $_ from 1 through $n
$new: nth($sequence, length($sequence)) + nth($sequence, length($sequence) - 1)
$sequence: append($sequence, $new)
@return nth($sequence, length($sequence))

.sidebar
float: left
margin-left: pow(4, 3) * 1px
margin-left: fibonacci(4) * 1px
{% endcodeExample %}

{% funFact %}
Expand Down
268 changes: 229 additions & 39 deletions source/documentation/values/calculations.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,17 @@ introduction: >
[plain CSS function]: /documentation/at-rules/function/#plain-css-functions
{% endcompatibility %}

{% compatibility 'dart: "1.67.0"', 'libsass: false', 'ruby: false', 'feature: "Adjacent values"' %}
Versions of Dart Sass between 1.40.0 and 1.67.0 don't allow multiple values in
calculations that aren't separated by an operator, even in cases like `calc(1
var(--plus-two))` which is valid CSS (since `--plus-two` can be defined to be `+
2`).

As of Dart Sass 1.67.0, multiple values in a calculation can be separated by
spaces as long as every other value evaluates to an unquoted string (such as a
`var()` expression or the unquoted string `"+ 2"`).
{% endcompatibility %}

{% codeExample 'calculations', false %}
@debug calc(400px + 10%); // calc(400px + 10%)
@debug calc(400px / 2); // 200px
Expand All @@ -43,10 +54,10 @@ division operator within a calculation!
the special calculation syntax!
{% endfunFact %}

You can also use [interpolation] in a calculation. However, if you do, nothing
in the parentheses that surround that interpolation will be simplified or
type-checked, so it's easy to end up with extra verbose or even invalid CSS.
Rather than writing `calc(10px + #{$var})`, just write `calc(10px + $var)`!
You can also use [interpolation] in a calculation. However, if you do, no
operations that involve that interpolation will be simplified or type-checked,
so it's easy to end up with extra verbose or even invalid CSS. Rather than
writing `calc(10px + #{$var})`, just write `calc(10px + $var)`!

[interpolation]: /documentation/interpolation

Expand Down Expand Up @@ -156,59 +167,133 @@ CSS to unitless numbers:
@debug calc(-infinity) < math.$min-number // true
{% endcodeExample %}

## `min()` and `max()`
## Calculation Functions

{% compatibility 'dart: ">=1.11.0 <1.42.0"', 'libsass: false', 'ruby: false', 'feature: "Special function syntax"' %}
LibSass, Ruby Sass, and versions of Dart Sass prior to 1.11.0 *always* parse
`min()` and `max()` as Sass functions. To create a plain CSS `min()` or
`max()` call for those implementations, you can write something like
`unquote("min(#{$padding}, env(safe-area-inset-left))")` instead.
{% compatibility 'dart: "1.65.0"', 'libsass: false', 'ruby: false', 'feature: "Additional functions"' %}
Versions of Dart Sass 1.65.0 and later _except_ 1.66.x handle the execution of
these calculation functions: `round()`, `mod()`, `rem()`, `sin()`, `cos()`,
`tan()`, `asin()`, `acos()`, `atan()`, `atan2()`, `pow()`, `sqrt()`,
`hypot()`, `log()`, `exp()`, `abs()`, and `sign()`.

Versions of Dart Sass between 1.11.0 and 1.40.0, and between 1.40.1 and 1.42.0
parse `min()` and `max()` functions as [special functions] if they're valid
plain CSS, but parse them as Sass functions if they contain Sass features
other than interpolation, like variables or function calls.
In Dart Sass 1.65.x, any function call whose name matched a calculation
function was _always_ parsed as a calculation function. This broke some
existing user-defined functions, so support for the new calculation functions
was removed in 1.66.0 until it could be added back _without_ breaking existing
behavior in 1.67.0.
{% endcompatibility %}

Dart Sass 1.41.0 parses `min()` and `max()` functions as calculations, but
doesn't allow unitless numbers to be combined with numbers with units. This
was backwards-incompatible with the global `min()` and `max()` functions, so
that behavior was reverted.
Sass parses the following functions as [calculations]:
* Comparison Functions: [`min()`], [`max()`], and [`clamp()`]
* Stepped Value Functions: [`round()`], [`mod()`], and [`rem()`].
* Trigonometric Functions: [`sin()`], [`cos()`], [`tan()`], [`asin()`], [`acos()`],
[`atan()`], and [`atan2()`].
* Exponential Functions: [`pow()`], [`sqrt()`], [`hypot()`], [`log()`], and [`exp()`].
* Sign-Related Functions: [`abs()`] and [`sign()`].

[calculations]: https://www.w3.org/TR/css-values-4/#math
[`min()`]: https://developer.mozilla.org/en-US/docs/Web/CSS/min
[`max()`]: https://developer.mozilla.org/en-US/docs/Web/CSS/max
[`clamp()`]: https://developer.mozilla.org/en-US/docs/Web/CSS/clamp
[`round()`]: https://developer.mozilla.org/en-US/docs/Web/CSS/round
[`abs()`]: https://developer.mozilla.org/en-US/docs/Web/CSS/abs
[`sin()`]: https://developer.mozilla.org/en-US/docs/Web/CSS/sin
[`cos()`]: https://developer.mozilla.org/en-US/docs/Web/CSS/cos
[`tan()`]: https://developer.mozilla.org/en-US/docs/Web/CSS/tan
[`asin()`]: https://developer.mozilla.org/en-US/docs/Web/CSS/asin
[`acos()`]: https://developer.mozilla.org/en-US/docs/Web/CSS/acos
[`atan()`]: https://developer.mozilla.org/en-US/docs/Web/CSS/atan
[`atan2()`]: https://developer.mozilla.org/en-US/docs/Web/CSS/atan2
[`pow()`]: https://developer.mozilla.org/en-US/docs/Web/CSS/pow
[`sqrt()`]: https://developer.mozilla.org/en-US/docs/Web/CSS/sqrt
[`hypot()`]: https://developer.mozilla.org/en-US/docs/Web/CSS/hypot
[`log()`]: https://developer.mozilla.org/en-US/docs/Web/CSS/log
[`exp()`]: https://developer.mozilla.org/en-US/docs/Web/CSS/exp
[`mod()`]: https://developer.mozilla.org/en-US/docs/Web/CSS/mod
[`rem()`]: https://developer.mozilla.org/en-US/docs/Web/CSS/rem
[`sign()`]: https://developer.mozilla.org/en-US/docs/Web/CSS/sign

[special functions]: /documentation/syntax/special-functions
{% endcompatibility %}
{% funFact %}
If you've defined a [Sass function] with the same name as a calculation
function, Sass will always call your function instead of creating a
calculation value.

[Sass function]: /documentation/at-rules/function
{% endfunFact %}

CSS added support for [`min()` and `max()` functions] in Values and Units Level
4, from where they were quickly adopted by Safari [to support the iPhoneX]. But
Sass supported its own [`min()`] and [`max()`] functions long before this, and
it needed to be backwards-compatible with all those existing stylesheets. This
led to the need for extra-special syntactic cleverness.
### Legacy Global Functions

[`min()` and `max()` functions]: https://drafts.csswg.org/css-values-4/#calc-notation
[to support the iPhoneX]: https://webkit.org/blog/7929/designing-websites-for-iphone-x/
[`min()`]: /documentation/modules/math#min
[`max()`]: /documentation/modules/math#max
CSS added support for [mathematical expressions] in Values and Units Level
4. However, Sass supported its own [`round()`], [`abs()`], [`min()`] and
[`max()`] long before this, and it needed to be backwards-compatible with all
those existing stylesheets. This led to the need for extra-special syntactic
cleverness.

If a `min()` or `max()` function call is a valid calculation expression, it will
be parsed as a calculation. But as soon as any part of the call contains a
SassScript feature that isn't supported in a calculation, like the [modulo
operator], it's parsed as a call to Sass's core `min()` or `max()` function
instead.
[mathematical expressions]: https://www.w3.org/TR/css-values-4/#math
[`round()`]: ../modules/math#round
[`abs()`]: ../modules/math#abs
[`min()`]: ../modules/math#min
[`max()`]: ../modules/math#max

If a call to `round()`, `abs()`, `min()`, or `max()` is a valid calculation
expression, it will be parsed as a calculation. But as soon as any part of the
call contains a SassScript feature that isn't supported in a calculation, like
the [modulo operator], it's parsed as a call to the appropriate Sass math
function instead.

Since calculations are simplified to numbers when possible anyway, the only
substantive difference is that the Sass functions only support units that can be
combined at build time, so `min(12px % 10, 10%)` will throw an error.

[modulo operator]: /documentation/operators/numeric
[modulo operator]: /documentation/operators/numeric/

{% headsUp %}
Other calculations don't allow unitless numbers to be added to, subtracted
from, or compared to numbers with units. `min()` and `max()` are different,
though: for backwards-compatibility with the global Sass `min()` and `max()`
functions which allow unit/unitless mixing for historical reasons, these units
can be mixed as long as they're contained directly within a `min()` or `max()`
from, or compared to numbers with units. [`min()`], [`max()`], [`abs()`] and
[single-argument `round()`] are different, though: for backwards-compatibility
with the global Sass legacy functions which allow unit/unitless mixing for
historical reasons, these units can be mixed as long as they're contained
directly within a `min()`, `max()`, `abs()`, or single-argument `round()`
calculation.

For instance, `min(5 + 10px, 20px)` will result in `15px`. However
`sqrt(5 + 10px)` will throw an error, as `sqrt(5 + 10px)` was never a global
Sass function, and these are incompatible units.

[single-argument `round()`]: https://developer.mozilla.org/en-US/docs/Web/CSS/round
[`abs()`]: https://developer.mozilla.org/en-US/docs/Web/CSS/abs
[`min()`]: https://developer.mozilla.org/en-US/docs/Web/CSS/min
[`max()`]: https://developer.mozilla.org/en-US/docs/Web/CSS/max
{% endheadsUp %}

#### `min()` and `max()`

{% compatibility 'dart: ">=1.11.0 <1.42.0"', 'libsass: false', 'ruby: false', 'feature: "min and max syntax"' %}
LibSass, Ruby Sass, and versions of Dart Sass prior to 1.11.0 *always* parse
`min()` and `max()` as Sass functions. To create a plain CSS `min()` or
`max()` call for those implementations, you can write something like
`unquote("min(#{$padding}, env(safe-area-inset-left))")` instead.

CSS added support for [`min()` and `max()` functions] in Values and Units
Level 4, from where they were quickly adopted by Safari [to support the iPhoneX].
Since we already supported `min()` and `max()` as legacy Sass functions, we
had to implement logic for backwards-compatibility and for support as CSS
functions.

Versions of Dart Sass between 1.11.0 and 1.40.0, and between 1.40.1
and 1.42.0 parse `min()` and `max()` functions as [special functions] if
they're valid plain CSS, but parse them as Sass functions if they contain Sass
features other than interpolation, like variables or function calls.

Dart Sass 1.41.0 parses `min()` and `max()` functions as calculations, but
doesn't allow unitless numbers to be combined with numbers with units. This
was backwards-incompatible with the global `min()` and `max()` functions, so
that behavior was reverted.

[`min()` and `max()` functions]: https://www.w3.org/TR/css-values-4/#math
[to support the iPhoneX]: https://webkit.org/blog/7929/designing-websites-for-iphone-x/
[special functions]: /documentation/syntax/special-functions/
{% endcompatibility %}

{% codeExample 'min-max' %}
$padding: 12px;

Expand All @@ -225,6 +310,7 @@ combined at build time, so `min(12px % 10, 10%)` will throw an error.
padding-left: max($padding % 10, 20px);
padding-right: max($padding % 10, 20px);
}

===
$padding: 12px

Expand All @@ -240,4 +326,108 @@ combined at build time, so `min(12px % 10, 10%)` will throw an error.
// SassScript function calls.
padding-left: max($padding % 10, 20px)
padding-right: max($padding % 10, 20px)

===
.post {
padding-left: max(12px, env(safe-area-inset-left));
padding-right: max(12px, env(safe-area-inset-right));
}

.sidebar {
padding-left: 20px;
padding-right: 20px;
}
{% endcodeExample %}

#### `round()`

{% compatibility 'dart: "1.65.0"', 'libsass: false', 'ruby: false', 'feature: "min and max syntax"' %}
LibSass, Ruby Sass, and versions of Dart Sass prior to 1.65.0, as well as Dart
Sass 1.66.x, *always* parse `round()` as a Sass function. To use a plain CSS
function for those implementations, you can write something like
`round(#{$strategy, $number, $step})` instead.
{% endcompatibility %}

The [`round(<strategy>, number, step)`] function accepts an optional rounding
strategy, a value to be rounded and a rounding interval `step`. `strategy`
should be `nearest`, `up`, `down`, or `to-zero`.

[`round(<strategy>, number, step)`]: https://developer.mozilla.org/en-US/docs/Web/CSS/round#parameter

{% codeExample 'round' %}
$number: 12.5px;
$step: 15px;

.post-image {
// Since these round() calls are valid calculation expressions, they're
// parsed as calculations.
padding-left: round(nearest, $number, $step);
padding-right: round($number + 10px);
padding-bottom: round($number + 10px, $step + 10%);
}

===
$number: 12.5px
$step: 15px

.post-image
// Since these round() calls are valid calculation expressions, they're
// parsed as calculations.
padding-left: round(nearest, $number, $step)
padding-right: round($number + 10px)
padding-bottom: round($number + 10px, $step + 10%)

===
.post-image {
padding-left: 15px;
padding-right: 23px;
padding-bottom: round(22.5px, 15px + 10%);
}
{% endcodeExample %}

#### `abs()`

{% compatibility 'dart: "1.67.0"', 'libsass: false', 'ruby: false', 'feature: "min and max syntax"' %}
LibSass, Ruby Sass, and versions of Dart Sass prior to 1.67.0 *always* parse
`abs()` as a Sass function. To create a plain CSS calculation for those
implementations, you can write something like `abs(#{$number})` instead.
{% endcompatibility %}

{% headsUp %}
The global `abs()` function compatibiliy with [% unit parameters is
deprecated]. In the future, this will emit a CSS abs() function to be resolved
by the browser.

[% unit parameters is deprecated]: /documentation/breaking-changes/abs-percent/
{% endheadsUp %}

The [`abs(value)`] takes in a single expressiona as a parameter and returns the
absolute value of `$value`. If `$value` is negative, this returns `-$value`, and if
`$value` is positive, it returns `$value` as-is.

[`abs(value)`]: https://developer.mozilla.org/en-US/docs/Web/CSS/abs

{% codeExample 'abs' %}
.post-image {
// Since these abs() calls are valid calculation expressions, they're
// parsed as calculations.
padding-left: abs(10px);
padding-right: math.abs(-7.5%);
padding-top: abs(1 + 1px);
}

===
.post-image
// Since these abs() calls are valid calculation expressions, they're
// parsed as calculations.
padding-left: abs(-10px)
padding-right: math.abs(-7.5%)
padding-top: abs(1 + 1px)

===
.post-image {
padding-left: 10px;
padding-right: 7.5%;
padding-top: 2px;
}
{% endcodeExample %}

0 comments on commit 0eab25a

Please sign in to comment.