From 2983614f3b4953a4bdcd0e891b1abfb7a6f65350 Mon Sep 17 00:00:00 2001 From: Devon Govett Date: Thu, 22 Feb 2024 19:13:59 -0500 Subject: [PATCH] Parse CSS system colors --- node/ast.d.ts | 48 ++++++++++++++- scripts/build-prefixes.js | 3 +- src/compat.rs | 27 +++++++++ src/lib.rs | 1 + src/values/color.rs | 122 ++++++++++++++++++++++++++++++++++++-- 5 files changed, 195 insertions(+), 6 deletions(-) diff --git a/node/ast.d.ts b/node/ast.d.ts index 05018418..25c9dd7f 100644 --- a/node/ast.d.ts +++ b/node/ast.d.ts @@ -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 | LightDark; +export type CssColor = CurrentColor | RGBColor | LABColor | PredefinedColor | FloatColor | LightDark | SystemColor; export type CurrentColor = { type: "currentcolor"; }; @@ -962,6 +962,52 @@ export type LightDark = { light: CssColor; type: "light-dark"; }; +/** + * A CSS [system color](https://drafts.csswg.org/css-color/#css-system-colors) keyword. + */ +export type SystemColor = + | "accentcolor" + | "accentcolortext" + | "activetext" + | "buttonborder" + | "buttonface" + | "buttontext" + | "canvas" + | "canvastext" + | "field" + | "fieldtext" + | "graytext" + | "highlight" + | "highlighttext" + | "linktext" + | "mark" + | "marktext" + | "selecteditem" + | "selecteditemtext" + | "visitedtext" + | "activeborder" + | "activecaption" + | "appworkspace" + | "background" + | "buttonhighlight" + | "buttonshadow" + | "captiontext" + | "inactiveborder" + | "inactivecaption" + | "inactivecaptiontext" + | "infobackground" + | "infotext" + | "menu" + | "menutext" + | "scrollbar" + | "threeddarkshadow" + | "threedface" + | "threedhighlight" + | "threedlightshadow" + | "threedshadow" + | "window" + | "windowframe" + | "windowtext"; /** * 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. */ diff --git a/scripts/build-prefixes.js b/scripts/build-prefixes.js index f335ce94..597e1dbd 100644 --- a/scripts/build-prefixes.js +++ b/scripts/build-prefixes.js @@ -327,7 +327,8 @@ 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 + lightDark: mdn.css.types.color['light-dark'].__compat.support, + accentSystemColor: mdn.css.types.color['system-color'].accentcolor_accentcolortext.__compat.support, }; for (let key in mdn.css.types.length) { diff --git a/src/compat.rs b/src/compat.rs index 3abc9214..1a4bec65 100644 --- a/src/compat.rs +++ b/src/compat.rs @@ -6,6 +6,7 @@ use crate::targets::Browsers; #[derive(Clone, Copy, PartialEq)] pub enum Feature { AbsFunction, + AccentSystemColor, AfarListStyleType, AmharicAbegedeListStyleType, AmharicListStyleType, @@ -3286,6 +3287,32 @@ impl Feature { return false; } } + Feature::AccentSystemColor => { + if let Some(version) = browsers.firefox { + if version < 6750208 { + return false; + } + } + if let Some(version) = browsers.safari { + if version < 1049856 { + return false; + } + } + if let Some(version) = browsers.ios_saf { + if version < 1049856 { + return false; + } + } + if browsers.android.is_some() + || browsers.chrome.is_some() + || browsers.edge.is_some() + || browsers.ie.is_some() + || browsers.opera.is_some() + || browsers.samsung.is_some() + { + return false; + } + } Feature::QUnit => { if let Some(version) = browsers.chrome { if version < 4128768 { diff --git a/src/lib.rs b/src/lib.rs index d22bbf33..efaf28c9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15919,6 +15919,7 @@ mod tests { minify_test(".foo { color: hsla(100 100% 50% / .8) }", ".foo{color:#5f0c}"); minify_test(".foo { color: transparent }", ".foo{color:#0000}"); minify_test(".foo { color: currentColor }", ".foo{color:currentColor}"); + minify_test(".foo { color: ButtonBorder }", ".foo{color:buttonborder}"); minify_test(".foo { color: hwb(194 0% 0%) }", ".foo{color:#00c4ff}"); minify_test(".foo { color: hwb(194 0% 0% / 50%) }", ".foo{color:#00c4ff80}"); minify_test(".foo { color: hwb(194 0% 50%) }", ".foo{color:#006280}"); diff --git a/src/values/color.rs b/src/values/color.rs index e49040d3..25a92333 100644 --- a/src/values/color.rs +++ b/src/values/color.rs @@ -64,6 +64,8 @@ pub enum CssColor { #[cfg_attr(feature = "visitor", skip_type)] #[cfg_attr(feature = "serde", serde(with = "LightDark"))] LightDark(Box, Box), + /// A [system color](https://drafts.csswg.org/css-color/#css-system-colors) keyword. + System(SystemColor), } #[cfg(feature = "serde")] @@ -361,7 +363,7 @@ impl CssColor { // below and including the authored color space, and remove the ones that aren't // compatible with our browser targets. let mut fallbacks = match self { - CssColor::CurrentColor | CssColor::RGBA(_) | CssColor::Float(..) => return ColorFallbackKind::empty(), + CssColor::CurrentColor | CssColor::RGBA(_) | CssColor::Float(..) | CssColor::System(..) => return ColorFallbackKind::empty(), CssColor::LAB(lab) => match &**lab { LABColor::LAB(..) | LABColor::LCH(..) if should_compile!(targets, LabColors) => { ColorFallbackKind::LAB.and_below() @@ -456,7 +458,8 @@ impl IsCompatible for CssColor { }, CssColor::LightDark(light, dark) => { Feature::LightDark.is_compatible(browsers) && light.is_compatible(browsers) && dark.is_compatible(browsers) - } + }, + CssColor::System(system) => system.is_compatible(browsers) } } } @@ -500,8 +503,13 @@ impl<'i> Parse<'i> for CssColor { "currentcolor" => CssColor::CurrentColor, "transparent" => CssColor::RGBA(RGBA::transparent()), _ => { - let (r, g, b) = parse_named_color(value).map_err(|_| location.new_unexpected_token_error(token.clone()))?; - CssColor::RGBA(RGBA { red: r, green: g, blue: b, alpha: 255 }) + if let Ok((r, g, b)) = parse_named_color(value) { + CssColor::RGBA(RGBA { red: r, green: g, blue: b, alpha: 255 }) + } else if let Ok(system_color) = SystemColor::parse_string(&value) { + CssColor::System(system_color) + } else { + return Err(location.new_unexpected_token_error(token.clone())) + } } }), Token::Function(ref name) => parse_color_function(location, name.clone(), input), @@ -603,6 +611,7 @@ impl ToCss for CssColor { dark.to_css(dest)?; dest.write_char(')') } + CssColor::System(system) => system.to_css(dest), } } } @@ -2881,6 +2890,7 @@ macro_rules! color_space { CssColor::Float(float) => (**float).into(), CssColor::CurrentColor => return Err(()), CssColor::LightDark(..) => return Err(()), + CssColor::System(..) => return Err(()) }) } } @@ -2895,6 +2905,7 @@ macro_rules! color_space { CssColor::Float(float) => (*float).into(), CssColor::CurrentColor => return Err(()), CssColor::LightDark(..) => return Err(()), + CssColor::System(..) => return Err(()) }) } } @@ -3578,3 +3589,106 @@ impl<'i, V: ?Sized + Visitor<'i, T>, T: Visit<'i, T, V>> Visit<'i, T, V> for RGB Ok(()) } } + +enum_property! { + /// A CSS [system color](https://drafts.csswg.org/css-color/#css-system-colors) keyword. + pub enum SystemColor { + /// Background of accented user interface controls. + AccentColor, + /// Text of accented user interface controls. + AccentColorText, + /// Text in active links. For light backgrounds, traditionally red. + ActiveText, + /// The base border color for push buttons. + ButtonBorder, + /// The face background color for push buttons. + ButtonFace, + /// Text on push buttons. + ButtonText, + /// Background of application content or documents. + Canvas, + /// Text in application content or documents. + CanvasText, + /// Background of input fields. + Field, + /// Text in input fields. + FieldText, + /// Disabled text. (Often, but not necessarily, gray.) + GrayText, + /// Background of selected text, for example from ::selection. + Highlight, + /// Text of selected text. + HighlightText, + /// Text in non-active, non-visited links. For light backgrounds, traditionally blue. + LinkText, + /// Background of text that has been specially marked (such as by the HTML mark element). + Mark, + /// Text that has been specially marked (such as by the HTML mark element). + MarkText, + /// Background of selected items, for example a selected checkbox. + SelectedItem, + /// Text of selected items. + SelectedItemText, + /// Text in visited links. For light backgrounds, traditionally purple. + VisitedText, + + // Deprecated colors: https://drafts.csswg.org/css-color/#deprecated-system-colors + + /// Active window border. Same as ButtonBorder. + ActiveBorder, + /// Active window caption. Same as Canvas. + ActiveCaption, + /// Background color of multiple document interface. Same as Canvas. + AppWorkspace, + /// Desktop background. Same as Canvas. + Background, + /// The color of the border facing the light source for 3-D elements that appear 3-D due to one layer of surrounding border. Same as ButtonFace. + ButtonHighlight, + /// The color of the border away from the light source for 3-D elements that appear 3-D due to one layer of surrounding border. Same as ButtonFace. + ButtonShadow, + /// Text in caption, size box, and scrollbar arrow box. Same as CanvasText. + CaptionText, + /// Inactive window border. Same as ButtonBorder. + InactiveBorder, + /// Inactive window caption. Same as Canvas. + InactiveCaption, + /// Color of text in an inactive caption. Same as GrayText. + InactiveCaptionText, + /// Background color for tooltip controls. Same as Canvas. + InfoBackground, + /// Text color for tooltip controls. Same as CanvasText. + InfoText, + /// Menu background. Same as Canvas. + Menu, + /// Text in menus. Same as CanvasText. + MenuText, + /// Scroll bar gray area. Same as Canvas. + Scrollbar, + /// The color of the darker (generally outer) of the two borders away from the light source for 3-D elements that appear 3-D due to two concentric layers of surrounding border. Same as ButtonBorder. + ThreeDDarkShadow, + /// The face background color for 3-D elements that appear 3-D due to two concentric layers of surrounding border. Same as ButtonFace. + ThreeDFace, + /// The color of the lighter (generally outer) of the two borders facing the light source for 3-D elements that appear 3-D due to two concentric layers of surrounding border. Same as ButtonBorder. + ThreeDHighlight, + /// The color of the darker (generally inner) of the two borders facing the light source for 3-D elements that appear 3-D due to two concentric layers of surrounding border. Same as ButtonBorder. + ThreeDLightShadow, + /// The color of the lighter (generally inner) of the two borders away from the light source for 3-D elements that appear 3-D due to two concentric layers of surrounding border. Same as ButtonBorder. + ThreeDShadow, + /// Window background. Same as Canvas. + Window, + /// Window frame. Same as ButtonBorder. + WindowFrame, + /// Text in windows. Same as CanvasText. + WindowText, + } +} + +impl IsCompatible for SystemColor { + fn is_compatible(&self, browsers: Browsers) -> bool { + use SystemColor::*; + match self { + AccentColor | AccentColorText => Feature::AccentSystemColor.is_compatible(browsers), + _ => true + } + } +}