accepted/angle-units.md
(Issue)
This proposal adds support for units other than deg to HSL and HWB functions.
This section is non-normative.
CSS Values and Units 3 defines a number of different units that can be used to
represent angles, including the hue angle that's passed to the hsl() and hwb() functions. However, Sass has historically ignored
units for arguments passed to hsl() and related functions, choosing instead to
always interpret the hue as degrees. For example, hsl(1rad 50% 50%) is
incorrectly interpreted as hsl(1deg 50% 50%) = #bf4240 rather than
hsl(57.3deg 50% 50%) = #bfba40.
Relatedly, hsl() and related functions don't enforce that the saturation and
lightness values are percentages. This is a less pressing issue, because it
doesn't mean that Sass is misinterpreting valid CSS, but it does mean that
invalid CSS like hsl(0 50 50) or even hsl(0 50px 50px) is being incorrectly
accepted.
This section is non-normative.
This proposal makes hsl(), hsla(), hwb(), adjust-hue(),
color.adjust(), and color.change() convert all hue angles into degrees and
reject all non-angle non-empty units for hue and all non-% units for
saturation and lightness. Because these are breaking changes, they're split
across three distinct phases:
Initially, passing any non-deg non-empty units to hue or non-% units to
saturation or lightness will produce a deprecation warning. Passing unitless
numbers for hue will still be allowed because the CSS spec allows it, but not
for saturation or lightness.
After at least three months (per the Dart Sass compatibility policy), all functions will start converting angle units to degrees. Because this brings Sass into compatibility with the CSS spec, Dart Sass will make it outside of a major version change despite it being a breaking change. All other deprecations will remain in place without breaking changes.
As part of the release of the next major version of Dart Sass, these
functions will start throwing errors for unknown units rather than
interpreting them as deg or %. Passing unitless numbers for hue will
still be supported.
It would be possible to further minimize the scope of the breaking change in
phase 2 by only changing how hsl() and hsla() interpret hue arguments with
other angle arguments, and leaving adjust-hue(), color.adjust(), and
color.change() alone. However, allowing the same hue argument to be accepted
in multiple functions but interpreted different ways is likely to cause
substantial confusion. In addition, it's unlikely that users are specifically
passing deg, rad, or turn units today and relying on their broken
behavior.
In other words, the cost of not slightly expanding this breaking change is likely to be high, and the benefit in terms of causing friction for existing users is likely to be very low.
Since we're requiring % for saturation and lightness in most functions, it
would make some sense to add the same requirement to the saturate(),
desaturate(), lighten(), and darken() functions as well. This proposal
chooses instead to leave them as-is because they're intended to be removed in
the next breaking release anyway, and until that release saturation and
lightness will only have deprecation warnings. This would put them in a
situation where they would only ever emit deprecation warnings without ever
actually rejecting other units, which is unlikely to be worth the effort.
Note that although the behavior of the
color.change()function is changing, no explicit changes are needed because per the spec it passes its$hue,$saturation, and$lightnessparameters directly tohsl().
hsl() and hsla()In the four-argument overload of the global hsl() function, replace
Let hue be ($hue % 360) / 60 without units.
Let saturation and lightness be the result of clamping $saturation and
$lightness, respectively, between 0 and 100 and dividing by 100.
with
Let hue be the result of converting $hue to deg allowing unitless.
Set hue to (hue % 360deg) / 60deg.
If $saturation and $lightness don't have unit %, throw an error.
Let saturation and lightness be the result of clamping $saturation and
$lightness, respectively, between 0% and 100% and dividing by 100%.
Because hsla() is identical to hsl(), it's updated identically.
color.hwb()In the four-argument overload of color.hwb(), replace
If $hue has any units other than deg, throw an error.
If either of $whiteness or $blackness don't have unit % or aren't
between 0% and 100% (inclusive), throw an error.
Let hue be ($hue % 360) / 60 without units.
with
Let hue be the result of converting $hue to deg allowing unitless.
Set hue to (hue % 360deg) / 60deg.
If either of $whiteness or $blackness don't have unit % or aren't
between 0% and 100% (inclusive), throw an error.
adjust-hue()The global adjust-hue() function will now behave as follows:
adjust-hue($color, $degrees)
If $color isn't a color or $degrees isn't a number, throw an error.
Let degrees be the result of converting $degrees to deg allowing
unitless.
Let saturation and lightness be the result of calling
color.saturation($color) and color.lightness($color), respectively.
Return the result of calling hsl() with degree, saturation,
lightness, and $color's alpha channel.
color.adjust()In the definition of color.adjust(), after
$hue isn't a number or null, throw an error.add
If $hue is a number and it has units that aren't compatible with deg,
throw an error.
Unitless numbers are allowed.
The existing definition of
color.adjust()includes the line "sethuetohue + $hue" which should throw an error if$huehas units that aren't compatible withdegand otherwise should convert$huetodeg. However, no implementation currently follows that behavior, so this spec change effectively serves to make the already-specified behavior more explicit.
In addition, replace
If either $saturation or $lightness aren't either null or numbers
between -100 and 100 (inclusive), throw an error.
Let hue, saturation, and lightness be the result of calling
hue($color), saturation($color), and lightness($color) respectively.
If $hue isn't null, set hue to hue + $hue.
If $saturation isn't null, set saturation to saturation + $saturation
clamped between 0 and 100.
If $lightness isn't null, set lightness to lightness + $lightness
clamped between 0 and 100.
with
If either $saturation or $lightness aren't either null or numbers with
unit % between -100% and 100% (inclusive), throw an error.
Let hue, saturation, and lightness be the result of calling
hue($color), saturation($color), and lightness($color) respectively.
If $hue isn't null, set hue to hue + $hue.
If $saturation isn't null, set saturation to saturation + $saturation
clamped between 0% and 100%.
If $lightness isn't null, set lightness to lightness + $lightness
clamped between 0% and 100%.
The deprecation process will be divided into three phases:
This phase adds no breaking changes. Its purpose is to notify users of the upcoming changes to behavior and give them a chance to move towards passing future-proof units.
Phase 1 implements none of the function changes described above.
Instead, if the $hue parameter to hsl(), hsla(), adjust-hue(),
color.adjust(), or color.change() is passed a number with a unit other
than deg, emit a deprecation warning. In addition, if either the $saturation
or $lightness parameters to hsl(), hsla(), color.adjust(), or
color.change() are passed a number without the unit %, emit a deprecation
warning.
Unitless hues should not cause deprecation warnings, but unitless saturations and lightnesses should.
This phase only breaks the behavior of passing
deg-compatible units as hues, and otherwise leaves existing behavior intact.
Phase 2 implements a subset of the function changes described above. In particular:
The color.hwb() function is updated as described above.
If the $hue parameter to hsl(), hsla(), adjust-hue(),
color.adjust(), or color.change() is passed a number with unit rad,
grad, or turn, convert it to deg before running the original function.
As in phase 1, if the $hue parameter to hsl(), hsla(),
adjust-hue(), color.adjust(), or color.change() is passed a number
with a unit other than deg, rad, grad, or turn, emit a deprecation
warning.
As in phase 1, if either the $saturation or $lightness parameters to
hsl(), hsla(), color.adjust(), or color.change() are passed a number
without the unit %, emit a deprecation warning.
Phase 3 implements the full function changes described above.
It's recommended that implementations increment their major version numbers with the release of phase 3.