Skip to content

Commit 2f0d0da

Browse files
authored
Merge pull request #2341 from sass/feature.color-4
Merge feature.color-4 into main
2 parents 4fa5a02 + de181d9 commit 2f0d0da

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

54 files changed

+6069
-1122
lines changed

CHANGELOG.md

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,199 @@
11
## 1.79.0
22

3+
* **Breaking change**: Passing a number with unit `%` to the `$alpha` parameter
4+
of `color.change()`, `color.adjust()`, `change-color()`, and `adjust-color()`
5+
is now interpreted as a percentage, instead of ignoring the unit. For example,
6+
`color.change(red, $alpha: 50%)` now returns `rgb(255 0 0 / 0.5)`.
7+
8+
* **Potentially breaking compatibility fix**: Sass no longer rounds RGB channels
9+
to the nearest integer. This means that, for example, `rgb(0 0 1) != rgb(0 0
10+
0.6)`. This matches the latest version of the CSS spec and browser behavior.
11+
12+
* **Potentially breaking compatibility fix**: Passing large positive or negative
13+
values to `color.adjust()` can now cause a color's channels to go outside that
14+
color's gamut. In most cases this will currently be clipped by the browser and
15+
end up showing the same color as before, but once browsers implement gamut
16+
mapping it may produce a different result.
17+
18+
* Add support for CSS Color Level 4 [color spaces]. Each color value now tracks
19+
its color space along with the values of each channel in that color space.
20+
There are two general principles to keep in mind when dealing with new color
21+
spaces:
22+
23+
1. With the exception of legacy color spaces (`rgb`, `hsl`, and `hwb`), colors
24+
will always be emitted in the color space they were defined in unless
25+
they're explicitly converted.
26+
27+
2. The `color.to-space()` function is the only way to convert a color to
28+
another color space. Some built-in functions may do operations in a
29+
different color space, but they'll always convert back to the original space
30+
afterwards.
31+
32+
* `rgb` colors can now have non-integer channels and channels outside the normal
33+
gamut of 0-255. These colors are always emitted using the `rgb()` syntax so
34+
that modern browsers that are being displayed on wide-gamut devices can
35+
display the most accurate color possible.
36+
37+
* Add support for all the new color syntax defined in Color Level 4, including:
38+
39+
* `oklab()`, `oklch()`, `lab()`, and `lch()` functions;
40+
* a top-level `hwb()` function that matches the space-separated CSS syntax;
41+
* and a `color()` function that supports the `srgb`, `srgb-linear`,
42+
`display-p3`, `a98-rgb`, `prophoto-rgb`, `rec2020`, `xyz`, `xyz-d50`, and
43+
`xyz-d65` color spaces.
44+
45+
* Add new functions for working with color spaces:
46+
47+
* `color.to-space($color, $space)` converts `$color` to the given `$space`. In
48+
most cases this conversion is lossless—the color may end up out-of-gamut for
49+
the destination color space, but browsers will generally display it as best
50+
they can regardless. However, the `hsl` and `hwb` spaces can't represent
51+
out-of-gamut colors and so will be clamped.
52+
53+
* `color.channel($color, $channel, $space: null)` returns the value of the
54+
given `$channel` in `$color`, after converting it to `$space` if necessary.
55+
It should be used instead of the old channel-specific functions such as
56+
`color.red()` and `color.hue()`.
57+
58+
* `color.same($color1, $color2)` returns whether two colors represent the same
59+
color even across color spaces. It differs from `$color1 == $color2` because
60+
`==` never consider colors in different (non-legacy) spaces as equal.
61+
62+
* `color.is-in-gamut($color, $space: null)` returns whether `$color` is
63+
in-gamut for its color space (or `$space` if it's passed).
64+
65+
* `color.to-gamut($color, $space: null)` returns `$color` constrained to its
66+
space's gamut (or to `$space`'s gamut, if passed). This is generally not
67+
recommended since even older browsers will display out-of-gamut colors as
68+
best they can, but it may be necessary in some cases.
69+
70+
* `color.space($color)`: Returns the name of `$color`'s color space.
71+
72+
* `color.is-legacy($color)`: Returns whether `$color` is in a legacy color
73+
space (`rgb`, `hsl`, or `hwb`).
74+
75+
* `color.is-powerless($color, $channel, $space: null)`: Returns whether the
76+
given `$channel` of `$color` is powerless in `$space` (or its own color
77+
space). A channel is "powerless" if its value doesn't affect the way the
78+
color is displayed, such as hue for a color with 0 chroma.
79+
80+
* `color.is-missing($color, $channel)`: Returns whether `$channel`'s value is
81+
missing in `$color`. Missing channels can be explicitly specified using the
82+
special value `none` and can appear automatically when `color.to-space()`
83+
returns a color with a powerless channel. Missing channels are usually
84+
treated as 0, except when interpolating between two colors and in
85+
`color.mix()` where they're treated as the same value as the other color.
86+
87+
* Update existing functions to support color spaces:
88+
89+
* `hsl()` and `color.hwb()` no longer forbid out-of-bounds values. Instead,
90+
they follow the CSS spec by clamping them to within the allowed range.
91+
92+
* `color.change()`, `color.adjust()`, and `color.scale()` now support all
93+
channels of all color spaces. However, if you want to modify a channel
94+
that's not in `$color`'s own color space, you have to explicitly specify the
95+
space with the `$space` parameter. (For backwards-compatibility, this
96+
doesn't apply to legacy channels of legacy colors—for example, you can still
97+
adjust an `rgb` color's saturation without passing `$space: hsl`).
98+
99+
* `color.mix()` and `color.invert()` now support the standard CSS algorithm
100+
for interpolating between two colors (the same one that's used for gradients
101+
and animations). To use this, pass the color space to use for interpolation
102+
to the `$method` parameter. For polar color spaces like `hsl` and `oklch`,
103+
this parameter also allows you to specify how hue interpolation is handled.
104+
105+
* `color.complement()` now supports a `$space` parameter that indicates which
106+
color space should be used to take the complement.
107+
108+
* `color.grayscale()` now operates in the `oklch` space for non-legacy colors.
109+
110+
* `color.ie-hex-str()` now automatically converts its color to the `rgb` space
111+
and gamut-maps it so that it can continue to take colors from any color
112+
space.
113+
114+
[color spaces]: https://developer.mozilla.org/en-US/docs/Web/CSS/color_value
115+
116+
* The following functions are now deprecated, and uses should be replaced with
117+
the new color-space-aware functions defined above:
118+
119+
* The `color.red()`, `color.green()`, `color.blue()`, `color.hue()`,
120+
`color.saturation()`, `color.lightness()`, `color.whiteness()`, and
121+
`color.blackness()` functions, as well as their global counterparts, should
122+
be replaced with calls to `color.channel()`.
123+
124+
* The global `adjust-hue()`, `saturate()`, `desaturate()`, `lighten()`,
125+
`darken()`, `transaprentize()`, `fade-out()`, `opacify()`, and `fade-in()`
126+
functions should be replaced by `color.adjust()` or `color.scale()`.
127+
3128
* Add a `global-builtin` future deprecation, which can be opted-into with the
4129
`--future-deprecation` flag or the `futureDeprecations` option in the JS or
5130
Dart API. This emits warnings when any global built-in functions that are
6131
now available in `sass:` modules are called. It will become active by default
7132
in an upcoming release alongside the `@import` deprecation.
8133

134+
### Dart API
135+
136+
* Added a `ColorSpace` class which represents the various color spaces defined
137+
in the CSS spec.
138+
139+
* Added `SassColor.space` which returns a color's color space.
140+
141+
* Added `SassColor.channels` and `.channelsOrNull` which returns a list
142+
of channel values, with missing channels converted to 0 or exposed as null,
143+
respectively.
144+
145+
* Added `SassColor.isLegacy`, `.isInGamut`, `.channel()`, `.isChannelMissing()`,
146+
`.isChannelPowerless()`, `.toSpace()`, `.toGamut()`, `.changeChannels()`, and
147+
`.interpolate()` which do the same thing as the Sass functions of the
148+
corresponding names.
149+
150+
* `SassColor.rgb()` now allows out-of-bounds and non-integer arguments.
151+
152+
* `SassColor.hsl()` and `.hwb()` now allow out-of-bounds arguments.
153+
154+
* Added `SassColor.hwb()`, `.srgb()`, `.srgbLinear()`, `.displayP3()`,
155+
`.a98Rgb()`, `.prophotoRgb()`, `.rec2020()`, `.xyzD50()`, `.xyzD65()`,
156+
`.lab()`, `.lch()`, `.oklab()`, `.oklch()`, and `.forSpace()` constructors.
157+
158+
* Deprecated `SassColor.red`, `.green`, `.blue`, `.hue`, `.saturation`,
159+
`.lightness`, `.whiteness`, and `.blackness` in favor of
160+
`SassColor.channel()`.
161+
162+
* Deprecated `SassColor.changeRgb()`, `.changeHsl()`, and `.changeHwb()` in
163+
favor of `SassColor.changeChannels()`.
164+
165+
* Added `SassNumber.convertValueToUnit()` as a shorthand for
166+
`SassNumber.convertValue()` with a single numerator.
167+
168+
* Added `InterpolationMethod` and `HueInterpolationMethod` which collectively
169+
represent the method to use to interpolate two colors.
170+
171+
### JS API
172+
173+
* Modify `SassColor` to accept a new `space` option, with support for all the
174+
new color spaces defined in Color Level 4.
175+
176+
* Add `SassColor.space` which returns a color's color space.
177+
178+
* Add `SassColor.channels` and `.channelsOrNull` which returns a list of channel
179+
values, with missing channels converted to 0 or exposed as null, respectively.
180+
181+
* Add `SassColor.isLegacy`, `.isInGamut()`, `.channel()`, `.isChannelMissing()`,
182+
`.isChannelPowerless()`, `.toSpace()`, `.toGamut()`, `.change()`, and
183+
`.interpolate()` which do the same thing as the Sass functions of the
184+
corresponding names.
185+
186+
* Deprecate `SassColor.red`, `.green`, `.blue`, `.hue`, `.saturation`,
187+
`.lightness`, `.whiteness`, and `.blackness` in favor of
188+
`SassColor.channel()`.
189+
190+
### Embedded Sass
191+
192+
* Add `Color` SassScript value, with support for all the new color spaces
193+
defined in Color Level 4.
194+
195+
* Remove `RgbColor`, `HslColor` and `HwbColor` SassScript values.
196+
9197
## 1.78.0
10198

11199
* The `meta.feature-exists` function is now deprecated. This deprecation is

lib/sass.dart

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,12 @@ export 'src/importer.dart';
3131
export 'src/logger.dart' show Logger;
3232
export 'src/syntax.dart';
3333
export 'src/value.dart'
34-
hide ColorFormat, SassApiColor, SassApiValue, SpanColorFormat;
34+
hide
35+
ColorChannel,
36+
ColorFormat,
37+
LinearChannel,
38+
SassApiColorSpace,
39+
SpanColorFormat;
3540
export 'src/visitor/serialize.dart' show OutputStyle;
3641
export 'src/evaluation_context.dart' show warn;
3742

lib/src/deprecation.dart

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ enum Deprecation {
1515
// DO NOT EDIT. This section was generated from the language repo.
1616
// See tool/grind/generate_deprecations.dart for details.
1717
//
18-
// Checksum: bf841a728263bf7efc2a85a091330a1f8074e067
18+
// Checksum: 5470e7252641d3eaa7093b072b52e423c3b77375
1919

2020
/// Deprecation for passing a string directly to meta.call().
2121
callString('call-string',
@@ -99,6 +99,16 @@ enum Deprecation {
9999
featureExists('feature-exists',
100100
deprecatedIn: '1.78.0', description: 'meta.feature-exists'),
101101

102+
/// Deprecation for certain uses of built-in sass:color functions.
103+
color4Api('color-4-api',
104+
deprecatedIn: '1.79.0',
105+
description: 'Certain uses of built-in sass:color functions.'),
106+
107+
/// Deprecation for using global color functions instead of sass:color.
108+
colorFunctions('color-functions',
109+
deprecatedIn: '1.79.0',
110+
description: 'Using global color functions instead of sass:color.'),
111+
102112
/// Deprecation for @import rules.
103113
import.future('import', description: '@import rules.'),
104114

lib/src/embedded/protofier.dart

Lines changed: 86 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -53,17 +53,12 @@ final class Protofier {
5353
..quoted = value.hasQuotes;
5454
case SassNumber():
5555
result.number = _protofyNumber(value);
56-
case SassColor(hasCalculatedHsl: true):
57-
result.hslColor = Value_HslColor()
58-
..hue = value.hue * 1.0
59-
..saturation = value.saturation * 1.0
60-
..lightness = value.lightness * 1.0
61-
..alpha = value.alpha * 1.0;
6256
case SassColor():
63-
result.rgbColor = Value_RgbColor()
64-
..red = value.red
65-
..green = value.green
66-
..blue = value.blue
57+
result.color = Value_Color()
58+
..space = value.space.name
59+
..channel1 = value.channel0
60+
..channel2 = value.channel1
61+
..channel3 = value.channel2
6762
..alpha = value.alpha * 1.0;
6863
case SassArgumentList():
6964
_argumentLists.add(value);
@@ -181,17 +176,85 @@ final class Protofier {
181176
case Value_Value.number:
182177
return _deprotofyNumber(value.number);
183178

184-
case Value_Value.rgbColor:
185-
return SassColor.rgb(value.rgbColor.red, value.rgbColor.green,
186-
value.rgbColor.blue, value.rgbColor.alpha);
187-
188-
case Value_Value.hslColor:
189-
return SassColor.hsl(value.hslColor.hue, value.hslColor.saturation,
190-
value.hslColor.lightness, value.hslColor.alpha);
191-
192-
case Value_Value.hwbColor:
193-
return SassColor.hwb(value.hwbColor.hue, value.hwbColor.whiteness,
194-
value.hwbColor.blackness, value.hwbColor.alpha);
179+
case Value_Value.color:
180+
var space = ColorSpace.fromName(value.color.space);
181+
switch (space) {
182+
case ColorSpace.rgb:
183+
return SassColor.rgb(value.color.channel1, value.color.channel2,
184+
value.color.channel3, value.color.alpha);
185+
186+
case ColorSpace.hsl:
187+
return SassColor.hsl(value.color.channel1, value.color.channel2,
188+
value.color.channel3, value.color.alpha);
189+
190+
case ColorSpace.hwb:
191+
return SassColor.hwb(value.color.channel1, value.color.channel2,
192+
value.color.channel3, value.color.alpha);
193+
194+
case ColorSpace.lab:
195+
return SassColor.lab(value.color.channel1, value.color.channel2,
196+
value.color.channel3, value.color.alpha);
197+
case ColorSpace.oklab:
198+
return SassColor.oklab(value.color.channel1, value.color.channel2,
199+
value.color.channel3, value.color.alpha);
200+
201+
case ColorSpace.lch:
202+
return SassColor.lch(value.color.channel1, value.color.channel2,
203+
value.color.channel3, value.color.alpha);
204+
case ColorSpace.oklch:
205+
return SassColor.oklch(value.color.channel1, value.color.channel2,
206+
value.color.channel3, value.color.alpha);
207+
208+
case ColorSpace.srgb:
209+
return SassColor.srgb(value.color.channel1, value.color.channel2,
210+
value.color.channel3, value.color.alpha);
211+
case ColorSpace.srgbLinear:
212+
return SassColor.srgbLinear(
213+
value.color.channel1,
214+
value.color.channel2,
215+
value.color.channel3,
216+
value.color.alpha);
217+
case ColorSpace.displayP3:
218+
return SassColor.displayP3(
219+
value.color.channel1,
220+
value.color.channel2,
221+
value.color.channel3,
222+
value.color.alpha);
223+
case ColorSpace.a98Rgb:
224+
return SassColor.a98Rgb(
225+
value.color.channel1,
226+
value.color.channel2,
227+
value.color.channel3,
228+
value.color.alpha);
229+
case ColorSpace.prophotoRgb:
230+
return SassColor.prophotoRgb(
231+
value.color.channel1,
232+
value.color.channel2,
233+
value.color.channel3,
234+
value.color.alpha);
235+
case ColorSpace.rec2020:
236+
return SassColor.rec2020(
237+
value.color.channel1,
238+
value.color.channel2,
239+
value.color.channel3,
240+
value.color.alpha);
241+
242+
case ColorSpace.xyzD50:
243+
return SassColor.xyzD50(
244+
value.color.channel1,
245+
value.color.channel2,
246+
value.color.channel3,
247+
value.color.alpha);
248+
case ColorSpace.xyzD65:
249+
return SassColor.xyzD65(
250+
value.color.channel1,
251+
value.color.channel2,
252+
value.color.channel3,
253+
value.color.alpha);
254+
255+
default:
256+
throw "Unreachable";
257+
}
195258

196259
case Value_Value.argumentList:
197260
if (value.argumentList.id != 0) {
@@ -276,10 +339,8 @@ final class Protofier {
276339
throw paramsError(error.toString());
277340
}
278341

279-
if (value.whichValue() == Value_Value.rgbColor) {
280-
name = 'RgbColor.$name';
281-
} else if (value.whichValue() == Value_Value.hslColor) {
282-
name = 'HslColor.$name';
342+
if (value.whichValue() == Value_Value.color) {
343+
name = 'Color.$name';
283344
}
284345

285346
throw paramsError(

0 commit comments

Comments
 (0)