Skip to content

Commit 808ce79

Browse files
committed
Support for light-dark() function and color-scheme property
1 parent 4f5a44c commit 808ce79

File tree

10 files changed

+479
-14
lines changed

10 files changed

+479
-14
lines changed

node/ast.d.ts

+18-1
Original file line numberDiff line numberDiff line change
@@ -637,7 +637,7 @@ export type Token =
637637
*
638638
* Each color space is represented as a struct that implements the `From` and `Into` traits for all other color spaces, so it is possible to convert between color spaces easily. In addition, colors support [interpolation](#method.interpolate) as in the `color-mix()` function.
639639
*/
640-
export type CssColor = CurrentColor | RGBColor | LABColor | PredefinedColor | FloatColor;
640+
export type CssColor = CurrentColor | RGBColor | LABColor | PredefinedColor | FloatColor | LightDark;
641641
export type CurrentColor = {
642642
type: "currentcolor";
643643
};
@@ -957,6 +957,11 @@ export type FloatColor =
957957
*/
958958
w: number;
959959
};
960+
export type LightDark = {
961+
dark: CssColor;
962+
light: CssColor;
963+
type: "light-dark";
964+
};
960965
/**
961966
* A color value with an unresolved alpha value (e.g. a variable). These can be converted from the modern slash syntax to older comma syntax. This can only be done when the only unresolved component is the alpha since variables can resolve to multiple tokens.
962967
*/
@@ -2240,6 +2245,9 @@ export type PropertyId =
22402245
| {
22412246
property: "view-transition-name";
22422247
}
2248+
| {
2249+
property: "color-scheme";
2250+
}
22432251
| {
22442252
property: "all";
22452253
}
@@ -3688,6 +3696,10 @@ export type Declaration =
36883696
property: "view-transition-name";
36893697
value: String;
36903698
}
3699+
| {
3700+
property: "color-scheme";
3701+
value: ColorScheme;
3702+
}
36913703
| {
36923704
property: "unparsed";
36933705
value: UnparsedProperty;
@@ -8830,6 +8842,11 @@ export interface Container {
88308842
*/
88318843
name: ContainerNameList;
88328844
}
8845+
export interface ColorScheme {
8846+
dark: boolean;
8847+
light: boolean;
8848+
only: boolean;
8849+
}
88338850
/**
88348851
* A known property with an unparsed value.
88358852
*

scripts/build-prefixes.js

+1
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,7 @@ let mdnFeatures = {
327327
fontStyleObliqueAngle: mdn.css.properties['font-style']['oblique-angle'].__compat.support,
328328
fontWeightNumber: mdn.css.properties['font-weight'].number.__compat.support,
329329
fontStretchPercentage: mdn.css.properties['font-stretch'].percentage.__compat.support,
330+
lightDark: mdn.css.types.color['light-dark'].__compat.support
330331
};
331332

332333
for (let key in mdn.css.types.length) {

src/compat.rs

+23
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ pub enum Feature {
108108
LangSelectorList,
109109
LaoListStyleType,
110110
LhUnit,
111+
LightDark,
111112
LinearGradient,
112113
LogicalBorderRadius,
113114
LogicalBorderShorthand,
@@ -3263,6 +3264,28 @@ impl Feature {
32633264
return false;
32643265
}
32653266
}
3267+
Feature::LightDark => {
3268+
if let Some(version) = browsers.chrome {
3269+
if version < 8060928 {
3270+
return false;
3271+
}
3272+
}
3273+
if let Some(version) = browsers.firefox {
3274+
if version < 7864320 {
3275+
return false;
3276+
}
3277+
}
3278+
if browsers.android.is_some()
3279+
|| browsers.edge.is_some()
3280+
|| browsers.ie.is_some()
3281+
|| browsers.ios_saf.is_some()
3282+
|| browsers.opera.is_some()
3283+
|| browsers.safari.is_some()
3284+
|| browsers.samsung.is_some()
3285+
{
3286+
return false;
3287+
}
3288+
}
32663289
Feature::QUnit => {
32673290
if let Some(version) = browsers.chrome {
32683291
if version < 4128768 {

src/context.rs

+44-1
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,15 @@ use std::collections::HashSet;
22

33
use crate::compat::Feature;
44
use crate::declaration::DeclarationBlock;
5+
use crate::media_query::{MediaCondition, MediaFeatureId, MediaFeatureName, MediaFeatureValue, MediaList, MediaQuery, MediaType, QueryFeature};
56
use crate::properties::custom::UnparsedProperty;
67
use crate::properties::Property;
8+
use crate::rules::media::MediaRule;
79
use crate::rules::supports::{SupportsCondition, SupportsRule};
810
use crate::rules::{style::StyleRule, CssRule, CssRuleList};
911
use crate::selector::{Direction, PseudoClass};
1012
use crate::targets::Targets;
13+
use crate::values::ident::Ident;
1114
use crate::vendor_prefix::VendorPrefix;
1215
use parcel_selectors::parser::Component;
1316

@@ -33,6 +36,7 @@ pub(crate) struct PropertyHandlerContext<'i, 'o> {
3336
supports: Vec<SupportsEntry<'i>>,
3437
ltr: Vec<Property<'i>>,
3538
rtl: Vec<Property<'i>>,
39+
dark: Vec<Property<'i>>,
3640
pub context: DeclarationContext,
3741
pub unused_symbols: &'o HashSet<String>,
3842
}
@@ -45,6 +49,7 @@ impl<'i, 'o> PropertyHandlerContext<'i, 'o> {
4549
supports: Vec::new(),
4650
ltr: Vec::new(),
4751
rtl: Vec::new(),
52+
dark: Vec::new(),
4853
context: DeclarationContext::None,
4954
unused_symbols,
5055
}
@@ -57,6 +62,7 @@ impl<'i, 'o> PropertyHandlerContext<'i, 'o> {
5762
supports: Vec::new(),
5863
ltr: Vec::new(),
5964
rtl: Vec::new(),
65+
dark: Vec::new(),
6066
context,
6167
unused_symbols: self.unused_symbols,
6268
}
@@ -77,7 +83,11 @@ impl<'i, 'o> PropertyHandlerContext<'i, 'o> {
7783
self.rtl.push(rtl);
7884
}
7985

80-
pub fn get_logical_rules<T>(&self, style_rule: &StyleRule<'i, T>) -> Vec<CssRule<'i, T>> {
86+
pub fn add_dark_rule(&mut self, property: Property<'i>) {
87+
self.dark.push(property);
88+
}
89+
90+
pub fn get_additional_rules<T>(&self, style_rule: &StyleRule<'i, T>) -> Vec<CssRule<'i, T>> {
8191
// TODO: :dir/:lang raises the specificity of the selector. Use :where to lower it?
8292
let mut dest = Vec::new();
8393

@@ -113,6 +123,38 @@ impl<'i, 'o> PropertyHandlerContext<'i, 'o> {
113123
rule!(Rtl, rtl);
114124
}
115125

126+
if !self.dark.is_empty() {
127+
dest.push(CssRule::Media(MediaRule {
128+
query: MediaList {
129+
media_queries: vec![
130+
MediaQuery {
131+
qualifier: None,
132+
media_type: MediaType::All,
133+
condition: Some(MediaCondition::Feature(
134+
QueryFeature::Plain {
135+
name: MediaFeatureName::Standard(MediaFeatureId::PrefersColorScheme),
136+
value: MediaFeatureValue::Ident(Ident("dark".into()))
137+
}
138+
))
139+
}
140+
],
141+
},
142+
rules: CssRuleList(vec![
143+
CssRule::Style(StyleRule {
144+
selectors: style_rule.selectors.clone(),
145+
vendor_prefix: VendorPrefix::None,
146+
declarations: DeclarationBlock {
147+
declarations: self.dark.clone(),
148+
important_declarations: vec![],
149+
},
150+
rules: CssRuleList(vec![]),
151+
loc: style_rule.loc.clone(),
152+
})
153+
]),
154+
loc: style_rule.loc.clone(),
155+
}))
156+
}
157+
116158
dest
117159
}
118160

@@ -190,5 +232,6 @@ impl<'i, 'o> PropertyHandlerContext<'i, 'o> {
190232
self.supports.clear();
191233
self.ltr.clear();
192234
self.rtl.clear();
235+
self.dark.clear();
193236
}
194237
}

src/declaration.rs

+4
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ use crate::properties::{
3030
text::TextDecorationHandler,
3131
transform::TransformHandler,
3232
transition::TransitionHandler,
33+
ui::ColorSchemeHandler
3334
};
3435
use crate::properties::{Property, PropertyId};
3536
use crate::traits::{PropertyHandler, ToCss};
@@ -509,6 +510,7 @@ pub(crate) struct DeclarationHandler<'i> {
509510
box_shadow: BoxShadowHandler,
510511
mask: MaskHandler<'i>,
511512
container: ContainerHandler<'i>,
513+
color_scheme: ColorSchemeHandler,
512514
fallback: FallbackHandler,
513515
prefix: PrefixHandler,
514516
decls: DeclarationList<'i>,
@@ -550,6 +552,7 @@ impl<'i> DeclarationHandler<'i> {
550552
|| self.box_shadow.handle_property(property, &mut self.decls, context)
551553
|| self.mask.handle_property(property, &mut self.decls, context)
552554
|| self.container.handle_property(property, &mut self.decls, context)
555+
|| self.color_scheme.handle_property(property, &mut self.decls, context)
553556
|| self.fallback.handle_property(property, &mut self.decls, context)
554557
|| self.prefix.handle_property(property, &mut self.decls, context)
555558
}
@@ -579,6 +582,7 @@ impl<'i> DeclarationHandler<'i> {
579582
self.box_shadow.finalize(&mut self.decls, context);
580583
self.mask.finalize(&mut self.decls, context);
581584
self.container.finalize(&mut self.decls, context);
585+
self.color_scheme.finalize(&mut self.decls, context);
582586
self.fallback.finalize(&mut self.decls, context);
583587
self.prefix.finalize(&mut self.decls, context);
584588
}

src/lib.rs

+103
Original file line numberDiff line numberDiff line change
@@ -26934,4 +26934,107 @@ mod tests {
2693426934
"#},
2693526935
);
2693626936
}
26937+
26938+
#[test]
26939+
fn test_color_scheme() {
26940+
minify_test(".foo { color-scheme: normal; }", ".foo{color-scheme:normal}");
26941+
minify_test(".foo { color-scheme: light; }", ".foo{color-scheme:light}");
26942+
minify_test(".foo { color-scheme: dark; }", ".foo{color-scheme:dark}");
26943+
minify_test(".foo { color-scheme: light dark; }", ".foo{color-scheme:light dark}");
26944+
minify_test(".foo { color-scheme: dark light; }", ".foo{color-scheme:light dark}");
26945+
minify_test(".foo { color-scheme: only light; }", ".foo{color-scheme:light only}");
26946+
minify_test(".foo { color-scheme: only dark; }", ".foo{color-scheme:dark only}");
26947+
minify_test(".foo { color-scheme: dark light only; }", ".foo{color-scheme:light dark only}");
26948+
minify_test(".foo { color-scheme: foo bar light; }", ".foo{color-scheme:light}");
26949+
minify_test(".foo { color-scheme: only foo dark bar; }", ".foo{color-scheme:dark only}");
26950+
prefix_test(
26951+
".foo { color-scheme: dark; }",
26952+
indoc! { r#"
26953+
.foo {
26954+
--lightningcss-light: ;
26955+
--lightningcss-dark: initial;
26956+
color-scheme: dark;
26957+
}
26958+
"#},
26959+
Browsers {
26960+
chrome: Some(90 << 16),
26961+
..Browsers::default()
26962+
}
26963+
);
26964+
prefix_test(
26965+
".foo { color-scheme: light; }",
26966+
indoc! { r#"
26967+
.foo {
26968+
--lightningcss-light: initial;
26969+
--lightningcss-dark: ;
26970+
color-scheme: light;
26971+
}
26972+
"#},
26973+
Browsers {
26974+
chrome: Some(90 << 16),
26975+
..Browsers::default()
26976+
}
26977+
);
26978+
prefix_test(
26979+
".foo { color-scheme: light dark; }",
26980+
indoc! { r#"
26981+
.foo {
26982+
--lightningcss-light: initial;
26983+
--lightningcss-dark: ;
26984+
color-scheme: light dark;
26985+
}
26986+
26987+
@media (prefers-color-scheme: dark) {
26988+
.foo {
26989+
--lightningcss-light: ;
26990+
--lightningcss-dark: initial;
26991+
}
26992+
}
26993+
"#},
26994+
Browsers {
26995+
chrome: Some(90 << 16),
26996+
..Browsers::default()
26997+
}
26998+
);
26999+
prefix_test(
27000+
".foo { color-scheme: light dark; }",
27001+
indoc! { r#"
27002+
.foo {
27003+
color-scheme: light dark;
27004+
}
27005+
"#},
27006+
Browsers {
27007+
firefox: Some(120 << 16),
27008+
..Browsers::default()
27009+
}
27010+
);
27011+
27012+
minify_test(".foo { color: light-dark(yellow, red); }", ".foo{color:light-dark(#ff0,red)}");
27013+
minify_test(".foo { color: light-dark(rgb(0, 0, 255), hsl(120deg, 50%, 50%)); }", ".foo{color:light-dark(#00f,#40bf40)}");
27014+
prefix_test(
27015+
".foo { color: light-dark(oklch(40% 0.1268735435 34.568626), oklab(59.686% 0.1009 0.1192)); }",
27016+
indoc! { r#"
27017+
.foo {
27018+
color: var(--lightningcss-light, #7e250f) var(--lightningcss-dark, #c65d07);
27019+
color: var(--lightningcss-light, lab(29.2661% 38.2437 35.3889)) var(--lightningcss-dark, lab(52.2319% 40.1449 59.9171));
27020+
}
27021+
"#},
27022+
Browsers {
27023+
chrome: Some(90 << 16),
27024+
..Browsers::default()
27025+
}
27026+
);
27027+
prefix_test(
27028+
".foo { color: light-dark(oklch(40% 0.1268735435 34.568626), oklab(59.686% 0.1009 0.1192)); }",
27029+
indoc! { r#"
27030+
.foo {
27031+
color: light-dark(oklch(40% .126874 34.5686), oklab(59.686% .1009 .1192));
27032+
}
27033+
"#},
27034+
Browsers {
27035+
firefox: Some(120 << 16),
27036+
..Browsers::default()
27037+
}
27038+
);
27039+
}
2693727040
}

src/properties/mod.rs

+4-1
Original file line numberDiff line numberDiff line change
@@ -1586,6 +1586,9 @@ define_properties! {
15861586

15871587
// https://w3c.github.io/csswg-drafts/css-view-transitions-1/
15881588
"view-transition-name": ViewTransitionName(CustomIdent<'i>),
1589+
1590+
// https://drafts.csswg.org/css-color-adjust/
1591+
"color-scheme": ColorScheme(ColorScheme),
15891592
}
15901593

15911594
impl<'i, T: smallvec::Array<Item = V>, V: Parse<'i>> Parse<'i> for SmallVec<T> {
@@ -1600,7 +1603,7 @@ impl<'i, T: smallvec::Array<Item = V>, V: Parse<'i>> Parse<'i> for SmallVec<T> {
16001603
}
16011604
match input.next() {
16021605
Err(_) => return Ok(values),
1603-
Ok(&Token::Comma) => continue,
1606+
Ok(&cssparser::Token::Comma) => continue,
16041607
Ok(_) => unreachable!(),
16051608
}
16061609
}

0 commit comments

Comments
 (0)