Skip to content

Commit

Permalink
Support for light-dark() function and color-scheme property
Browse files Browse the repository at this point in the history
  • Loading branch information
devongovett committed Feb 13, 2024
1 parent 4f5a44c commit 808ce79
Show file tree
Hide file tree
Showing 10 changed files with 479 additions and 14 deletions.
19 changes: 18 additions & 1 deletion node/ast.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -637,7 +637,7 @@ export type Token =
*
* 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.
*/
export type CssColor = CurrentColor | RGBColor | LABColor | PredefinedColor | FloatColor;
export type CssColor = CurrentColor | RGBColor | LABColor | PredefinedColor | FloatColor | LightDark;
export type CurrentColor = {
type: "currentcolor";
};
Expand Down Expand Up @@ -957,6 +957,11 @@ export type FloatColor =
*/
w: number;
};
export type LightDark = {
dark: CssColor;
light: CssColor;
type: "light-dark";
};
/**
* 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.
*/
Expand Down Expand Up @@ -2240,6 +2245,9 @@ export type PropertyId =
| {
property: "view-transition-name";
}
| {
property: "color-scheme";
}
| {
property: "all";
}
Expand Down Expand Up @@ -3688,6 +3696,10 @@ export type Declaration =
property: "view-transition-name";
value: String;
}
| {
property: "color-scheme";
value: ColorScheme;
}
| {
property: "unparsed";
value: UnparsedProperty;
Expand Down Expand Up @@ -8830,6 +8842,11 @@ export interface Container {
*/
name: ContainerNameList;
}
export interface ColorScheme {
dark: boolean;
light: boolean;
only: boolean;
}
/**
* A known property with an unparsed value.
*
Expand Down
1 change: 1 addition & 0 deletions scripts/build-prefixes.js
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,7 @@ let mdnFeatures = {
fontStyleObliqueAngle: mdn.css.properties['font-style']['oblique-angle'].__compat.support,
fontWeightNumber: mdn.css.properties['font-weight'].number.__compat.support,
fontStretchPercentage: mdn.css.properties['font-stretch'].percentage.__compat.support,
lightDark: mdn.css.types.color['light-dark'].__compat.support
};

for (let key in mdn.css.types.length) {
Expand Down
23 changes: 23 additions & 0 deletions src/compat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ pub enum Feature {
LangSelectorList,
LaoListStyleType,
LhUnit,
LightDark,
LinearGradient,
LogicalBorderRadius,
LogicalBorderShorthand,
Expand Down Expand Up @@ -3263,6 +3264,28 @@ impl Feature {
return false;
}
}
Feature::LightDark => {
if let Some(version) = browsers.chrome {
if version < 8060928 {
return false;
}
}
if let Some(version) = browsers.firefox {
if version < 7864320 {
return false;
}
}
if browsers.android.is_some()
|| browsers.edge.is_some()
|| browsers.ie.is_some()
|| browsers.ios_saf.is_some()
|| browsers.opera.is_some()
|| browsers.safari.is_some()
|| browsers.samsung.is_some()
{
return false;
}
}
Feature::QUnit => {
if let Some(version) = browsers.chrome {
if version < 4128768 {
Expand Down
45 changes: 44 additions & 1 deletion src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@ use std::collections::HashSet;

use crate::compat::Feature;
use crate::declaration::DeclarationBlock;
use crate::media_query::{MediaCondition, MediaFeatureId, MediaFeatureName, MediaFeatureValue, MediaList, MediaQuery, MediaType, QueryFeature};
use crate::properties::custom::UnparsedProperty;
use crate::properties::Property;
use crate::rules::media::MediaRule;
use crate::rules::supports::{SupportsCondition, SupportsRule};
use crate::rules::{style::StyleRule, CssRule, CssRuleList};
use crate::selector::{Direction, PseudoClass};
use crate::targets::Targets;
use crate::values::ident::Ident;
use crate::vendor_prefix::VendorPrefix;
use parcel_selectors::parser::Component;

Expand All @@ -33,6 +36,7 @@ pub(crate) struct PropertyHandlerContext<'i, 'o> {
supports: Vec<SupportsEntry<'i>>,
ltr: Vec<Property<'i>>,
rtl: Vec<Property<'i>>,
dark: Vec<Property<'i>>,
pub context: DeclarationContext,
pub unused_symbols: &'o HashSet<String>,
}
Expand All @@ -45,6 +49,7 @@ impl<'i, 'o> PropertyHandlerContext<'i, 'o> {
supports: Vec::new(),
ltr: Vec::new(),
rtl: Vec::new(),
dark: Vec::new(),
context: DeclarationContext::None,
unused_symbols,
}
Expand All @@ -57,6 +62,7 @@ impl<'i, 'o> PropertyHandlerContext<'i, 'o> {
supports: Vec::new(),
ltr: Vec::new(),
rtl: Vec::new(),
dark: Vec::new(),
context,
unused_symbols: self.unused_symbols,
}
Expand All @@ -77,7 +83,11 @@ impl<'i, 'o> PropertyHandlerContext<'i, 'o> {
self.rtl.push(rtl);
}

pub fn get_logical_rules<T>(&self, style_rule: &StyleRule<'i, T>) -> Vec<CssRule<'i, T>> {
pub fn add_dark_rule(&mut self, property: Property<'i>) {
self.dark.push(property);
}

pub fn get_additional_rules<T>(&self, style_rule: &StyleRule<'i, T>) -> Vec<CssRule<'i, T>> {
// TODO: :dir/:lang raises the specificity of the selector. Use :where to lower it?
let mut dest = Vec::new();

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

if !self.dark.is_empty() {
dest.push(CssRule::Media(MediaRule {
query: MediaList {
media_queries: vec![
MediaQuery {
qualifier: None,
media_type: MediaType::All,
condition: Some(MediaCondition::Feature(
QueryFeature::Plain {
name: MediaFeatureName::Standard(MediaFeatureId::PrefersColorScheme),
value: MediaFeatureValue::Ident(Ident("dark".into()))
}
))
}
],
},
rules: CssRuleList(vec![
CssRule::Style(StyleRule {
selectors: style_rule.selectors.clone(),
vendor_prefix: VendorPrefix::None,
declarations: DeclarationBlock {
declarations: self.dark.clone(),
important_declarations: vec![],
},
rules: CssRuleList(vec![]),
loc: style_rule.loc.clone(),
})
]),
loc: style_rule.loc.clone(),
}))
}

dest
}

Expand Down Expand Up @@ -190,5 +232,6 @@ impl<'i, 'o> PropertyHandlerContext<'i, 'o> {
self.supports.clear();
self.ltr.clear();
self.rtl.clear();
self.dark.clear();
}
}
4 changes: 4 additions & 0 deletions src/declaration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ use crate::properties::{
text::TextDecorationHandler,
transform::TransformHandler,
transition::TransitionHandler,
ui::ColorSchemeHandler
};
use crate::properties::{Property, PropertyId};
use crate::traits::{PropertyHandler, ToCss};
Expand Down Expand Up @@ -509,6 +510,7 @@ pub(crate) struct DeclarationHandler<'i> {
box_shadow: BoxShadowHandler,
mask: MaskHandler<'i>,
container: ContainerHandler<'i>,
color_scheme: ColorSchemeHandler,
fallback: FallbackHandler,
prefix: PrefixHandler,
decls: DeclarationList<'i>,
Expand Down Expand Up @@ -550,6 +552,7 @@ impl<'i> DeclarationHandler<'i> {
|| self.box_shadow.handle_property(property, &mut self.decls, context)
|| self.mask.handle_property(property, &mut self.decls, context)
|| self.container.handle_property(property, &mut self.decls, context)
|| self.color_scheme.handle_property(property, &mut self.decls, context)
|| self.fallback.handle_property(property, &mut self.decls, context)
|| self.prefix.handle_property(property, &mut self.decls, context)
}
Expand Down Expand Up @@ -579,6 +582,7 @@ impl<'i> DeclarationHandler<'i> {
self.box_shadow.finalize(&mut self.decls, context);
self.mask.finalize(&mut self.decls, context);
self.container.finalize(&mut self.decls, context);
self.color_scheme.finalize(&mut self.decls, context);
self.fallback.finalize(&mut self.decls, context);
self.prefix.finalize(&mut self.decls, context);
}
Expand Down
103 changes: 103 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26934,4 +26934,107 @@ mod tests {
"#},
);
}

#[test]
fn test_color_scheme() {
minify_test(".foo { color-scheme: normal; }", ".foo{color-scheme:normal}");
minify_test(".foo { color-scheme: light; }", ".foo{color-scheme:light}");
minify_test(".foo { color-scheme: dark; }", ".foo{color-scheme:dark}");
minify_test(".foo { color-scheme: light dark; }", ".foo{color-scheme:light dark}");
minify_test(".foo { color-scheme: dark light; }", ".foo{color-scheme:light dark}");
minify_test(".foo { color-scheme: only light; }", ".foo{color-scheme:light only}");
minify_test(".foo { color-scheme: only dark; }", ".foo{color-scheme:dark only}");
minify_test(".foo { color-scheme: dark light only; }", ".foo{color-scheme:light dark only}");
minify_test(".foo { color-scheme: foo bar light; }", ".foo{color-scheme:light}");
minify_test(".foo { color-scheme: only foo dark bar; }", ".foo{color-scheme:dark only}");
prefix_test(
".foo { color-scheme: dark; }",
indoc! { r#"
.foo {
--lightningcss-light: ;
--lightningcss-dark: initial;
color-scheme: dark;
}
"#},
Browsers {
chrome: Some(90 << 16),
..Browsers::default()
}
);
prefix_test(
".foo { color-scheme: light; }",
indoc! { r#"
.foo {
--lightningcss-light: initial;
--lightningcss-dark: ;
color-scheme: light;
}
"#},
Browsers {
chrome: Some(90 << 16),
..Browsers::default()
}
);
prefix_test(
".foo { color-scheme: light dark; }",
indoc! { r#"
.foo {
--lightningcss-light: initial;
--lightningcss-dark: ;
color-scheme: light dark;
}

@media (prefers-color-scheme: dark) {
.foo {
--lightningcss-light: ;
--lightningcss-dark: initial;
}
}
"#},
Browsers {
chrome: Some(90 << 16),
..Browsers::default()
}
);
prefix_test(
".foo { color-scheme: light dark; }",
indoc! { r#"
.foo {
color-scheme: light dark;
}
"#},
Browsers {
firefox: Some(120 << 16),
..Browsers::default()
}
);

minify_test(".foo { color: light-dark(yellow, red); }", ".foo{color:light-dark(#ff0,red)}");
minify_test(".foo { color: light-dark(rgb(0, 0, 255), hsl(120deg, 50%, 50%)); }", ".foo{color:light-dark(#00f,#40bf40)}");
prefix_test(
".foo { color: light-dark(oklch(40% 0.1268735435 34.568626), oklab(59.686% 0.1009 0.1192)); }",
indoc! { r#"
.foo {
color: var(--lightningcss-light, #7e250f) var(--lightningcss-dark, #c65d07);
color: var(--lightningcss-light, lab(29.2661% 38.2437 35.3889)) var(--lightningcss-dark, lab(52.2319% 40.1449 59.9171));
}
"#},
Browsers {
chrome: Some(90 << 16),
..Browsers::default()
}
);
prefix_test(
".foo { color: light-dark(oklch(40% 0.1268735435 34.568626), oklab(59.686% 0.1009 0.1192)); }",
indoc! { r#"
.foo {
color: light-dark(oklch(40% .126874 34.5686), oklab(59.686% .1009 .1192));
}
"#},
Browsers {
firefox: Some(120 << 16),
..Browsers::default()
}
);
}
}
5 changes: 4 additions & 1 deletion src/properties/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1586,6 +1586,9 @@ define_properties! {

// https://w3c.github.io/csswg-drafts/css-view-transitions-1/
"view-transition-name": ViewTransitionName(CustomIdent<'i>),

// https://drafts.csswg.org/css-color-adjust/
"color-scheme": ColorScheme(ColorScheme),
}

impl<'i, T: smallvec::Array<Item = V>, V: Parse<'i>> Parse<'i> for SmallVec<T> {
Expand All @@ -1600,7 +1603,7 @@ impl<'i, T: smallvec::Array<Item = V>, V: Parse<'i>> Parse<'i> for SmallVec<T> {
}
match input.next() {
Err(_) => return Ok(values),
Ok(&Token::Comma) => continue,
Ok(&cssparser::Token::Comma) => continue,
Ok(_) => unreachable!(),
}
}
Expand Down
Loading

0 comments on commit 808ce79

Please sign in to comment.