See #2904
11 KiB
Angle Units: Draft 1
(Issue)
This proposal adds support for units other than deg
to HSL and HWB functions.
Table of Contents
Background
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.
Summary
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.
Design Decisions
Scope of Phase 2
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.
Global Saturation and Lightness Functions
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.
Functions
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$lightness
parameters 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
andlightness
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
todeg
allowing unitless. -
Set
hue
to(hue % 360deg) / 60deg
. -
If
$saturation
and$lightness
don't have unit%
, throw an error. -
Let
saturation
andlightness
be the result of clamping$saturation
and$lightness
, respectively, between0%
and100%
and dividing by100%
.
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 thandeg
, throw an error. -
If either of
$whiteness
or$blackness
don't have unit%
or aren't between0%
and100%
(inclusive), throw an error. -
Let
hue
be($hue % 360) / 60
without units.
with
-
Let
hue
be the result of converting$hue
todeg
allowing unitless. -
Set
hue
to(hue % 360deg) / 60deg
. -
If either of
$whiteness
or$blackness
don't have unit%
or aren't between0%
and100%
(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
todeg
allowing unitless. -
Let
saturation
andlightness
be the result of callingcolor.saturation($color)
andcolor.lightness($color)
, respectively. -
Return the result of calling
hsl()
withdegree
,saturation
,lightness
, and$color
's alpha channel.
color.adjust()
In the definition of color.adjust()
, after
- If
$hue
isn't a number or null, throw an error.
add
-
If
$hue
is a number and it has units that aren't compatible withdeg
, throw an error.Unitless numbers are allowed.
The existing definition of
color.adjust()
includes the line "sethue
tohue + $hue
" which should throw an error if$hue
has units that aren't compatible withdeg
and otherwise should convert$hue
todeg
. 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
, andlightness
be the result of callinghue($color)
,saturation($color)
, andlightness($color)
respectively. -
If
$hue
isn't null, sethue
tohue + $hue
. -
If
$saturation
isn't null, setsaturation
tosaturation + $saturation
clamped between 0 and 100. -
If
$lightness
isn't null, setlightness
tolightness + $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
, andlightness
be the result of callinghue($color)
,saturation($color)
, andlightness($color)
respectively. -
If
$hue
isn't null, sethue
tohue + $hue
. -
If
$saturation
isn't null, setsaturation
tosaturation + $saturation
clamped between 0% and 100%. -
If
$lightness
isn't null, setlightness
tolightness + $lightness
clamped between 0% and 100%.
Deprecation Process
The deprecation process will be divided into three phases:
Phase 1
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.
Phase 2
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 tohsl()
,hsla()
,adjust-hue()
,color.adjust()
, orcolor.change()
is passed a number with unitrad
,grad
, orturn
, convert it todeg
before running the original function. -
As in phase 1, if the
$hue
parameter tohsl()
,hsla()
,adjust-hue()
,color.adjust()
, orcolor.change()
is passed a number with a unit other thandeg
,rad
,grad
, orturn
, emit a deprecation warning. -
As in phase 1, if either the
$saturation
or$lightness
parameters tohsl()
,hsla()
,color.adjust()
, orcolor.change()
are passed a number without the unit%
, emit a deprecation warning.
Phase 3
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.