From d7aeff3db67ee9d15e0fefce9251cf41c0b8ec44 Mon Sep 17 00:00:00 2001
From: Devon Govett
Date: Mon, 13 May 2024 19:19:35 -0700
Subject: [PATCH 001/152] optimize "all" property
---
Cargo.lock | 7 ++--
Cargo.toml | 1 +
node/ast.d.ts | 34 +++++++++++++++-
node/test/visitor.test.mjs | 4 +-
src/declaration.rs | 66 +++++++++++++++++++++++++-------
src/lib.rs | 41 ++++++++++++++++----
src/properties/mod.rs | 48 ++++++++++++++++++++++-
src/properties/prefix_handler.rs | 6 ---
src/properties/text.rs | 28 ++++++++++++++
9 files changed, 200 insertions(+), 35 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
index db6c59ae..7358e44c 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -153,7 +153,7 @@ dependencies = [
"anyhow",
"chrono",
"either",
- "indexmap 2.2.2",
+ "indexmap 2.2.6",
"itertools 0.12.1",
"nom",
"once_cell",
@@ -658,9 +658,9 @@ dependencies = [
[[package]]
name = "indexmap"
-version = "2.2.2"
+version = "2.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "824b2ae422412366ba479e8111fd301f7b5faece8149317bb81925979a53f520"
+checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26"
dependencies = [
"equivalent",
"hashbrown 0.14.2",
@@ -775,6 +775,7 @@ dependencies = [
"dashmap",
"data-encoding",
"getrandom",
+ "indexmap 2.2.6",
"indoc",
"itertools 0.10.5",
"jemallocator",
diff --git a/Cargo.toml b/Cargo.toml
index 4e27dbf5..795b25ac 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -62,6 +62,7 @@ const-str = "0.3.1"
pathdiff = "0.2.1"
ahash = "0.8.7"
paste = "1.0.12"
+indexmap = "2.2.6"
# CLI deps
atty = { version = "0.2", optional = true }
clap = { version = "3.0.6", features = ["derive"], optional = true }
diff --git a/node/ast.d.ts b/node/ast.d.ts
index 25c9dd7f..bd6e0e98 100644
--- a/node/ast.d.ts
+++ b/node/ast.d.ts
@@ -2066,6 +2066,12 @@ export type PropertyId =
property: "text-size-adjust";
vendorPrefix: VendorPrefix;
}
+ | {
+ property: "direction";
+ }
+ | {
+ property: "unicode-bidi";
+ }
| {
property: "box-decoration-break";
vendorPrefix: VendorPrefix;
@@ -3445,6 +3451,14 @@ export type Declaration =
value: TextSizeAdjust;
vendorPrefix: VendorPrefix;
}
+ | {
+ property: "direction";
+ value: Direction2;
+ }
+ | {
+ property: "unicode-bidi";
+ value: UnicodeBidi;
+ }
| {
property: "box-decoration-break";
value: BoxDecorationBreak;
@@ -3757,6 +3771,10 @@ export type Declaration =
property: "color-scheme";
value: ColorScheme;
}
+ | {
+ property: "all";
+ value: CSSWideKeyword;
+ }
| {
property: "unparsed";
value: UnparsedProperty;
@@ -5729,6 +5747,14 @@ export type TextSizeAdjust =
type: "percentage";
value: number;
};
+/**
+ * A value for the [direction](https://drafts.csswg.org/css-writing-modes-3/#direction) property.
+ */
+export type Direction2 = "ltr" | "rtl";
+/**
+ * A value for the [unicode-bidi](https://drafts.csswg.org/css-writing-modes-3/#unicode-bidi) property.
+ */
+export type UnicodeBidi = "normal" | "embed" | "isolate" | "bidi-override" | "isolate-override" | "plaintext";
/**
* A value for the [box-decoration-break](https://www.w3.org/TR/css-break-3/#break-decoration) property.
*/
@@ -6234,6 +6260,10 @@ export type ContainerNameList =
type: "names";
value: String[];
};
+/**
+ * A [CSS-wide keyword](https://drafts.csswg.org/css-cascade-5/#defaulting-keywords).
+ */
+export type CSSWideKeyword = "initial" | "inherit" | "unset" | "revert" | "revert-layer";
/**
* A CSS custom property name.
*/
@@ -7040,8 +7070,8 @@ export type ParsedComponent =
};
}
| {
- type: "token";
- value: Token;
+ type: "token-list";
+ value: TokenOrValue[];
};
/**
* A [multiplier](https://drafts.css-houdini.org/css-properties-values-api/#multipliers) for a [SyntaxComponent](SyntaxComponent). Indicates whether and how the component may be repeated.
diff --git a/node/test/visitor.test.mjs b/node/test/visitor.test.mjs
index 3a42a696..87ee208d 100644
--- a/node/test/visitor.test.mjs
+++ b/node/test/visitor.test.mjs
@@ -116,7 +116,7 @@ test('custom units', () => {
}
});
- assert.equal(res.code.toString(), '.foo{--step:.25rem;font-size:calc(3*var(--step))}');
+ assert.equal(res.code.toString(), '.foo{font-size:calc(3*var(--step));--step:.25rem}');
});
test('design tokens', () => {
@@ -822,7 +822,7 @@ test('dashed idents', () => {
}
});
- assert.equal(res.code.toString(), '.foo{--prefix-foo:#ff0;color:var(--prefix-foo)}');
+ assert.equal(res.code.toString(), '.foo{color:var(--prefix-foo);--prefix-foo:#ff0}');
});
test('custom idents', () => {
diff --git a/src/declaration.rs b/src/declaration.rs
index 556fd152..501e2f8c 100644
--- a/src/declaration.rs
+++ b/src/declaration.rs
@@ -1,7 +1,6 @@
//! CSS declarations.
use std::borrow::Cow;
-use std::collections::HashMap;
use std::ops::Range;
use crate::context::{DeclarationContext, PropertyHandlerContext};
@@ -11,6 +10,7 @@ use crate::printer::Printer;
use crate::properties::box_shadow::BoxShadowHandler;
use crate::properties::custom::{CustomProperty, CustomPropertyName};
use crate::properties::masking::MaskHandler;
+use crate::properties::text::{Direction, UnicodeBidi};
use crate::properties::{
align::AlignHandler,
animation::AnimationHandler,
@@ -33,13 +33,14 @@ use crate::properties::{
transition::TransitionHandler,
ui::ColorSchemeHandler,
};
-use crate::properties::{Property, PropertyId};
+use crate::properties::{CSSWideKeyword, Property, PropertyId};
use crate::traits::{PropertyHandler, ToCss};
use crate::values::ident::DashedIdent;
use crate::values::string::CowArcStr;
#[cfg(feature = "visitor")]
use crate::visitor::Visit;
use cssparser::*;
+use indexmap::IndexMap;
/// A CSS declaration block.
///
@@ -515,7 +516,10 @@ pub(crate) struct DeclarationHandler<'i> {
color_scheme: ColorSchemeHandler,
fallback: FallbackHandler,
prefix: PrefixHandler,
- custom_properties: HashMap<'i>, usize>,
+ all: Option,
+ direction: Option,
+ unicode_bidi: Option,
+ custom_properties: IndexMap<'i>, CustomProperty<'i>>,
decls: DeclarationList<'i>,
}
@@ -552,6 +556,7 @@ impl<'i> DeclarationHandler<'i> {
|| 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)
+ || self.handle_all(property)
|| self.handle_custom_property(property, context)
}
@@ -566,18 +571,13 @@ impl<'i> DeclarationHandler<'i> {
}
if let CustomPropertyName::Custom(name) = &custom.name {
- if let Some(index) = self.custom_properties.get(name) {
- if self.decls[*index] == *property {
+ if let Some(prev) = self.custom_properties.get_mut(name) {
+ if prev.value == custom.value {
return true;
}
- let mut custom = custom.clone();
- self.add_conditional_fallbacks(&mut custom, context);
- self.decls[*index] = Property::Custom(custom);
+ *prev = custom.clone();
} else {
- self.custom_properties.insert(name.clone(), self.decls.len());
- let mut custom = custom.clone();
- self.add_conditional_fallbacks(&mut custom, context);
- self.decls.push(Property::Custom(custom));
+ self.custom_properties.insert(name.clone(), custom.clone());
}
return true;
@@ -587,6 +587,32 @@ impl<'i> DeclarationHandler<'i> {
false
}
+ fn handle_all(&mut self, property: &Property<'i>) -> bool {
+ // The `all` property resets all properies except `unicode-bidi`, `direction`, and custom properties.
+ // https://drafts.csswg.org/css-cascade-5/#all-shorthand
+ match property {
+ Property::UnicodeBidi(bidi) => {
+ self.unicode_bidi = Some(*bidi);
+ true
+ }
+ Property::Direction(direction) => {
+ self.direction = Some(*direction);
+ true
+ }
+ Property::All(keyword) => {
+ *self = DeclarationHandler {
+ custom_properties: std::mem::take(&mut self.custom_properties),
+ unicode_bidi: self.unicode_bidi.clone(),
+ direction: self.direction.clone(),
+ all: Some(keyword.clone()),
+ ..Default::default()
+ };
+ true
+ }
+ _ => false,
+ }
+ }
+
fn add_conditional_fallbacks(
&self,
custom: &mut CustomProperty<'i>,
@@ -607,6 +633,21 @@ impl<'i> DeclarationHandler<'i> {
}
pub fn finalize(&mut self, context: &mut PropertyHandlerContext<'i, '_>) {
+ // Always place the `all` property first. Previous properties will have been omitted.
+ if let Some(all) = std::mem::take(&mut self.all) {
+ self.decls.push(Property::All(all));
+ }
+ if let Some(direction) = std::mem::take(&mut self.direction) {
+ self.decls.push(Property::Direction(direction));
+ }
+ if let Some(unicode_bidi) = std::mem::take(&mut self.unicode_bidi) {
+ self.decls.push(Property::UnicodeBidi(unicode_bidi));
+ }
+ for (_, mut value) in std::mem::take(&mut self.custom_properties) {
+ self.add_conditional_fallbacks(&mut value, context);
+ self.decls.push(Property::Custom(value));
+ }
+
self.background.finalize(&mut self.decls, context);
self.border.finalize(&mut self.decls, context);
self.outline.finalize(&mut self.decls, context);
@@ -634,6 +675,5 @@ impl<'i> DeclarationHandler<'i> {
self.color_scheme.finalize(&mut self.decls, context);
self.fallback.finalize(&mut self.decls, context);
self.prefix.finalize(&mut self.decls, context);
- self.custom_properties.clear();
}
}
diff --git a/src/lib.rs b/src/lib.rs
index f13b7c48..dd3b0fe8 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -21435,26 +21435,26 @@ mod tests {
indoc! {r#"
@keyframes foo {
from {
- --custom: #ff0;
opacity: 0;
+ --custom: #ff0;
}
to {
- --custom: #ee00be;
opacity: 1;
+ --custom: #ee00be;
}
}
@supports (color: lab(0% 0 0)) {
@keyframes foo {
from {
- --custom: #ff0;
opacity: 0;
+ --custom: #ff0;
}
to {
- --custom: lab(50.998% 125.506 -50.7078);
opacity: 1;
+ --custom: lab(50.998% 125.506 -50.7078);
}
}
}
@@ -23457,8 +23457,8 @@ mod tests {
}
.EgL3uq_foo {
- --foo: red;
color: var(--foo);
+ --foo: red;
}
"#},
map! {
@@ -23507,10 +23507,10 @@ mod tests {
}
.EgL3uq_foo {
- --EgL3uq_foo: red;
- --EgL3uq_bar: green;
color: var(--EgL3uq_foo);
font-palette: --EgL3uq_Cooler;
+ --EgL3uq_foo: red;
+ --EgL3uq_bar: green;
}
.EgL3uq_bar {
@@ -27244,4 +27244,31 @@ mod tests {
},
);
}
+
+ #[test]
+ fn test_all() {
+ minify_test(".foo { all: initial; all: initial }", ".foo{all:initial}");
+ minify_test(".foo { all: initial; all: revert }", ".foo{all:revert}");
+ minify_test(".foo { background: red; all: revert-layer }", ".foo{all:revert-layer}");
+ minify_test(
+ ".foo { background: red; all: revert-layer; background: green }",
+ ".foo{all:revert-layer;background:green}",
+ );
+ minify_test(
+ ".foo { --test: red; all: revert-layer }",
+ ".foo{all:revert-layer;--test:red}",
+ );
+ minify_test(
+ ".foo { unicode-bidi: embed; all: revert-layer }",
+ ".foo{all:revert-layer;unicode-bidi:embed}",
+ );
+ minify_test(
+ ".foo { direction: rtl; all: revert-layer }",
+ ".foo{all:revert-layer;direction:rtl}",
+ );
+ minify_test(
+ ".foo { direction: rtl; all: revert-layer; direction: ltr }",
+ ".foo{all:revert-layer;direction:ltr}",
+ );
+ }
}
diff --git a/src/properties/mod.rs b/src/properties/mod.rs
index 845140f9..98756755 100644
--- a/src/properties/mod.rs
+++ b/src/properties/mod.rs
@@ -123,6 +123,7 @@ pub mod ui;
use crate::declaration::DeclarationBlock;
use crate::error::{ParserError, PrinterError};
use crate::logical::{LogicalGroup, PropertyCategory};
+use crate::macros::enum_property;
use crate::parser::starts_with_ignore_ascii_case;
use crate::parser::ParserOptions;
use crate::prefixes::Feature;
@@ -688,6 +689,8 @@ macro_rules! define_properties {
$(#[$meta])*
$property($type, $($vp)?),
)+
+ /// The [all](https://drafts.csswg.org/css-cascade-5/#all-shorthand) shorthand property.
+ All(CSSWideKeyword),
/// An unparsed property.
Unparsed(UnparsedProperty<'i>),
/// A custom or unknown property.
@@ -710,6 +713,7 @@ macro_rules! define_properties {
}
},
)+
+ PropertyId::All => return Ok(Property::All(CSSWideKeyword::parse(input)?)),
PropertyId::Custom(name) => return Ok(Property::Custom(CustomProperty::parse(name, input, options)?)),
_ => {}
};
@@ -731,6 +735,7 @@ macro_rules! define_properties {
$(#[$meta])*
$property(_, $(vp_name!($vp, p))?) => PropertyId::$property$((*vp_name!($vp, p)))?,
)+
+ All(_) => PropertyId::All,
Unparsed(unparsed) => unparsed.property_id.clone(),
Custom(custom) => PropertyId::Custom(custom.name.clone())
}
@@ -779,6 +784,7 @@ macro_rules! define_properties {
val.to_css(dest)
}
)+
+ All(keyword) => keyword.to_css(dest),
Unparsed(unparsed) => {
unparsed.value.to_css(dest, false)
}
@@ -838,6 +844,7 @@ macro_rules! define_properties {
($name, get_prefix!($($vp)?))
},
)+
+ All(_) => ("all", VendorPrefix::None),
Unparsed(unparsed) => {
let mut prefix = unparsed.property_id.prefix();
if prefix.is_empty() {
@@ -966,7 +973,10 @@ macro_rules! define_properties {
s.serialize_field("value", value)?;
}
)+
- _ => unreachable!()
+ All(value) => {
+ s.serialize_field("value", value)?;
+ }
+ Unparsed(_) | Custom(_) => unreachable!()
}
s.end()
@@ -1072,7 +1082,10 @@ macro_rules! define_properties {
Ok(Property::Custom(value))
}
}
- PropertyId::All => unreachable!()
+ PropertyId::All => {
+ let value = CSSWideKeyword::deserialize(deserializer)?;
+ Ok(Property::All(value))
+ }
}
}
}
@@ -1134,6 +1147,17 @@ macro_rules! define_properties {
with_prefix!($($vp)?)
},
)+
+ {
+ property!("all");
+ #[derive(schemars::JsonSchema)]
+ struct T {
+ #[schemars(rename = "property", schema_with = "property")]
+ _property: u8,
+ #[schemars(rename = "value")]
+ _value: CSSWideKeyword
+ }
+ T::json_schema(gen)
+ },
{
property!("unparsed");
@@ -1513,6 +1537,10 @@ define_properties! {
// https://w3c.github.io/csswg-drafts/css-size-adjust/
"text-size-adjust": TextSizeAdjust(TextSizeAdjust, VendorPrefix) / WebKit / Moz / Ms,
+ // https://drafts.csswg.org/css-writing-modes-3/
+ "direction": Direction(Direction),
+ "unicode-bidi": UnicodeBidi(UnicodeBidi),
+
// https://www.w3.org/TR/css-break-3/
"box-decoration-break": BoxDecorationBreak(BoxDecorationBreak, VendorPrefix) / WebKit,
@@ -1667,3 +1695,19 @@ impl ToCss for Vec {
Ok(())
}
}
+
+enum_property! {
+ /// A [CSS-wide keyword](https://drafts.csswg.org/css-cascade-5/#defaulting-keywords).
+ pub enum CSSWideKeyword {
+ /// The property's initial value.
+ "initial": Initial,
+ /// The property's computed value on the parent element.
+ "inherit": Inherit,
+ /// Either inherit or initial depending on whether the property is inherited.
+ "unset": Unset,
+ /// Rolls back the cascade to the cascaded value of the earlier origin.
+ "revert": Revert,
+ /// Rolls back the cascade to the value of the previous cascade layer.
+ "revert-layer": RevertLayer,
+ }
+}
diff --git a/src/properties/prefix_handler.rs b/src/properties/prefix_handler.rs
index 78dd7d09..7ad2dfe8 100644
--- a/src/properties/prefix_handler.rs
+++ b/src/properties/prefix_handler.rs
@@ -141,12 +141,6 @@ macro_rules! define_fallbacks {
(val, paste::paste! { &mut self.[<$name:snake>] })
}
)+
- PropertyId::All => {
- let mut unparsed = val.clone();
- context.add_unparsed_fallbacks(&mut unparsed);
- dest.push(Property::Unparsed(unparsed));
- return true
- },
_ => return false
};
diff --git a/src/properties/text.rs b/src/properties/text.rs
index 4c7b6fde..e22f6fab 100644
--- a/src/properties/text.rs
+++ b/src/properties/text.rs
@@ -1599,3 +1599,31 @@ impl FallbackValues for SmallVec<[TextShadow; 1]> {
res
}
}
+
+enum_property! {
+ /// A value for the [direction](https://drafts.csswg.org/css-writing-modes-3/#direction) property.
+ pub enum Direction {
+ /// This value sets inline base direction (bidi directionality) to line-left-to-line-right.
+ Ltr,
+ /// This value sets inline base direction (bidi directionality) to line-right-to-line-left.
+ Rtl,
+ }
+}
+
+enum_property! {
+ /// A value for the [unicode-bidi](https://drafts.csswg.org/css-writing-modes-3/#unicode-bidi) property.
+ pub enum UnicodeBidi {
+ /// The box does not open an additional level of embedding.
+ "normal": Normal,
+ /// If the box is inline, this value creates a directional embedding by opening an additional level of embedding.
+ "embed": Embed,
+ /// On an inline box, this bidi-isolates its contents.
+ "isolate": Isolate,
+ /// This value puts the box’s immediate inline content in a directional override.
+ "bidi-override": BidiOverride,
+ /// This combines the isolation behavior of isolate with the directional override behavior of bidi-override.
+ "isolate-override": IsolateOverride,
+ /// This value behaves as isolate except that the base directionality is determined using a heuristic rather than the direction property.
+ "plaintext": Plaintext,
+ }
+}
From a4cc0246d28c364c0f4e16552685bd911444bdc5 Mon Sep 17 00:00:00 2001
From: Robin Malfait
Date: Tue, 14 May 2024 04:51:41 +0200
Subject: [PATCH 002/152] Prevent simplifying `translate: none;` and `scale:
none;` (#703)
---
node/ast.d.ts | 78 ++++++++++++-----------
src/lib.rs | 4 +-
src/properties/transform.rs | 119 +++++++++++++++++++++++-------------
3 files changed, 120 insertions(+), 81 deletions(-)
diff --git a/node/ast.d.ts b/node/ast.d.ts
index bd6e0e98..11e92001 100644
--- a/node/ast.d.ts
+++ b/node/ast.d.ts
@@ -1,4 +1,4 @@
-/* tslint:disable */
+/* eslint-disable */
/**
* This file was automatically generated by json-schema-to-typescript.
* DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file,
@@ -5615,6 +5615,48 @@ export type Perspective =
type: "length";
value: Length;
};
+/**
+ * A value for the [translate](https://drafts.csswg.org/css-transforms-2/#propdef-translate) property.
+ */
+export type Translate =
+ | "None"
+ | {
+ XYZ: {
+ /**
+ * The x translation.
+ */
+ x: DimensionPercentageFor_LengthValue;
+ /**
+ * The y translation.
+ */
+ y: DimensionPercentageFor_LengthValue;
+ /**
+ * The z translation.
+ */
+ z: Length;
+ };
+ };
+/**
+ * A value for the [scale](https://drafts.csswg.org/css-transforms-2/#propdef-scale) property.
+ */
+export type Scale =
+ | "None"
+ | {
+ XYZ: {
+ /**
+ * Scale on the x axis.
+ */
+ x: NumberOrPercentage;
+ /**
+ * Scale on the y axis.
+ */
+ y: NumberOrPercentage;
+ /**
+ * Scale on the z axis.
+ */
+ z: NumberOrPercentage;
+ };
+ };
/**
* Defines how text case should be transformed in the [text-transform](https://www.w3.org/TR/2021/CRD-css-text-3-20210422/#text-transform-property) property.
*/
@@ -8519,23 +8561,6 @@ export interface Matrix3DForFloat {
m43: number;
m44: number;
}
-/**
- * A value for the [translate](https://drafts.csswg.org/css-transforms-2/#propdef-translate) property.
- */
-export interface Translate {
- /**
- * The x translation.
- */
- x: DimensionPercentageFor_LengthValue;
- /**
- * The y translation.
- */
- y: DimensionPercentageFor_LengthValue;
- /**
- * The z translation.
- */
- z: Length;
-}
/**
* A value for the [rotate](https://drafts.csswg.org/css-transforms-2/#propdef-rotate) property.
*/
@@ -8557,23 +8582,6 @@ export interface Rotate {
*/
z: number;
}
-/**
- * A value for the [scale](https://drafts.csswg.org/css-transforms-2/#propdef-scale) property.
- */
-export interface Scale {
- /**
- * Scale on the x axis.
- */
- x: NumberOrPercentage;
- /**
- * Scale on the y axis.
- */
- y: NumberOrPercentage;
- /**
- * Scale on the z axis.
- */
- z: NumberOrPercentage;
-}
/**
* A value for the [text-transform](https://www.w3.org/TR/2021/CRD-css-text-3-20210422/#text-transform-property) property.
*/
diff --git a/src/lib.rs b/src/lib.rs
index dd3b0fe8..b63e3ec8 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -11645,7 +11645,7 @@ mod tests {
minify_test(".foo { translate: 1px 0px 0px }", ".foo{translate:1px}");
minify_test(".foo { translate: 1px 2px 0px }", ".foo{translate:1px 2px}");
minify_test(".foo { translate: 1px 0px 2px }", ".foo{translate:1px 0 2px}");
- minify_test(".foo { translate: none }", ".foo{translate:0}");
+ minify_test(".foo { translate: none }", ".foo{translate:none}");
minify_test(".foo { rotate: 10deg }", ".foo{rotate:10deg}");
minify_test(".foo { rotate: z 10deg }", ".foo{rotate:10deg}");
minify_test(".foo { rotate: 0 0 1 10deg }", ".foo{rotate:10deg}");
@@ -11659,7 +11659,7 @@ mod tests {
minify_test(".foo { scale: 1 }", ".foo{scale:1}");
minify_test(".foo { scale: 1 1 }", ".foo{scale:1}");
minify_test(".foo { scale: 1 1 1 }", ".foo{scale:1}");
- minify_test(".foo { scale: none }", ".foo{scale:1}");
+ minify_test(".foo { scale: none }", ".foo{scale:none}");
minify_test(".foo { scale: 1 0 }", ".foo{scale:1 0}");
minify_test(".foo { scale: 1 0 1 }", ".foo{scale:1 0}");
minify_test(".foo { scale: 1 0 0 }", ".foo{scale:1 0 0}");
diff --git a/src/properties/transform.rs b/src/properties/transform.rs
index 75cd909a..ae390ae3 100644
--- a/src/properties/transform.rs
+++ b/src/properties/transform.rs
@@ -1457,23 +1457,25 @@ impl ToCss for Perspective {
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
-pub struct Translate {
- /// The x translation.
- pub x: LengthPercentage,
- /// The y translation.
- pub y: LengthPercentage,
- /// The z translation.
- pub z: Length,
+pub enum Translate {
+ /// The "none" keyword.
+ None,
+
+ /// The x, y, and z translations.
+ XYZ {
+ /// The x translation.
+ x: LengthPercentage,
+ /// The y translation.
+ y: LengthPercentage,
+ /// The z translation.
+ z: Length,
+ },
}
impl<'i> Parse<'i> for Translate {
fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> {
if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() {
- return Ok(Translate {
- x: LengthPercentage::zero(),
- y: LengthPercentage::zero(),
- z: Length::zero(),
- });
+ return Ok(Translate::None);
}
let x = LengthPercentage::parse(input)?;
@@ -1484,7 +1486,7 @@ impl<'i> Parse<'i> for Translate {
None
};
- Ok(Translate {
+ Ok(Translate::XYZ {
x,
y: y.unwrap_or(LengthPercentage::zero()),
z: z.unwrap_or(Length::zero()),
@@ -1497,15 +1499,23 @@ impl ToCss for Translate {
where
W: std::fmt::Write,
{
- self.x.to_css(dest)?;
- if !self.y.is_zero() || !self.z.is_zero() {
- dest.write_char(' ')?;
- self.y.to_css(dest)?;
- if !self.z.is_zero() {
- dest.write_char(' ')?;
- self.z.to_css(dest)?;
+ match self {
+ Translate::None => {
+ dest.write_str("none")?;
}
- }
+ Translate::XYZ { x, y, z } => {
+ x.to_css(dest)?;
+ if !y.is_zero() || !z.is_zero() {
+ dest.write_char(' ')?;
+ y.to_css(dest)?;
+ if !z.is_zero() {
+ dest.write_char(' ')?;
+ z.to_css(dest)?;
+ }
+ }
+ }
+ };
+
Ok(())
}
}
@@ -1513,7 +1523,12 @@ impl ToCss for Translate {
impl Translate {
/// Converts the translation to a transform function.
pub fn to_transform(&self) -> Transform {
- Transform::Translate3d(self.x.clone(), self.y.clone(), self.z.clone())
+ match self {
+ Translate::None => {
+ Transform::Translate3d(LengthPercentage::zero(), LengthPercentage::zero(), Length::zero())
+ }
+ Translate::XYZ { x, y, z } => Transform::Translate3d(x.clone(), y.clone(), z.clone()),
+ }
}
}
@@ -1610,23 +1625,25 @@ impl Rotate {
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
-pub struct Scale {
- /// Scale on the x axis.
- pub x: NumberOrPercentage,
- /// Scale on the y axis.
- pub y: NumberOrPercentage,
- /// Scale on the z axis.
- pub z: NumberOrPercentage,
+pub enum Scale {
+ /// The "none" keyword.
+ None,
+
+ /// Scale on the x, y, and z axis.
+ XYZ {
+ /// Scale on the x axis.
+ x: NumberOrPercentage,
+ /// Scale on the y axis.
+ y: NumberOrPercentage,
+ /// Scale on the z axis.
+ z: NumberOrPercentage,
+ },
}
impl<'i> Parse<'i> for Scale {
fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> {
if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() {
- return Ok(Scale {
- x: NumberOrPercentage::Number(1.0),
- y: NumberOrPercentage::Number(1.0),
- z: NumberOrPercentage::Number(1.0),
- });
+ return Ok(Scale::None);
}
let x = NumberOrPercentage::parse(input)?;
@@ -1637,7 +1654,7 @@ impl<'i> Parse<'i> for Scale {
None
};
- Ok(Scale {
+ Ok(Scale::XYZ {
x: x.clone(),
y: y.unwrap_or(x),
z: z.unwrap_or(NumberOrPercentage::Number(1.0)),
@@ -1650,14 +1667,21 @@ impl ToCss for Scale {
where
W: std::fmt::Write,
{
- self.x.to_css(dest)?;
- let zv: f32 = (&self.z).into();
- if self.y != self.x || zv != 1.0 {
- dest.write_char(' ')?;
- self.y.to_css(dest)?;
- if zv != 1.0 {
- dest.write_char(' ')?;
- self.z.to_css(dest)?;
+ match self {
+ Scale::None => {
+ dest.write_str("none")?;
+ }
+ Scale::XYZ { x, y, z } => {
+ x.to_css(dest)?;
+ let zv: f32 = z.into();
+ if y != x || zv != 1.0 {
+ dest.write_char(' ')?;
+ y.to_css(dest)?;
+ if zv != 1.0 {
+ dest.write_char(' ')?;
+ z.to_css(dest)?;
+ }
+ }
}
}
@@ -1668,7 +1692,14 @@ impl ToCss for Scale {
impl Scale {
/// Converts the scale to a transform function.
pub fn to_transform(&self) -> Transform {
- Transform::Scale3d(self.x.clone(), self.y.clone(), self.z.clone())
+ match self {
+ Scale::None => Transform::Scale3d(
+ NumberOrPercentage::Number(1.0),
+ NumberOrPercentage::Number(1.0),
+ NumberOrPercentage::Number(1.0),
+ ),
+ Scale::XYZ { x, y, z } => Transform::Scale3d(x.clone(), y.clone(), z.clone()),
+ }
}
}
From 029c88ee04e1599505488a8e4f4a374d45053236 Mon Sep 17 00:00:00 2001
From: Devon Govett
Date: Mon, 13 May 2024 21:18:59 -0700
Subject: [PATCH 003/152] fixup ast
---
Cargo.lock | 56 ++++++++++++++++-----------------
Cargo.toml | 4 +--
napi/Cargo.toml | 2 +-
node/ast.d.ts | 62 +++++++++++++++++--------------------
scripts/build-ast.js | 12 +++++++
selectors/Cargo.toml | 4 +--
src/properties/transform.rs | 7 +++--
7 files changed, 79 insertions(+), 68 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
index 7358e44c..1aba540b 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -403,7 +403,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331"
dependencies = [
"quote",
- "syn 2.0.39",
+ "syn 2.0.63",
]
[[package]]
@@ -413,7 +413,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad291aa74992b9b7a7e88c38acbbf6ad7e107f1d90ee8775b7bc1fc3394f485c"
dependencies = [
"quote",
- "syn 2.0.39",
+ "syn 2.0.63",
]
[[package]]
@@ -921,7 +921,7 @@ dependencies = [
"napi-derive-backend",
"proc-macro2",
"quote",
- "syn 2.0.39",
+ "syn 2.0.63",
]
[[package]]
@@ -936,7 +936,7 @@ dependencies = [
"quote",
"regex",
"semver",
- "syn 2.0.39",
+ "syn 2.0.63",
]
[[package]]
@@ -1122,7 +1122,7 @@ dependencies = [
"phf_shared 0.11.2",
"proc-macro2",
"quote",
- "syn 2.0.39",
+ "syn 2.0.63",
]
[[package]]
@@ -1223,9 +1223,9 @@ dependencies = [
[[package]]
name = "proc-macro2"
-version = "1.0.69"
+version = "1.0.82"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da"
+checksum = "8ad3d49ab951a01fbaafe34f2ec74122942fe18a3f9814c3268f1bb72042131b"
dependencies = [
"unicode-ident",
]
@@ -1252,9 +1252,9 @@ dependencies = [
[[package]]
name = "quote"
-version = "1.0.33"
+version = "1.0.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
+checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
dependencies = [
"proc-macro2",
]
@@ -1420,9 +1420,9 @@ dependencies = [
[[package]]
name = "schemars"
-version = "0.8.15"
+version = "0.8.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1f7b0ce13155372a76ee2e1c5ffba1fe61ede73fbea5630d61eee6fac4929c0c"
+checksum = "fc6e7ed6919cb46507fb01ff1654309219f62b4d603822501b0b80d42f6f21ef"
dependencies = [
"dyn-clone",
"schemars_derive",
@@ -1433,14 +1433,14 @@ dependencies = [
[[package]]
name = "schemars_derive"
-version = "0.8.15"
+version = "0.8.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e85e2a16b12bdb763244c69ab79363d71db2b4b918a2def53f80b02e0574b13c"
+checksum = "185f2b7aa7e02d418e453790dde16890256bbd2bcd04b7dc5348811052b53f49"
dependencies = [
"proc-macro2",
"quote",
"serde_derive_internals",
- "syn 1.0.109",
+ "syn 2.0.63",
]
[[package]]
@@ -1463,9 +1463,9 @@ checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca"
[[package]]
name = "serde"
-version = "1.0.192"
+version = "1.0.201"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bca2a08484b285dcb282d0f67b26cadc0df8b19f8c12502c13d966bf9482f001"
+checksum = "780f1cebed1629e4753a1a38a3c72d30b97ec044f0aef68cb26650a3c5cf363c"
dependencies = [
"serde_derive",
]
@@ -1491,24 +1491,24 @@ dependencies = [
[[package]]
name = "serde_derive"
-version = "1.0.192"
+version = "1.0.201"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d6c7207fbec9faa48073f3e3074cbe553af6ea512d7c21ba46e434e70ea9fbc1"
+checksum = "c5e405930b9796f1c00bee880d03fc7e0bb4b9a11afc776885ffe84320da2865"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.39",
+ "syn 2.0.63",
]
[[package]]
name = "serde_derive_internals"
-version = "0.26.0"
+version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "85bf8229e7920a9f636479437026331ce11aa132b4dde37d121944a44d6e5f3c"
+checksum = "330f01ce65a3a5fe59a60c82f3c9a024b573b8a6e875bd233fe5f934e71d54e3"
dependencies = [
"proc-macro2",
"quote",
- "syn 1.0.109",
+ "syn 2.0.63",
]
[[package]]
@@ -1614,9 +1614,9 @@ dependencies = [
[[package]]
name = "syn"
-version = "2.0.39"
+version = "2.0.63"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a"
+checksum = "bf5be731623ca1a1fb7d8be6f261a3be6d3e2337b8a1f97be944d020c8fcb704"
dependencies = [
"proc-macro2",
"quote",
@@ -1680,7 +1680,7 @@ checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.39",
+ "syn 2.0.63",
]
[[package]]
@@ -1793,7 +1793,7 @@ dependencies = [
"once_cell",
"proc-macro2",
"quote",
- "syn 2.0.39",
+ "syn 2.0.63",
"wasm-bindgen-shared",
]
@@ -1815,7 +1815,7 @@ checksum = "c5353b8dab669f5e10f5bd76df26a9360c748f054f862ff5f3f8aae0c7fb3907"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.39",
+ "syn 2.0.63",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
@@ -1964,5 +1964,5 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.39",
+ "syn 2.0.63",
]
diff --git a/Cargo.toml b/Cargo.toml
index 795b25ac..73ac94fe 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -48,7 +48,7 @@ into_owned = ["static-self", "static-self/smallvec", "parcel_selectors/into_owne
substitute_variables = ["visitor", "into_owned"]
[dependencies]
-serde = { version = "1.0.123", features = ["derive"], optional = true }
+serde = { version = "1.0.201", features = ["derive"], optional = true }
cssparser = "0.33.0"
cssparser-color = "0.1.0"
parcel_selectors = { version = "0.26.4", path = "./selectors" }
@@ -71,7 +71,7 @@ rayon = { version = "1.5.1", optional = true }
dashmap = { version = "5.0.0", optional = true }
serde_json = { version = "1.0.78", optional = true }
lightningcss-derive = { version = "=1.0.0-alpha.42", path = "./derive", optional = true }
-schemars = { version = "0.8.11", features = ["smallvec"], optional = true }
+schemars = { version = "0.8.19", features = ["smallvec"], optional = true }
static-self = { version = "0.1.0", path = "static-self", optional = true }
[target.'cfg(target_os = "macos")'.dependencies]
diff --git a/napi/Cargo.toml b/napi/Cargo.toml
index 3360a9cd..b296c3b6 100644
--- a/napi/Cargo.toml
+++ b/napi/Cargo.toml
@@ -13,7 +13,7 @@ visitor = ["lightningcss/visitor"]
bundler = ["dep:crossbeam-channel", "dep:rayon"]
[dependencies]
-serde = { version = "1.0.123", features = ["derive"] }
+serde = { version = "1.0.201", features = ["derive"] }
serde_bytes = "0.11.5"
cssparser = "0.33.0"
lightningcss = { version = "1.0.0-alpha.54", path = "../", features = ["nodejs", "serde"] }
diff --git a/node/ast.d.ts b/node/ast.d.ts
index 11e92001..6015ff44 100644
--- a/node/ast.d.ts
+++ b/node/ast.d.ts
@@ -1,4 +1,4 @@
-/* eslint-disable */
+/* tslint:disable */
/**
* This file was automatically generated by json-schema-to-typescript.
* DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file,
@@ -5619,44 +5619,40 @@ export type Perspective =
* A value for the [translate](https://drafts.csswg.org/css-transforms-2/#propdef-translate) property.
*/
export type Translate =
- | "None"
+ | "none"
| {
- XYZ: {
- /**
- * The x translation.
- */
- x: DimensionPercentageFor_LengthValue;
- /**
- * The y translation.
- */
- y: DimensionPercentageFor_LengthValue;
- /**
- * The z translation.
- */
- z: Length;
- };
- };
+ /**
+ * The x translation.
+ */
+ x: DimensionPercentageFor_LengthValue;
+ /**
+ * The y translation.
+ */
+ y: DimensionPercentageFor_LengthValue;
+ /**
+ * The z translation.
+ */
+ z: Length;
+ };
/**
* A value for the [scale](https://drafts.csswg.org/css-transforms-2/#propdef-scale) property.
*/
export type Scale =
- | "None"
+ | "none"
| {
- XYZ: {
- /**
- * Scale on the x axis.
- */
- x: NumberOrPercentage;
- /**
- * Scale on the y axis.
- */
- y: NumberOrPercentage;
- /**
- * Scale on the z axis.
- */
- z: NumberOrPercentage;
- };
- };
+ /**
+ * Scale on the x axis.
+ */
+ x: NumberOrPercentage;
+ /**
+ * Scale on the y axis.
+ */
+ y: NumberOrPercentage;
+ /**
+ * Scale on the z axis.
+ */
+ z: NumberOrPercentage;
+ };
/**
* Defines how text case should be transformed in the [text-transform](https://www.w3.org/TR/2021/CRD-css-text-3-20210422/#text-transform-property) property.
*/
diff --git a/scripts/build-ast.js b/scripts/build-ast.js
index 4e6bbebf..7b7fc020 100644
--- a/scripts/build-ast.js
+++ b/scripts/build-ast.js
@@ -55,6 +55,18 @@ compileFromFile('node/ast.json', {
if (path.node.name.startsWith('GenericBorderFor_LineStyleAnd_')) {
path.node.name = 'GenericBorderFor_LineStyle';
}
+ },
+ TSTypeAliasDeclaration(path) {
+ // Workaround for schemars not supporting untagged variants.
+ // https://github.com/GREsau/schemars/issues/222
+ if (
+ (path.node.id.name === 'Translate' || path.node.id.name === 'Scale') &&
+ path.node.typeAnnotation.type === 'TSUnionType' &&
+ path.node.typeAnnotation.types[1].type === 'TSTypeLiteral' &&
+ path.node.typeAnnotation.types[1].members[0].key.name === 'xyz'
+ ) {
+ path.get('typeAnnotation.types.1').replaceWith(path.node.typeAnnotation.types[1].members[0].typeAnnotation.typeAnnotation);
+ }
}
});
diff --git a/selectors/Cargo.toml b/selectors/Cargo.toml
index c3dd9402..481c468b 100644
--- a/selectors/Cargo.toml
+++ b/selectors/Cargo.toml
@@ -29,8 +29,8 @@ log = "0.4"
phf = "0.10"
precomputed-hash = "0.1"
smallvec = "1.0"
-serde = { version = "1.0.123", features = ["derive"], optional = true }
-schemars = { version = "0.8.11", features = ["smallvec"], optional = true }
+serde = { version = "1.0.201", features = ["derive"], optional = true }
+schemars = { version = "0.8.19", features = ["smallvec"], optional = true }
static-self = { version = "0.1.0", path = "../static-self", optional = true }
[build-dependencies]
diff --git a/src/properties/transform.rs b/src/properties/transform.rs
index ae390ae3..cbf6d60b 100644
--- a/src/properties/transform.rs
+++ b/src/properties/transform.rs
@@ -19,6 +19,7 @@ use crate::vendor_prefix::VendorPrefix;
use crate::visitor::Visit;
use cssparser::*;
use std::f32::consts::PI;
+use crate::serialization::ValueWrapper;
/// A value for the [transform](https://www.w3.org/TR/2019/CR-css-transforms-1-20190214/#propdef-transform) property.
#[derive(Debug, Clone, PartialEq, Default)]
@@ -1454,7 +1455,7 @@ impl ToCss for Perspective {
/// A value for the [translate](https://drafts.csswg.org/css-transforms-2/#propdef-translate) property.
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "visitor", derive(Visit))]
-#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
+#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(rename_all = "lowercase"))]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
pub enum Translate {
@@ -1462,6 +1463,7 @@ pub enum Translate {
None,
/// The x, y, and z translations.
+ #[cfg_attr(feature = "serde", serde(untagged))]
XYZ {
/// The x translation.
x: LengthPercentage,
@@ -1622,7 +1624,7 @@ impl Rotate {
/// A value for the [scale](https://drafts.csswg.org/css-transforms-2/#propdef-scale) property.
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "visitor", derive(Visit))]
-#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
+#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(rename_all = "lowercase"))]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
pub enum Scale {
@@ -1630,6 +1632,7 @@ pub enum Scale {
None,
/// Scale on the x, y, and z axis.
+ #[cfg_attr(feature = "serde", serde(untagged))]
XYZ {
/// Scale on the x axis.
x: NumberOrPercentage,
From 06ba62f6d1af563cd8ad31dba13844f434345720 Mon Sep 17 00:00:00 2001
From: magic-akari
Date: Tue, 14 May 2024 12:19:25 +0800
Subject: [PATCH 004/152] Fix box-shadow combination of `oklch` and
`currentColor` (#722)
---
src/lib.rs | 20 ++++++++++++++++++++
src/properties/box_shadow.rs | 4 ++--
2 files changed, 22 insertions(+), 2 deletions(-)
diff --git a/src/lib.rs b/src/lib.rs
index b63e3ec8..90b4ca33 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -27182,6 +27182,26 @@ mod tests {
..Browsers::default()
},
);
+ prefix_test(
+ r#"
+ .foo {
+ box-shadow:
+ oklch(100% 0 0deg / 50%) 0 0.63rem 0.94rem -0.19rem,
+ currentColor 0 0.44rem 0.8rem -0.58rem;
+ }
+ "#,
+ indoc! { r#"
+ .foo {
+ box-shadow: 0 .63rem .94rem -.19rem #ffffff80, 0 .44rem .8rem -.58rem;
+ box-shadow: 0 .63rem .94rem -.19rem lab(100% 0 0 / .5), 0 .44rem .8rem -.58rem;
+ }
+ "#},
+ Browsers {
+ chrome: Some(95 << 16),
+ ..Browsers::default()
+ },
+ );
+
prefix_test(
".foo { color: light-dark(var(--light), var(--dark)); }",
indoc! { r#"
diff --git a/src/properties/box_shadow.rs b/src/properties/box_shadow.rs
index 0d757caf..de60f4d3 100644
--- a/src/properties/box_shadow.rs
+++ b/src/properties/box_shadow.rs
@@ -208,7 +208,7 @@ impl BoxShadowHandler {
let rgb = box_shadows
.iter()
.map(|shadow| BoxShadow {
- color: shadow.color.to_rgb().unwrap(),
+ color: shadow.color.to_rgb().unwrap_or_else(|_| shadow.color.clone()),
..shadow.clone()
})
.collect();
@@ -236,7 +236,7 @@ impl BoxShadowHandler {
let lab = box_shadows
.iter()
.map(|shadow| BoxShadow {
- color: shadow.color.to_lab().unwrap(),
+ color: shadow.color.to_lab().unwrap_or_else(|_| shadow.color.clone()),
..shadow.clone()
})
.collect();
From 4a168e94a671492ba834ed7682dc41ecf66205be Mon Sep 17 00:00:00 2001
From: Devon Govett
Date: Mon, 13 May 2024 21:18:59 -0700
Subject: [PATCH 005/152] fixup ast
---
Cargo.lock | 56 ++++++++++++++++-----------------
Cargo.toml | 4 +--
napi/Cargo.toml | 2 +-
node/ast.d.ts | 62 +++++++++++++++++--------------------
scripts/build-ast.js | 12 +++++++
selectors/Cargo.toml | 4 +--
src/properties/transform.rs | 6 ++--
7 files changed, 78 insertions(+), 68 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
index 7358e44c..1aba540b 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -403,7 +403,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331"
dependencies = [
"quote",
- "syn 2.0.39",
+ "syn 2.0.63",
]
[[package]]
@@ -413,7 +413,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad291aa74992b9b7a7e88c38acbbf6ad7e107f1d90ee8775b7bc1fc3394f485c"
dependencies = [
"quote",
- "syn 2.0.39",
+ "syn 2.0.63",
]
[[package]]
@@ -921,7 +921,7 @@ dependencies = [
"napi-derive-backend",
"proc-macro2",
"quote",
- "syn 2.0.39",
+ "syn 2.0.63",
]
[[package]]
@@ -936,7 +936,7 @@ dependencies = [
"quote",
"regex",
"semver",
- "syn 2.0.39",
+ "syn 2.0.63",
]
[[package]]
@@ -1122,7 +1122,7 @@ dependencies = [
"phf_shared 0.11.2",
"proc-macro2",
"quote",
- "syn 2.0.39",
+ "syn 2.0.63",
]
[[package]]
@@ -1223,9 +1223,9 @@ dependencies = [
[[package]]
name = "proc-macro2"
-version = "1.0.69"
+version = "1.0.82"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da"
+checksum = "8ad3d49ab951a01fbaafe34f2ec74122942fe18a3f9814c3268f1bb72042131b"
dependencies = [
"unicode-ident",
]
@@ -1252,9 +1252,9 @@ dependencies = [
[[package]]
name = "quote"
-version = "1.0.33"
+version = "1.0.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
+checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
dependencies = [
"proc-macro2",
]
@@ -1420,9 +1420,9 @@ dependencies = [
[[package]]
name = "schemars"
-version = "0.8.15"
+version = "0.8.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1f7b0ce13155372a76ee2e1c5ffba1fe61ede73fbea5630d61eee6fac4929c0c"
+checksum = "fc6e7ed6919cb46507fb01ff1654309219f62b4d603822501b0b80d42f6f21ef"
dependencies = [
"dyn-clone",
"schemars_derive",
@@ -1433,14 +1433,14 @@ dependencies = [
[[package]]
name = "schemars_derive"
-version = "0.8.15"
+version = "0.8.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e85e2a16b12bdb763244c69ab79363d71db2b4b918a2def53f80b02e0574b13c"
+checksum = "185f2b7aa7e02d418e453790dde16890256bbd2bcd04b7dc5348811052b53f49"
dependencies = [
"proc-macro2",
"quote",
"serde_derive_internals",
- "syn 1.0.109",
+ "syn 2.0.63",
]
[[package]]
@@ -1463,9 +1463,9 @@ checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca"
[[package]]
name = "serde"
-version = "1.0.192"
+version = "1.0.201"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bca2a08484b285dcb282d0f67b26cadc0df8b19f8c12502c13d966bf9482f001"
+checksum = "780f1cebed1629e4753a1a38a3c72d30b97ec044f0aef68cb26650a3c5cf363c"
dependencies = [
"serde_derive",
]
@@ -1491,24 +1491,24 @@ dependencies = [
[[package]]
name = "serde_derive"
-version = "1.0.192"
+version = "1.0.201"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d6c7207fbec9faa48073f3e3074cbe553af6ea512d7c21ba46e434e70ea9fbc1"
+checksum = "c5e405930b9796f1c00bee880d03fc7e0bb4b9a11afc776885ffe84320da2865"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.39",
+ "syn 2.0.63",
]
[[package]]
name = "serde_derive_internals"
-version = "0.26.0"
+version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "85bf8229e7920a9f636479437026331ce11aa132b4dde37d121944a44d6e5f3c"
+checksum = "330f01ce65a3a5fe59a60c82f3c9a024b573b8a6e875bd233fe5f934e71d54e3"
dependencies = [
"proc-macro2",
"quote",
- "syn 1.0.109",
+ "syn 2.0.63",
]
[[package]]
@@ -1614,9 +1614,9 @@ dependencies = [
[[package]]
name = "syn"
-version = "2.0.39"
+version = "2.0.63"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a"
+checksum = "bf5be731623ca1a1fb7d8be6f261a3be6d3e2337b8a1f97be944d020c8fcb704"
dependencies = [
"proc-macro2",
"quote",
@@ -1680,7 +1680,7 @@ checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.39",
+ "syn 2.0.63",
]
[[package]]
@@ -1793,7 +1793,7 @@ dependencies = [
"once_cell",
"proc-macro2",
"quote",
- "syn 2.0.39",
+ "syn 2.0.63",
"wasm-bindgen-shared",
]
@@ -1815,7 +1815,7 @@ checksum = "c5353b8dab669f5e10f5bd76df26a9360c748f054f862ff5f3f8aae0c7fb3907"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.39",
+ "syn 2.0.63",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
@@ -1964,5 +1964,5 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.39",
+ "syn 2.0.63",
]
diff --git a/Cargo.toml b/Cargo.toml
index 795b25ac..73ac94fe 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -48,7 +48,7 @@ into_owned = ["static-self", "static-self/smallvec", "parcel_selectors/into_owne
substitute_variables = ["visitor", "into_owned"]
[dependencies]
-serde = { version = "1.0.123", features = ["derive"], optional = true }
+serde = { version = "1.0.201", features = ["derive"], optional = true }
cssparser = "0.33.0"
cssparser-color = "0.1.0"
parcel_selectors = { version = "0.26.4", path = "./selectors" }
@@ -71,7 +71,7 @@ rayon = { version = "1.5.1", optional = true }
dashmap = { version = "5.0.0", optional = true }
serde_json = { version = "1.0.78", optional = true }
lightningcss-derive = { version = "=1.0.0-alpha.42", path = "./derive", optional = true }
-schemars = { version = "0.8.11", features = ["smallvec"], optional = true }
+schemars = { version = "0.8.19", features = ["smallvec"], optional = true }
static-self = { version = "0.1.0", path = "static-self", optional = true }
[target.'cfg(target_os = "macos")'.dependencies]
diff --git a/napi/Cargo.toml b/napi/Cargo.toml
index 3360a9cd..b296c3b6 100644
--- a/napi/Cargo.toml
+++ b/napi/Cargo.toml
@@ -13,7 +13,7 @@ visitor = ["lightningcss/visitor"]
bundler = ["dep:crossbeam-channel", "dep:rayon"]
[dependencies]
-serde = { version = "1.0.123", features = ["derive"] }
+serde = { version = "1.0.201", features = ["derive"] }
serde_bytes = "0.11.5"
cssparser = "0.33.0"
lightningcss = { version = "1.0.0-alpha.54", path = "../", features = ["nodejs", "serde"] }
diff --git a/node/ast.d.ts b/node/ast.d.ts
index 11e92001..6015ff44 100644
--- a/node/ast.d.ts
+++ b/node/ast.d.ts
@@ -1,4 +1,4 @@
-/* eslint-disable */
+/* tslint:disable */
/**
* This file was automatically generated by json-schema-to-typescript.
* DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file,
@@ -5619,44 +5619,40 @@ export type Perspective =
* A value for the [translate](https://drafts.csswg.org/css-transforms-2/#propdef-translate) property.
*/
export type Translate =
- | "None"
+ | "none"
| {
- XYZ: {
- /**
- * The x translation.
- */
- x: DimensionPercentageFor_LengthValue;
- /**
- * The y translation.
- */
- y: DimensionPercentageFor_LengthValue;
- /**
- * The z translation.
- */
- z: Length;
- };
- };
+ /**
+ * The x translation.
+ */
+ x: DimensionPercentageFor_LengthValue;
+ /**
+ * The y translation.
+ */
+ y: DimensionPercentageFor_LengthValue;
+ /**
+ * The z translation.
+ */
+ z: Length;
+ };
/**
* A value for the [scale](https://drafts.csswg.org/css-transforms-2/#propdef-scale) property.
*/
export type Scale =
- | "None"
+ | "none"
| {
- XYZ: {
- /**
- * Scale on the x axis.
- */
- x: NumberOrPercentage;
- /**
- * Scale on the y axis.
- */
- y: NumberOrPercentage;
- /**
- * Scale on the z axis.
- */
- z: NumberOrPercentage;
- };
- };
+ /**
+ * Scale on the x axis.
+ */
+ x: NumberOrPercentage;
+ /**
+ * Scale on the y axis.
+ */
+ y: NumberOrPercentage;
+ /**
+ * Scale on the z axis.
+ */
+ z: NumberOrPercentage;
+ };
/**
* Defines how text case should be transformed in the [text-transform](https://www.w3.org/TR/2021/CRD-css-text-3-20210422/#text-transform-property) property.
*/
diff --git a/scripts/build-ast.js b/scripts/build-ast.js
index 4e6bbebf..7b7fc020 100644
--- a/scripts/build-ast.js
+++ b/scripts/build-ast.js
@@ -55,6 +55,18 @@ compileFromFile('node/ast.json', {
if (path.node.name.startsWith('GenericBorderFor_LineStyleAnd_')) {
path.node.name = 'GenericBorderFor_LineStyle';
}
+ },
+ TSTypeAliasDeclaration(path) {
+ // Workaround for schemars not supporting untagged variants.
+ // https://github.com/GREsau/schemars/issues/222
+ if (
+ (path.node.id.name === 'Translate' || path.node.id.name === 'Scale') &&
+ path.node.typeAnnotation.type === 'TSUnionType' &&
+ path.node.typeAnnotation.types[1].type === 'TSTypeLiteral' &&
+ path.node.typeAnnotation.types[1].members[0].key.name === 'xyz'
+ ) {
+ path.get('typeAnnotation.types.1').replaceWith(path.node.typeAnnotation.types[1].members[0].typeAnnotation.typeAnnotation);
+ }
}
});
diff --git a/selectors/Cargo.toml b/selectors/Cargo.toml
index c3dd9402..481c468b 100644
--- a/selectors/Cargo.toml
+++ b/selectors/Cargo.toml
@@ -29,8 +29,8 @@ log = "0.4"
phf = "0.10"
precomputed-hash = "0.1"
smallvec = "1.0"
-serde = { version = "1.0.123", features = ["derive"], optional = true }
-schemars = { version = "0.8.11", features = ["smallvec"], optional = true }
+serde = { version = "1.0.201", features = ["derive"], optional = true }
+schemars = { version = "0.8.19", features = ["smallvec"], optional = true }
static-self = { version = "0.1.0", path = "../static-self", optional = true }
[build-dependencies]
diff --git a/src/properties/transform.rs b/src/properties/transform.rs
index ae390ae3..8ff7b968 100644
--- a/src/properties/transform.rs
+++ b/src/properties/transform.rs
@@ -1454,7 +1454,7 @@ impl ToCss for Perspective {
/// A value for the [translate](https://drafts.csswg.org/css-transforms-2/#propdef-translate) property.
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "visitor", derive(Visit))]
-#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
+#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(rename_all = "lowercase"))]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
pub enum Translate {
@@ -1462,6 +1462,7 @@ pub enum Translate {
None,
/// The x, y, and z translations.
+ #[cfg_attr(feature = "serde", serde(untagged))]
XYZ {
/// The x translation.
x: LengthPercentage,
@@ -1622,7 +1623,7 @@ impl Rotate {
/// A value for the [scale](https://drafts.csswg.org/css-transforms-2/#propdef-scale) property.
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "visitor", derive(Visit))]
-#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
+#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(rename_all = "lowercase"))]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
pub enum Scale {
@@ -1630,6 +1631,7 @@ pub enum Scale {
None,
/// Scale on the x, y, and z axis.
+ #[cfg_attr(feature = "serde", serde(untagged))]
XYZ {
/// Scale on the x axis.
x: NumberOrPercentage,
From 445def9a77f89aa612fecb2f776261fc2c9d8f66 Mon Sep 17 00:00:00 2001
From: Devon Govett
Date: Mon, 13 May 2024 22:27:30 -0700
Subject: [PATCH 006/152] fix minifying predefined color spaces
fixes #727
---
src/lib.rs | 31 +++++++++++++++++--------------
src/properties/transform.rs | 12 ++++++++++--
src/values/color.rs | 30 +++++++++---------------------
3 files changed, 36 insertions(+), 37 deletions(-)
diff --git a/src/lib.rs b/src/lib.rs
index 90b4ca33..7d8aff33 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -16040,11 +16040,11 @@ mod tests {
);
minify_test(
".foo { color: color(display-p3 1 0.5 0); }",
- ".foo{color:color(display-p3 1 .5)}",
+ ".foo{color:color(display-p3 1 .5 0)}",
);
minify_test(
".foo { color: color(display-p3 100% 50% 0%); }",
- ".foo{color:color(display-p3 1 .5)}",
+ ".foo{color:color(display-p3 1 .5 0)}",
);
minify_test(
".foo { color: color(xyz-d50 0.2005 0.14089 0.4472); }",
@@ -16070,24 +16070,27 @@ mod tests {
".foo { color: color(xyz 20.05% 14.089% 44.72%); }",
".foo{color:color(xyz .2005 .14089 .4472)}",
);
- minify_test(".foo { color: color(xyz 0.2005 0 0); }", ".foo{color:color(xyz .2005)}");
- minify_test(".foo { color: color(xyz 0 0 0); }", ".foo{color:color(xyz)}");
- minify_test(".foo { color: color(xyz 0 1 0); }", ".foo{color:color(xyz 0 1)}");
- minify_test(".foo { color: color(xyz 0 1); }", ".foo{color:color(xyz 0 1)}");
- minify_test(".foo { color: color(xyz 1); }", ".foo{color:color(xyz 1)}");
- minify_test(".foo { color: color(xyz); }", ".foo{color:color(xyz)}");
+ minify_test(
+ ".foo { color: color(xyz 0.2005 0 0); }",
+ ".foo{color:color(xyz .2005 0 0)}",
+ );
+ minify_test(".foo { color: color(xyz 0 0 0); }", ".foo{color:color(xyz 0 0 0)}");
+ minify_test(".foo { color: color(xyz 0 1 0); }", ".foo{color:color(xyz 0 1 0)}");
minify_test(
".foo { color: color(xyz 0 1 0 / 20%); }",
- ".foo{color:color(xyz 0 1/.2)}",
+ ".foo{color:color(xyz 0 1 0/.2)}",
+ );
+ minify_test(
+ ".foo { color: color(xyz 0 0 0 / 20%); }",
+ ".foo{color:color(xyz 0 0 0/.2)}",
);
- minify_test(".foo { color: color(xyz / 20%); }", ".foo{color:color(xyz/.2)}");
minify_test(
".foo { color: color(display-p3 100% 50% 0 / 20%); }",
- ".foo{color:color(display-p3 1 .5/.2)}",
+ ".foo{color:color(display-p3 1 .5 0/.2)}",
);
minify_test(
- ".foo { color: color(display-p3 100% / 20%); }",
- ".foo{color:color(display-p3 1/.2)}",
+ ".foo { color: color(display-p3 100% 0 0 / 20%); }",
+ ".foo{color:color(display-p3 1 0 0/.2)}",
);
minify_test(".foo { color: hsl(none none none) }", ".foo{color:#000}");
minify_test(".foo { color: hwb(none none none) }", ".foo{color:red}");
@@ -20034,7 +20037,7 @@ mod tests {
".foo {{ color: color-mix(in {0}, color({0} -2 -3 -4 / -5), color({0} -4 -6 -8 / -10)) }}",
color_space
),
- &format!(".foo{{color:color({}/0)}}", result_color_space),
+ &format!(".foo{{color:color({} 0 0 0/0)}}", result_color_space),
);
minify_test(
diff --git a/src/properties/transform.rs b/src/properties/transform.rs
index 8ff7b968..d58a08fe 100644
--- a/src/properties/transform.rs
+++ b/src/properties/transform.rs
@@ -1454,7 +1454,11 @@ impl ToCss for Perspective {
/// A value for the [translate](https://drafts.csswg.org/css-transforms-2/#propdef-translate) property.
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "visitor", derive(Visit))]
-#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(rename_all = "lowercase"))]
+#[cfg_attr(
+ feature = "serde",
+ derive(serde::Serialize, serde::Deserialize),
+ serde(rename_all = "lowercase")
+)]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
pub enum Translate {
@@ -1623,7 +1627,11 @@ impl Rotate {
/// A value for the [scale](https://drafts.csswg.org/css-transforms-2/#propdef-scale) property.
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "visitor", derive(Visit))]
-#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(rename_all = "lowercase"))]
+#[cfg_attr(
+ feature = "serde",
+ derive(serde::Serialize, serde::Deserialize),
+ serde(rename_all = "lowercase")
+)]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
pub enum Scale {
diff --git a/src/values/color.rs b/src/values/color.rs
index 524c43c7..efa2bd26 100644
--- a/src/values/color.rs
+++ b/src/values/color.rs
@@ -1169,15 +1169,9 @@ fn parse_predefined_relative<'i, 't>(
// Out of gamut values should not be clamped, i.e. values < 0 or > 1 should be preserved.
// The browser will gamut-map the color for the target device that it is rendered on.
- let a = input
- .try_parse(|input| parse_number_or_percentage(input, parser))
- .unwrap_or(0.0);
- let b = input
- .try_parse(|input| parse_number_or_percentage(input, parser))
- .unwrap_or(0.0);
- let c = input
- .try_parse(|input| parse_number_or_percentage(input, parser))
- .unwrap_or(0.0);
+ let a = input.try_parse(|input| parse_number_or_percentage(input, parser))?;
+ let b = input.try_parse(|input| parse_number_or_percentage(input, parser))?;
+ let c = input.try_parse(|input| parse_number_or_percentage(input, parser))?;
let alpha = parse_alpha(input, parser)?;
let res = match_ignore_ascii_case! { &*&colorspace,
@@ -1429,18 +1423,12 @@ where
dest.write_str("color(")?;
dest.write_str(name)?;
- if !dest.minify || a != 0.0 || b != 0.0 || c != 0.0 {
- dest.write_char(' ')?;
- write_component(a, dest)?;
- if !dest.minify || b != 0.0 || c != 0.0 {
- dest.write_char(' ')?;
- write_component(b, dest)?;
- if !dest.minify || c != 0.0 {
- dest.write_char(' ')?;
- write_component(c, dest)?;
- }
- }
- }
+ dest.write_char(' ')?;
+ write_component(a, dest)?;
+ dest.write_char(' ')?;
+ write_component(b, dest)?;
+ dest.write_char(' ')?;
+ write_component(c, dest)?;
if alpha.is_nan() || (alpha - 1.0).abs() > f32::EPSILON {
dest.delim('/', true)?;
From 83839a98dbe0000acbdf039d968f33e1e8c50277 Mon Sep 17 00:00:00 2001
From: Tim Neutkens
Date: Wed, 15 May 2024 07:25:52 +0200
Subject: [PATCH 007/152] Add granular CSS Modules options (#739)
---
Cargo.lock | 23 ++++++
Cargo.toml | 1 +
napi/src/lib.rs | 9 +++
src/css_modules.rs | 23 +++++-
src/lib.rs | 132 +++++++++++++++++++++++++++++++++++
src/printer.rs | 44 ++++++------
src/properties/animation.rs | 19 +++--
src/properties/grid.rs | 27 +++----
src/rules/keyframes.rs | 7 +-
src/selector.rs | 6 +-
src/values/ident.rs | 21 +++++-
website/pages/css-modules.md | 51 +++++++++-----
12 files changed, 299 insertions(+), 64 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
index 1aba540b..eed17d06 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -444,6 +444,12 @@ dependencies = [
"matches",
]
+[[package]]
+name = "diff"
+version = "0.1.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8"
+
[[package]]
name = "difflib"
version = "0.4.0"
@@ -786,6 +792,7 @@ dependencies = [
"paste",
"pathdiff",
"predicates 2.1.5",
+ "pretty_assertions",
"rayon",
"schemars",
"serde",
@@ -1197,6 +1204,16 @@ dependencies = [
"termtree",
]
+[[package]]
+name = "pretty_assertions"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af7cee1a6c8a5b9208b3cb1061f10c0cb689087b3d8ce85fb9d2dd7a29b6ba66"
+dependencies = [
+ "diff",
+ "yansi",
+]
+
[[package]]
name = "proc-macro-error"
version = "1.0.4"
@@ -1947,6 +1964,12 @@ dependencies = [
"tap",
]
+[[package]]
+name = "yansi"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec"
+
[[package]]
name = "zerocopy"
version = "0.7.32"
diff --git a/Cargo.toml b/Cargo.toml
index 73ac94fe..a298e0b1 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -86,6 +86,7 @@ assert_cmd = "2.0"
assert_fs = "1.0"
predicates = "2.1"
serde_json = "1"
+pretty_assertions = "1.4.0"
[[test]]
name = "cli_integration_tests"
diff --git a/napi/src/lib.rs b/napi/src/lib.rs
index cc3ce78e..9edfb1ce 100644
--- a/napi/src/lib.rs
+++ b/napi/src/lib.rs
@@ -605,6 +605,9 @@ enum CssModulesOption {
struct CssModulesConfig {
pattern: Option,
dashed_idents: Option,
+ animation: Option,
+ grid: Option,
+ custom_idents: Option,
}
#[cfg(feature = "bundler")]
@@ -713,6 +716,9 @@ fn compile<'i>(
Default::default()
},
dashed_idents: c.dashed_idents.unwrap_or_default(),
+ animation: c.animation.unwrap_or(true),
+ grid: c.grid.unwrap_or(true),
+ custom_idents: c.custom_idents.unwrap_or(true),
}),
}
} else {
@@ -840,6 +846,9 @@ fn compile_bundle<
Default::default()
},
dashed_idents: c.dashed_idents.unwrap_or_default(),
+ animation: c.animation.unwrap_or(true),
+ grid: c.grid.unwrap_or(true),
+ custom_idents: c.custom_idents.unwrap_or(true),
}),
}
} else {
diff --git a/src/css_modules.rs b/src/css_modules.rs
index b794bb43..cfe33d71 100644
--- a/src/css_modules.rs
+++ b/src/css_modules.rs
@@ -25,13 +25,34 @@ use std::hash::{Hash, Hasher};
use std::path::Path;
/// Configuration for CSS modules.
-#[derive(Default, Clone, Debug)]
+#[derive(Clone, Debug)]
pub struct Config<'i> {
/// The name pattern to use when renaming class names and other identifiers.
/// Default is `[hash]_[local]`.
pub pattern: Pattern<'i>,
/// Whether to rename dashed identifiers, e.g. custom properties.
pub dashed_idents: bool,
+ /// Whether to scope animation names.
+ /// Default is `true`.
+ pub animation: bool,
+ /// Whether to scope grid names.
+ /// Default is `true`.
+ pub grid: bool,
+ /// Whether to scope custom identifiers
+ /// Default is `true`.
+ pub custom_idents: bool,
+}
+
+impl<'i> Default for Config<'i> {
+ fn default() -> Self {
+ Config {
+ pattern: Default::default(),
+ dashed_idents: Default::default(),
+ animation: true,
+ grid: true,
+ custom_idents: true,
+ }
+ }
}
/// A CSS modules class name pattern.
diff --git a/src/lib.rs b/src/lib.rs
index 7d8aff33..d0ea1982 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -64,6 +64,7 @@ mod tests {
use crate::vendor_prefix::VendorPrefix;
use cssparser::SourceLocation;
use indoc::indoc;
+ use pretty_assertions::assert_eq;
use std::collections::HashMap;
fn test(source: &str, expected: &str) {
@@ -23053,6 +23054,97 @@ mod tests {
Default::default(),
);
+ css_modules_test(
+ r#"
+ .foo {
+ color: red;
+ }
+
+ #id {
+ animation: 2s test;
+ }
+
+ @keyframes test {
+ from { color: red }
+ to { color: yellow }
+ }
+ "#,
+ indoc! {r#"
+ .EgL3uq_foo {
+ color: red;
+ }
+
+ #EgL3uq_id {
+ animation: 2s test;
+ }
+
+ @keyframes test {
+ from {
+ color: red;
+ }
+
+ to {
+ color: #ff0;
+ }
+ }
+ "#},
+ map! {
+ "foo" => "EgL3uq_foo",
+ "id" => "EgL3uq_id"
+ },
+ HashMap::new(),
+ crate::css_modules::Config {
+ animation: false,
+ // custom_idents: false,
+ ..Default::default()
+ },
+ );
+
+ css_modules_test(
+ r#"
+ @counter-style circles {
+ symbols: Ⓐ Ⓑ Ⓒ;
+ }
+
+ ul {
+ list-style: circles;
+ }
+
+ ol {
+ list-style-type: none;
+ }
+
+ li {
+ list-style-type: disc;
+ }
+ "#,
+ indoc! {r#"
+ @counter-style circles {
+ symbols: Ⓐ Ⓑ Ⓒ;
+ }
+
+ ul {
+ list-style: circles;
+ }
+
+ ol {
+ list-style-type: none;
+ }
+
+ li {
+ list-style-type: disc;
+ }
+ "#},
+ map! {
+ "circles" => "EgL3uq_circles" referenced: true
+ },
+ HashMap::new(),
+ crate::css_modules::Config {
+ custom_idents: false,
+ ..Default::default()
+ },
+ );
+
#[cfg(feature = "grid")]
css_modules_test(
r#"
@@ -23135,6 +23227,46 @@ mod tests {
Default::default(),
);
+ #[cfg(feature = "grid")]
+ css_modules_test(
+ r#"
+ .grid {
+ grid-template-areas: "foo";
+ }
+
+ .foo {
+ grid-area: foo;
+ }
+
+ .bar {
+ grid-column-start: foo-start;
+ }
+ "#,
+ indoc! {r#"
+ .EgL3uq_grid {
+ grid-template-areas: "foo";
+ }
+
+ .EgL3uq_foo {
+ grid-area: foo;
+ }
+
+ .EgL3uq_bar {
+ grid-column-start: foo-start;
+ }
+ "#},
+ map! {
+ "foo" => "EgL3uq_foo",
+ "grid" => "EgL3uq_grid",
+ "bar" => "EgL3uq_bar"
+ },
+ HashMap::new(),
+ crate::css_modules::Config {
+ grid: false,
+ ..Default::default()
+ },
+ );
+
css_modules_test(
r#"
test {
diff --git a/src/printer.rs b/src/printer.rs
index 12fe09e3..d8c08e2d 100644
--- a/src/printer.rs
+++ b/src/printer.rs
@@ -267,30 +267,32 @@ impl<'a, 'b, 'c, W: std::fmt::Write + Sized> Printer<'a, 'b, 'c, W> {
/// Writes a CSS identifier to the underlying destination, escaping it
/// as appropriate. If the `css_modules` option was enabled, then a hash
/// is added, and the mapping is added to the CSS module.
- pub fn write_ident(&mut self, ident: &str) -> Result<(), PrinterError> {
- if let Some(css_module) = &mut self.css_module {
- let dest = &mut self.dest;
- let mut first = true;
- css_module.config.pattern.write(
- &css_module.hashes[self.loc.source_index as usize],
- &css_module.sources[self.loc.source_index as usize],
- ident,
- |s| {
- self.col += s.len() as u32;
- if first {
- first = false;
- serialize_identifier(s, dest)
- } else {
- serialize_name(s, dest)
- }
- },
- )?;
+ pub fn write_ident(&mut self, ident: &str, handle_css_module: bool) -> Result<(), PrinterError> {
+ if handle_css_module {
+ if let Some(css_module) = &mut self.css_module {
+ let dest = &mut self.dest;
+ let mut first = true;
+ css_module.config.pattern.write(
+ &css_module.hashes[self.loc.source_index as usize],
+ &css_module.sources[self.loc.source_index as usize],
+ ident,
+ |s| {
+ self.col += s.len() as u32;
+ if first {
+ first = false;
+ serialize_identifier(s, dest)
+ } else {
+ serialize_name(s, dest)
+ }
+ },
+ )?;
- css_module.add_local(&ident, &ident, self.loc.source_index);
- } else {
- serialize_identifier(ident, self)?;
+ css_module.add_local(&ident, &ident, self.loc.source_index);
+ return Ok(());
+ }
}
+ serialize_identifier(ident, self)?;
Ok(())
}
diff --git a/src/properties/animation.rs b/src/properties/animation.rs
index c995c222..d5d73f83 100644
--- a/src/properties/animation.rs
+++ b/src/properties/animation.rs
@@ -58,17 +58,24 @@ impl<'i> ToCss for AnimationName<'i> {
where
W: std::fmt::Write,
{
+ let css_module_animation_enabled =
+ dest.css_module.as_ref().map_or(false, |css_module| css_module.config.animation);
+
match self {
AnimationName::None => dest.write_str("none"),
AnimationName::Ident(s) => {
- if let Some(css_module) = &mut dest.css_module {
- css_module.reference(&s.0, dest.loc.source_index)
+ if css_module_animation_enabled {
+ if let Some(css_module) = &mut dest.css_module {
+ css_module.reference(&s.0, dest.loc.source_index)
+ }
}
- s.to_css(dest)
+ s.to_css_with_options(dest, css_module_animation_enabled)
}
AnimationName::String(s) => {
- if let Some(css_module) = &mut dest.css_module {
- css_module.reference(&s, dest.loc.source_index)
+ if css_module_animation_enabled {
+ if let Some(css_module) = &mut dest.css_module {
+ css_module.reference(&s, dest.loc.source_index)
+ }
}
// CSS-wide keywords and `none` cannot remove quotes.
@@ -78,7 +85,7 @@ impl<'i> ToCss for AnimationName<'i> {
Ok(())
},
_ => {
- dest.write_ident(s.as_ref())
+ dest.write_ident(s.as_ref(), css_module_animation_enabled)
}
}
}
diff --git a/src/properties/grid.rs b/src/properties/grid.rs
index fa0e5a64..912f563d 100644
--- a/src/properties/grid.rs
+++ b/src/properties/grid.rs
@@ -430,21 +430,24 @@ fn write_ident(name: &str, dest: &mut Printer) -> Result<(), PrinterError>
where
W: std::fmt::Write,
{
- if let Some(css_module) = &mut dest.css_module {
- if let Some(last) = css_module.config.pattern.segments.last() {
- if !matches!(last, crate::css_modules::Segment::Local) {
- return Err(Error {
- kind: PrinterErrorKind::InvalidCssModulesPatternInGrid,
- loc: Some(ErrorLocation {
- filename: dest.filename().into(),
- line: dest.loc.line,
- column: dest.loc.column,
- }),
- });
+ let css_module_grid_enabled = dest.css_module.as_ref().map_or(false, |css_module| css_module.config.grid);
+ if css_module_grid_enabled {
+ if let Some(css_module) = &mut dest.css_module {
+ if let Some(last) = css_module.config.pattern.segments.last() {
+ if !matches!(last, crate::css_modules::Segment::Local) {
+ return Err(Error {
+ kind: PrinterErrorKind::InvalidCssModulesPatternInGrid,
+ loc: Some(ErrorLocation {
+ filename: dest.filename().into(),
+ line: dest.loc.line,
+ column: dest.loc.column,
+ }),
+ });
+ }
}
}
}
- dest.write_ident(name)?;
+ dest.write_ident(name, css_module_grid_enabled)?;
Ok(())
}
diff --git a/src/rules/keyframes.rs b/src/rules/keyframes.rs
index a9489f30..511a3172 100644
--- a/src/rules/keyframes.rs
+++ b/src/rules/keyframes.rs
@@ -92,9 +92,12 @@ impl<'i> ToCss for KeyframesName<'i> {
where
W: std::fmt::Write,
{
+ let css_module_animation_enabled =
+ dest.css_module.as_ref().map_or(false, |css_module| css_module.config.animation);
+
match self {
KeyframesName::Ident(ident) => {
- dest.write_ident(ident.0.as_ref())?;
+ dest.write_ident(ident.0.as_ref(), css_module_animation_enabled)?;
}
KeyframesName::Custom(s) => {
// CSS-wide keywords and `none` cannot remove quotes.
@@ -103,7 +106,7 @@ impl<'i> ToCss for KeyframesName<'i> {
serialize_string(&s, dest)?;
},
_ => {
- dest.write_ident(s.as_ref())?;
+ dest.write_ident(s.as_ref(), css_module_animation_enabled)?;
}
}
}
diff --git a/src/selector.rs b/src/selector.rs
index 9cc04848..62550198 100644
--- a/src/selector.rs
+++ b/src/selector.rs
@@ -672,7 +672,7 @@ where
if let Some(class) = class {
dest.write_char('.')?;
- dest.write_ident(class)
+ dest.write_ident(class, true)
} else {
dest.write_str($s)
}
@@ -1551,11 +1551,11 @@ where
Component::Nesting => serialize_nesting(dest, context, false),
Component::Class(ref class) => {
dest.write_char('.')?;
- dest.write_ident(&class.0)
+ dest.write_ident(&class.0, true)
}
Component::ID(ref id) => {
dest.write_char('#')?;
- dest.write_ident(&id.0)
+ dest.write_ident(&id.0, true)
}
Component::Host(selector) => {
dest.write_str(":host")?;
diff --git a/src/values/ident.rs b/src/values/ident.rs
index 9d244965..173d3a08 100644
--- a/src/values/ident.rs
+++ b/src/values/ident.rs
@@ -49,7 +49,26 @@ impl<'i> ToCss for CustomIdent<'i> {
where
W: std::fmt::Write,
{
- dest.write_ident(&self.0)
+ self.to_css_with_options(dest, true)
+ }
+}
+
+impl<'i> CustomIdent<'i> {
+ /// Write the custom ident to CSS.
+ pub(crate) fn to_css_with_options(
+ &self,
+ dest: &mut Printer,
+ enabled_css_modules: bool,
+ ) -> Result<(), PrinterError>
+ where
+ W: std::fmt::Write,
+ {
+ let css_module_custom_idents_enabled = enabled_css_modules
+ && dest
+ .css_module
+ .as_mut()
+ .map_or(false, |css_module| css_module.config.custom_idents);
+ dest.write_ident(&self.0, css_module_custom_idents_enabled)
}
}
diff --git a/website/pages/css-modules.md b/website/pages/css-modules.md
index d66016c3..732f83cf 100644
--- a/website/pages/css-modules.md
+++ b/website/pages/css-modules.md
@@ -13,16 +13,16 @@ CSS modules treat the classes defined in each file as unique. Each class name or
To enable CSS modules, provide the `cssModules` option when calling the Lightning CSS API. When using the CLI, enable the `--css-modules` flag.
```js
-import { transform } from 'lightningcss';
+import {transform} from 'lightningcss';
-let { code, map, exports } = transform({
+let {code, map, exports} = transform({
// ...
cssModules: true,
code: Buffer.from(`
.logo {
background: skyblue;
}
- `)
+ `),
});
```
@@ -89,7 +89,7 @@ You can also reference class names defined in a different CSS file using the `fr
```css
.logo {
- composes: bg-indigo from "./colors.module.css";
+ composes: bg-indigo from './colors.module.css';
}
```
@@ -150,10 +150,10 @@ compiles to:
By default, class names, id selectors, and the names of `@keyframes`, `@counter-style`, and CSS grid lines and areas are scoped to the module they are defined in. Scoping for CSS variables and other [``](https://www.w3.org/TR/css-values-4/#dashed-idents) names can also be enabled using the `dashedIdents` option when calling the Lightning CSS API. When using the CLI, enable the `--css-modules-dashed-idents` flag.
```js
-let { code, map, exports } = transform({
+let {code, map, exports} = transform({
// ...
cssModules: {
- dashedIdents: true
+ dashedIdents: true,
},
});
```
@@ -186,7 +186,7 @@ You can also reference variables defined in other files using the `from` keyword
```css
.button {
- background: var(--accent-color from "./vars.module.css");
+ background: var(--accent-color from './vars.module.css');
}
```
@@ -207,19 +207,19 @@ By default, Lightning CSS prepends the hash of the filename to each class name a
A pattern is a string with placeholders that will be filled in by Lightning CSS. This allows you to add custom prefixes or adjust the naming convention for scoped classes.
```js
-let { code, map, exports } = transform({
+let {code, map, exports} = transform({
// ...
cssModules: {
- pattern: 'my-company-[name]-[hash]-[local]'
- }
+ pattern: 'my-company-[name]-[hash]-[local]',
+ },
});
```
The following placeholders are currently supported:
-* `[name]` - The base name of the file, without the extension.
-* `[hash]` - A hash of the full file path.
-* `[local]` - The original class name or identifier.
+- `[name]` - The base name of the file, without the extension.
+- `[hash]` - A hash of the full file path.
+- `[local]` - The original class name or identifier.
@@ -231,7 +231,7 @@ The following placeholders are currently supported:
let { code, map, exports } = transform({
// ...
cssModules: {
- // ❌ [local] must be at the end so that
+ // ❌ [local] must be at the end so that
// auto-generated grid line names work
pattern: '[local]-[hash]'
// ✅ do this instead
@@ -242,7 +242,7 @@ let { code, map, exports } = transform({
```css
.grid {
- grid-template-areas: "nav main";
+ grid-template-areas: 'nav main';
}
.nav {
@@ -252,10 +252,25 @@ let { code, map, exports } = transform({
+## Turning off feature scoping
+
+Scoping of grid, animations, and custom identifiers can be turned off. By default all of these are scoped.
+
+```js
+let {code, map, exports} = transform({
+ // ...
+ cssModules: {
+ animation: true,
+ grid: true,
+ customIdents: true,
+ },
+});
+```
+
## Unsupported features
Lightning CSS does not currently implement all CSS modules features available in other implementations. Some of these may be added in the future.
-* Non-function syntax for the `:local` and `:global` pseudo classes.
-* The `@value` rule – superseded by standard CSS variables.
-* The `:import` and `:export` ICSS rules.
+- Non-function syntax for the `:local` and `:global` pseudo classes.
+- The `@value` rule – superseded by standard CSS variables.
+- The `:import` and `:export` ICSS rules.
From fb4b33488cbf54ba063de8babb42aec016a96ce8 Mon Sep 17 00:00:00 2001
From: Devon Govett
Date: Tue, 14 May 2024 23:14:24 -0700
Subject: [PATCH 008/152] fix CSS module scoping with variables
fixes #194
---
src/lib.rs | 93 +++++++++++++++++++++++++++++++++++++
src/properties/animation.rs | 52 +++++++++++++++------
src/properties/custom.rs | 7 +++
3 files changed, 139 insertions(+), 13 deletions(-)
diff --git a/src/lib.rs b/src/lib.rs
index d0ea1982..559f3197 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -23672,6 +23672,99 @@ mod tests {
},
);
+ css_modules_test(
+ r#"
+ .test {
+ animation: rotate var(--duration) linear infinite;
+ }
+ "#,
+ indoc! {r#"
+ .EgL3uq_test {
+ animation: EgL3uq_rotate var(--duration) linear infinite;
+ }
+ "#},
+ map! {
+ "test" => "EgL3uq_test",
+ "rotate" => "EgL3uq_rotate" referenced: true
+ },
+ HashMap::new(),
+ Default::default(),
+ );
+ css_modules_test(
+ r#"
+ .test {
+ animation: none var(--duration);
+ }
+ "#,
+ indoc! {r#"
+ .EgL3uq_test {
+ animation: none var(--duration);
+ }
+ "#},
+ map! {
+ "test" => "EgL3uq_test"
+ },
+ HashMap::new(),
+ Default::default(),
+ );
+ css_modules_test(
+ r#"
+ .test {
+ animation: var(--animation);
+ }
+ "#,
+ indoc! {r#"
+ .EgL3uq_test {
+ animation: var(--animation);
+ }
+ "#},
+ map! {
+ "test" => "EgL3uq_test"
+ },
+ HashMap::new(),
+ Default::default(),
+ );
+ css_modules_test(
+ r#"
+ .test {
+ animation: rotate var(--duration);
+ }
+ "#,
+ indoc! {r#"
+ .EgL3uq_test {
+ animation: rotate var(--duration);
+ }
+ "#},
+ map! {
+ "test" => "EgL3uq_test"
+ },
+ HashMap::new(),
+ crate::css_modules::Config {
+ animation: false,
+ ..Default::default()
+ }
+ );
+ css_modules_test(
+ r#"
+ .test {
+ animation: "rotate" var(--duration);
+ }
+ "#,
+ indoc! {r#"
+ .EgL3uq_test {
+ animation: EgL3uq_rotate var(--duration);
+ }
+ "#},
+ map! {
+ "test" => "EgL3uq_test",
+ "rotate" => "EgL3uq_rotate" referenced: true
+ },
+ HashMap::new(),
+ crate::css_modules::Config {
+ ..Default::default()
+ }
+ );
+
// Stable hashes between project roots.
fn test_project_root(project_root: &str, filename: &str, hash: &str) {
let stylesheet = StyleSheet::parse(
diff --git a/src/properties/animation.rs b/src/properties/animation.rs
index d5d73f83..2b2277d7 100644
--- a/src/properties/animation.rs
+++ b/src/properties/animation.rs
@@ -1,12 +1,14 @@
//! CSS properties related to keyframe animations.
+use std::borrow::Cow;
+
use crate::context::PropertyHandlerContext;
use crate::declaration::{DeclarationBlock, DeclarationList};
use crate::error::{ParserError, PrinterError};
use crate::macros::*;
use crate::prefixes::Feature;
use crate::printer::Printer;
-use crate::properties::{Property, PropertyId, VendorPrefix};
+use crate::properties::{Property, PropertyId, TokenOrValue, VendorPrefix};
use crate::traits::{Parse, PropertyHandler, Shorthand, ToCss, Zero};
use crate::values::number::CSSNumber;
use crate::values::string::CowArcStr;
@@ -346,8 +348,6 @@ impl<'i> PropertyHandler<'i> for AnimationHandler<'i> {
dest: &mut DeclarationList<'i>,
context: &mut PropertyHandlerContext<'i, '_>,
) -> bool {
- use Property::*;
-
macro_rules! maybe_flush {
($prop: ident, $val: expr, $vp: ident) => {{
// If two vendor prefixes for the same property have different
@@ -376,15 +376,15 @@ impl<'i> PropertyHandler<'i> for AnimationHandler<'i> {
}
match property {
- AnimationName(val, vp) => property!(names, val, vp),
- AnimationDuration(val, vp) => property!(durations, val, vp),
- AnimationTimingFunction(val, vp) => property!(timing_functions, val, vp),
- AnimationIterationCount(val, vp) => property!(iteration_counts, val, vp),
- AnimationDirection(val, vp) => property!(directions, val, vp),
- AnimationPlayState(val, vp) => property!(play_states, val, vp),
- AnimationDelay(val, vp) => property!(delays, val, vp),
- AnimationFillMode(val, vp) => property!(fill_modes, val, vp),
- Animation(val, vp) => {
+ Property::AnimationName(val, vp) => property!(names, val, vp),
+ Property::AnimationDuration(val, vp) => property!(durations, val, vp),
+ Property::AnimationTimingFunction(val, vp) => property!(timing_functions, val, vp),
+ Property::AnimationIterationCount(val, vp) => property!(iteration_counts, val, vp),
+ Property::AnimationDirection(val, vp) => property!(directions, val, vp),
+ Property::AnimationPlayState(val, vp) => property!(play_states, val, vp),
+ Property::AnimationDelay(val, vp) => property!(delays, val, vp),
+ Property::AnimationFillMode(val, vp) => property!(fill_modes, val, vp),
+ Property::Animation(val, vp) => {
let names = val.iter().map(|b| b.name.clone()).collect();
maybe_flush!(names, &names, vp);
@@ -418,7 +418,33 @@ impl<'i> PropertyHandler<'i> for AnimationHandler<'i> {
property!(delays, &delays, vp);
property!(fill_modes, &fill_modes, vp);
}
- Unparsed(val) if is_animation_property(&val.property_id) => {
+ Property::Unparsed(val) if is_animation_property(&val.property_id) => {
+ let mut val = Cow::Borrowed(val);
+ if matches!(val.property_id, PropertyId::Animation(_)) {
+ use crate::properties::custom::Token;
+
+ // Find an identifier that isn't a keyword and replace it with an
+ // AnimationName token so it is scoped in CSS modules.
+ for token in &mut val.to_mut().value.0 {
+ match token {
+ TokenOrValue::Token(Token::Ident(id)) => {
+ if AnimationDirection::parse_string(&id).is_err()
+ && AnimationPlayState::parse_string(&id).is_err()
+ && AnimationFillMode::parse_string(&id).is_err()
+ && !EasingFunction::is_ident(&id)
+ && id.as_ref() != "infinite"
+ {
+ *token = TokenOrValue::AnimationName(AnimationName::Ident(CustomIdent(id.clone())));
+ }
+ }
+ TokenOrValue::Token(Token::String(s)) => {
+ *token = TokenOrValue::AnimationName(AnimationName::String(s.clone()));
+ }
+ _ => {}
+ }
+ }
+ }
+
self.flush(dest, context);
dest.push(Property::Unparsed(
val.get_prefixed(context.targets, Feature::Animation),
diff --git a/src/properties/custom.rs b/src/properties/custom.rs
index f93b4005..c3621af6 100644
--- a/src/properties/custom.rs
+++ b/src/properties/custom.rs
@@ -29,6 +29,7 @@ use cssparser::*;
#[cfg(feature = "serde")]
use crate::serialization::ValueWrapper;
+use super::AnimationName;
/// A CSS custom property, representing any unknown property.
#[derive(Debug, Clone, PartialEq)]
@@ -241,6 +242,8 @@ pub enum TokenOrValue<'i> {
Resolution(Resolution),
/// A dashed ident.
DashedIdent(DashedIdent<'i>),
+ /// An animation name.
+ AnimationName(AnimationName<'i>),
}
impl<'i> From<'i>> for TokenOrValue<'i> {
@@ -590,6 +593,10 @@ impl<'i> TokenList<'i> {
v.to_css(dest)?;
false
}
+ TokenOrValue::AnimationName(v) => {
+ v.to_css(dest)?;
+ false
+ }
TokenOrValue::Token(token) => match token {
Token::Delim(d) => {
if *d == '+' || *d == '-' {
From ec9da43eeb1a236f5fd8dc472b3fe060c3db9a79 Mon Sep 17 00:00:00 2001
From: Devon Govett
Date: Thu, 16 May 2024 19:29:01 -0700
Subject: [PATCH 009/152] update browser compat data
---
package.json | 6 +-
scripts/build-prefixes.js | 2 +-
src/compat.rs | 287 +++++++++++++++++++++++++++++++-------
src/prefixes.rs | 14 +-
yarn.lock | 48 +++----
5 files changed, 274 insertions(+), 83 deletions(-)
diff --git a/package.json b/package.json
index c9fe3d72..740531f3 100644
--- a/package.json
+++ b/package.json
@@ -48,10 +48,10 @@
"@codemirror/lang-javascript": "^6.1.2",
"@codemirror/lint": "^6.1.0",
"@codemirror/theme-one-dark": "^6.1.0",
- "@mdn/browser-compat-data": "~5.5.0",
+ "@mdn/browser-compat-data": "~5.5.28",
"@napi-rs/cli": "^2.14.0",
- "autoprefixer": "^10.4.17",
- "caniuse-lite": "^1.0.30001585",
+ "autoprefixer": "^10.4.19",
+ "caniuse-lite": "^1.0.30001620",
"codemirror": "^6.0.1",
"cssnano": "^5.0.8",
"esbuild": "^0.19.8",
diff --git a/scripts/build-prefixes.js b/scripts/build-prefixes.js
index 597e1dbd..1533c8c9 100644
--- a/scripts/build-prefixes.js
+++ b/scripts/build-prefixes.js
@@ -282,7 +282,7 @@ let mdnFeatures = {
logicalPaddingShorthand: mdn.css.properties['padding-inline'].__compat.support,
logicalInset: mdn.css.properties['inset-inline-start'].__compat.support,
logicalSize: mdn.css.properties['inline-size'].__compat.support,
- logicalTextAlign: mdn.css.properties['text-align']['flow_relative_values_start_and_end'].__compat.support,
+ logicalTextAlign: mdn.css.properties['text-align'].start.__compat.support,
labColors: mdn.css.types.color.lab.__compat.support,
oklabColors: mdn.css.types.color.oklab.__compat.support,
colorFunction: mdn.css.types.color.color.__compat.support,
diff --git a/src/compat.rs b/src/compat.rs
index 1a4bec65..1d283cb3 100644
--- a/src/compat.rs
+++ b/src/compat.rs
@@ -10,11 +10,13 @@ pub enum Feature {
AfarListStyleType,
AmharicAbegedeListStyleType,
AmharicListStyleType,
+ AnchorSizeSize,
AnyLink,
AnyPseudo,
ArabicIndicListStyleType,
ArmenianListStyleType,
AsterisksListStyleType,
+ AutoSize,
Autofill,
BengaliListStyleType,
BinaryListStyleType,
@@ -142,6 +144,7 @@ pub enum Feature {
MyanmarListStyleType,
Namespaces,
Nesting,
+ NoneListStyleType,
NotSelectorList,
NthChildOf,
OctalListStyleType,
@@ -441,7 +444,7 @@ impl Feature {
}
}
if let Some(version) = browsers.android {
- if version < 7929856 {
+ if version < 8126464 {
return false;
}
}
@@ -533,7 +536,7 @@ impl Feature {
}
}
if let Some(version) = browsers.android {
- if version < 7929856 {
+ if version < 8126464 {
return false;
}
}
@@ -578,7 +581,7 @@ impl Feature {
}
}
if let Some(version) = browsers.android {
- if version < 7929856 {
+ if version < 8126464 {
return false;
}
}
@@ -623,7 +626,7 @@ impl Feature {
}
}
if let Some(version) = browsers.android {
- if version < 7929856 {
+ if version < 8126464 {
return false;
}
}
@@ -663,7 +666,7 @@ impl Feature {
}
}
if let Some(version) = browsers.android {
- if version < 7929856 {
+ if version < 8126464 {
return false;
}
}
@@ -708,7 +711,7 @@ impl Feature {
}
}
if let Some(version) = browsers.android {
- if version < 7929856 {
+ if version < 8126464 {
return false;
}
}
@@ -753,7 +756,7 @@ impl Feature {
}
}
if let Some(version) = browsers.android {
- if version < 7929856 {
+ if version < 8126464 {
return false;
}
}
@@ -798,7 +801,7 @@ impl Feature {
}
}
if let Some(version) = browsers.android {
- if version < 7929856 {
+ if version < 8126464 {
return false;
}
}
@@ -890,7 +893,7 @@ impl Feature {
}
}
if let Some(version) = browsers.android {
- if version < 7929856 {
+ if version < 8126464 {
return false;
}
}
@@ -935,7 +938,7 @@ impl Feature {
}
}
if let Some(version) = browsers.android {
- if version < 7929856 {
+ if version < 8126464 {
return false;
}
}
@@ -1015,7 +1018,7 @@ impl Feature {
}
}
if let Some(version) = browsers.android {
- if version < 7929856 {
+ if version < 8126464 {
return false;
}
}
@@ -1060,7 +1063,7 @@ impl Feature {
}
}
if let Some(version) = browsers.android {
- if version < 7929856 {
+ if version < 8126464 {
return false;
}
}
@@ -1150,7 +1153,7 @@ impl Feature {
}
}
if let Some(version) = browsers.android {
- if version < 7929856 {
+ if version < 8126464 {
return false;
}
}
@@ -1195,7 +1198,7 @@ impl Feature {
}
}
if let Some(version) = browsers.android {
- if version < 7929856 {
+ if version < 8126464 {
return false;
}
}
@@ -1245,7 +1248,7 @@ impl Feature {
}
}
if let Some(version) = browsers.android {
- if version < 7929856 {
+ if version < 8126464 {
return false;
}
}
@@ -1332,7 +1335,7 @@ impl Feature {
}
}
if let Some(version) = browsers.android {
- if version < 7929856 {
+ if version < 8126464 {
return false;
}
}
@@ -1377,7 +1380,7 @@ impl Feature {
}
}
if let Some(version) = browsers.android {
- if version < 7929856 {
+ if version < 8126464 {
return false;
}
}
@@ -1422,7 +1425,7 @@ impl Feature {
}
}
if let Some(version) = browsers.android {
- if version < 7929856 {
+ if version < 8126464 {
return false;
}
}
@@ -1462,7 +1465,7 @@ impl Feature {
}
}
if let Some(version) = browsers.android {
- if version < 7929856 {
+ if version < 8126464 {
return false;
}
}
@@ -1507,7 +1510,7 @@ impl Feature {
}
}
if let Some(version) = browsers.android {
- if version < 7929856 {
+ if version < 8126464 {
return false;
}
}
@@ -1552,7 +1555,7 @@ impl Feature {
}
}
if let Some(version) = browsers.android {
- if version < 7929856 {
+ if version < 8126464 {
return false;
}
}
@@ -1619,7 +1622,7 @@ impl Feature {
}
}
if let Some(version) = browsers.android {
- if version < 7929856 {
+ if version < 8126464 {
return false;
}
}
@@ -2868,12 +2871,47 @@ impl Feature {
return false;
}
}
- Feature::RoundFunction
- | Feature::RemFunction
- | Feature::ModFunction
- | Feature::AbsFunction
- | Feature::SignFunction
- | Feature::HypotFunction => {
+ Feature::RoundFunction | Feature::RemFunction | Feature::ModFunction => {
+ if let Some(version) = browsers.chrome {
+ if version < 8192000 {
+ return false;
+ }
+ }
+ if let Some(version) = browsers.edge {
+ if version < 8192000 {
+ return false;
+ }
+ }
+ if let Some(version) = browsers.firefox {
+ if version < 7733248 {
+ return false;
+ }
+ }
+ if let Some(version) = browsers.opera {
+ if version < 7274496 {
+ return false;
+ }
+ }
+ if let Some(version) = browsers.safari {
+ if version < 984064 {
+ return false;
+ }
+ }
+ if let Some(version) = browsers.ios_saf {
+ if version < 984064 {
+ return false;
+ }
+ }
+ if let Some(version) = browsers.android {
+ if version < 8192000 {
+ return false;
+ }
+ }
+ if browsers.ie.is_some() || browsers.samsung.is_some() {
+ return false;
+ }
+ }
+ Feature::AbsFunction | Feature::SignFunction => {
if let Some(version) = browsers.firefox {
if version < 7733248 {
return false;
@@ -2899,6 +2937,51 @@ impl Feature {
return false;
}
}
+ Feature::HypotFunction => {
+ if let Some(version) = browsers.chrome {
+ if version < 7864320 {
+ return false;
+ }
+ }
+ if let Some(version) = browsers.edge {
+ if version < 7864320 {
+ return false;
+ }
+ }
+ if let Some(version) = browsers.firefox {
+ if version < 7733248 {
+ return false;
+ }
+ }
+ if let Some(version) = browsers.opera {
+ if version < 5242880 {
+ return false;
+ }
+ }
+ if let Some(version) = browsers.safari {
+ if version < 984064 {
+ return false;
+ }
+ }
+ if let Some(version) = browsers.ios_saf {
+ if version < 984064 {
+ return false;
+ }
+ }
+ if let Some(version) = browsers.samsung {
+ if version < 1638400 {
+ return false;
+ }
+ }
+ if let Some(version) = browsers.android {
+ if version < 7864320 {
+ return false;
+ }
+ }
+ if browsers.ie.is_some() {
+ return false;
+ }
+ }
Feature::GradientInterpolationHints => {
if let Some(version) = browsers.chrome {
if version < 2621440 {
@@ -2986,7 +3069,7 @@ impl Feature {
}
}
if let Some(version) = browsers.android {
- if version < 2424832 {
+ if version < 263168 {
return false;
}
}
@@ -3080,7 +3163,7 @@ impl Feature {
}
}
if let Some(version) = browsers.android {
- if version < 262144 {
+ if version < 2752512 {
return false;
}
}
@@ -3271,19 +3354,37 @@ impl Feature {
return false;
}
}
+ if let Some(version) = browsers.edge {
+ 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()
- {
+ if let Some(version) = browsers.opera {
+ if version < 5373952 {
+ return false;
+ }
+ }
+ if let Some(version) = browsers.safari {
+ if version < 1115392 {
+ return false;
+ }
+ }
+ if let Some(version) = browsers.ios_saf {
+ if version < 1115392 {
+ return false;
+ }
+ }
+ if let Some(version) = browsers.android {
+ if version < 8060928 {
+ return false;
+ }
+ }
+ if browsers.ie.is_some() || browsers.samsung.is_some() {
return false;
}
}
@@ -3389,12 +3490,17 @@ impl Feature {
return false;
}
}
+ if let Some(version) = browsers.samsung {
+ if version < 1572864 {
+ return false;
+ }
+ }
if let Some(version) = browsers.android {
if version < 7667712 {
return false;
}
}
- if browsers.ie.is_some() || browsers.samsung.is_some() {
+ if browsers.ie.is_some() {
return false;
}
}
@@ -3704,12 +3810,17 @@ impl Feature {
return false;
}
}
+ if let Some(version) = browsers.samsung {
+ if version < 1572864 {
+ return false;
+ }
+ }
if let Some(version) = browsers.android {
if version < 7667712 {
return false;
}
}
- if browsers.firefox.is_some() || browsers.ie.is_some() || browsers.samsung.is_some() {
+ if browsers.firefox.is_some() || browsers.ie.is_some() {
return false;
}
}
@@ -4670,14 +4781,15 @@ impl Feature {
| Feature::HiraganaListStyleType
| Feature::HiraganaIrohaListStyleType
| Feature::KatakanaListStyleType
- | Feature::KatakanaIrohaListStyleType => {
+ | Feature::KatakanaIrohaListStyleType
+ | Feature::AutoSize => {
if let Some(version) = browsers.chrome {
if version < 1179648 {
return false;
}
}
if let Some(version) = browsers.edge {
- if version < 5177344 {
+ if version < 786432 {
return false;
}
}
@@ -4686,6 +4798,11 @@ impl Feature {
return false;
}
}
+ if let Some(version) = browsers.ie {
+ if version < 720896 {
+ return false;
+ }
+ }
if let Some(version) = browsers.opera {
if version < 917504 {
return false;
@@ -4711,9 +4828,6 @@ impl Feature {
return false;
}
}
- if browsers.ie.is_some() {
- return false;
- }
}
Feature::KoreanHangulFormalListStyleType
| Feature::KoreanHanjaFormalListStyleType
@@ -4854,6 +4968,53 @@ impl Feature {
}
}
}
+ Feature::NoneListStyleType => {
+ if let Some(version) = browsers.chrome {
+ if version < 1179648 {
+ return false;
+ }
+ }
+ if let Some(version) = browsers.edge {
+ if version < 786432 {
+ return false;
+ }
+ }
+ if let Some(version) = browsers.firefox {
+ if version < 5177344 {
+ return false;
+ }
+ }
+ if let Some(version) = browsers.ie {
+ if version < 720896 {
+ return false;
+ }
+ }
+ if let Some(version) = browsers.opera {
+ if version < 917504 {
+ return false;
+ }
+ }
+ if let Some(version) = browsers.safari {
+ if version < 65536 {
+ return false;
+ }
+ }
+ if let Some(version) = browsers.ios_saf {
+ if version < 65536 {
+ return false;
+ }
+ }
+ if let Some(version) = browsers.samsung {
+ if version < 65536 {
+ return false;
+ }
+ }
+ if let Some(version) = browsers.android {
+ if version < 263168 {
+ return false;
+ }
+ }
+ }
Feature::SimpChineseFormalListStyleType
| Feature::SimpChineseInformalListStyleType
| Feature::TradChineseFormalListStyleType
@@ -4965,6 +5126,36 @@ impl Feature {
return false;
}
}
+ Feature::AnchorSizeSize => {
+ if let Some(version) = browsers.chrome {
+ if version < 8192000 {
+ return false;
+ }
+ }
+ if let Some(version) = browsers.edge {
+ if version < 8192000 {
+ return false;
+ }
+ }
+ if let Some(version) = browsers.opera {
+ if version < 7274496 {
+ return false;
+ }
+ }
+ if let Some(version) = browsers.android {
+ if version < 8192000 {
+ return false;
+ }
+ }
+ if browsers.firefox.is_some()
+ || browsers.ie.is_some()
+ || browsers.ios_saf.is_some()
+ || browsers.safari.is_some()
+ || browsers.samsung.is_some()
+ {
+ return false;
+ }
+ }
Feature::FitContentSize => {
if let Some(version) = browsers.chrome {
if version < 1638400 {
@@ -5059,7 +5250,7 @@ impl Feature {
}
Feature::MaxContentSize => {
if let Some(version) = browsers.chrome {
- if version < 3014656 {
+ if version < 1638400 {
return false;
}
}
@@ -5089,12 +5280,12 @@ impl Feature {
}
}
if let Some(version) = browsers.samsung {
- if version < 327680 {
+ if version < 66816 {
return false;
}
}
if let Some(version) = browsers.android {
- if version < 3014656 {
+ if version < 263168 {
return false;
}
}
diff --git a/src/prefixes.rs b/src/prefixes.rs
index fe06123d..56697404 100644
--- a/src/prefixes.rs
+++ b/src/prefixes.rs
@@ -806,7 +806,7 @@ impl Feature {
}
}
if let Some(version) = browsers.opera {
- if version >= 983040 {
+ if version >= 983040 && version <= 6881280 {
prefixes |= VendorPrefix::WebKit;
}
}
@@ -1124,7 +1124,7 @@ impl Feature {
}
}
if let Some(version) = browsers.opera {
- if version >= 983040 {
+ if version >= 983040 && version <= 6225920 {
prefixes |= VendorPrefix::WebKit;
}
}
@@ -1468,7 +1468,7 @@ impl Feature {
}
}
if let Some(version) = browsers.opera {
- if version >= 983040 {
+ if version >= 983040 && version <= 6881280 {
prefixes |= VendorPrefix::WebKit;
}
}
@@ -1510,7 +1510,7 @@ impl Feature {
}
}
if let Some(version) = browsers.samsung {
- if version >= 262144 {
+ if version >= 262144 && version <= 327680 {
prefixes |= VendorPrefix::WebKit;
}
}
@@ -1785,7 +1785,7 @@ impl Feature {
}
}
if let Some(version) = browsers.samsung {
- if version >= 262144 {
+ if version >= 262144 && version <= 851968 {
prefixes |= VendorPrefix::WebKit;
}
}
@@ -1865,7 +1865,7 @@ impl Feature {
}
}
if let Some(version) = browsers.opera {
- if version >= 983040 {
+ if version >= 983040 && version <= 6422528 {
prefixes |= VendorPrefix::WebKit;
}
}
@@ -1875,7 +1875,7 @@ impl Feature {
}
}
if let Some(version) = browsers.samsung {
- if version >= 262144 {
+ if version >= 262144 && version <= 1441792 {
prefixes |= VendorPrefix::WebKit;
}
}
diff --git a/yarn.lock b/yarn.lock
index d2d191f4..af6b910d 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -454,10 +454,10 @@
resolved "https://registry.yarnpkg.com/@lmdb/lmdb-win32-x64/-/lmdb-win32-x64-2.5.2.tgz#28f643fbc0bec30b07fbe95b137879b6b4d1c9c5"
integrity sha512-zrBczSbXKxEyK2ijtbRdICDygRqWSRPpZMN5dD1T8VMEW5RIhIbwFWw2phDRXuBQdVDpSjalCIUMWMV2h3JaZA==
-"@mdn/browser-compat-data@~5.5.0":
- version "5.5.10"
- resolved "https://registry.yarnpkg.com/@mdn/browser-compat-data/-/browser-compat-data-5.5.10.tgz#b7c0454a617b268b7a9ab9ca6c886abd1ec69473"
- integrity sha512-s2GGND9oLhEuksOFtICYOBZdMWPANBXTMqAXh89q6g1Mi3+OuWEmp9WFzw2v/nmS175vqeewpC1kDJA7taaxyA==
+"@mdn/browser-compat-data@~5.5.28":
+ version "5.5.28"
+ resolved "https://registry.yarnpkg.com/@mdn/browser-compat-data/-/browser-compat-data-5.5.28.tgz#76cd0651ebf9725916da10b686ac98288403d89f"
+ integrity sha512-yKS9hfVRsYx/3usEAk+86rq2KnHHyoafyvw2Xob5dXPSOZKIY56Nkas/JK3cmav8nZjjHWiYS/asv9TQy1YLbg==
"@mischnic/json-sourcemap@^0.1.0":
version "0.1.0"
@@ -1228,13 +1228,13 @@ ast-types@0.15.2:
dependencies:
tslib "^2.0.1"
-autoprefixer@^10.4.17:
- version "10.4.17"
- resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.17.tgz#35cd5695cbbe82f536a50fa025d561b01fdec8be"
- integrity sha512-/cpVNRLSfhOtcGflT13P2794gVSgmPgTR+erw5ifnMLZb0UnSlkK4tquLmkd3BhA+nLo5tX8Cu0upUsGKvKbmg==
+autoprefixer@^10.4.19:
+ version "10.4.19"
+ resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.19.tgz#ad25a856e82ee9d7898c59583c1afeb3fa65f89f"
+ integrity sha512-BaENR2+zBZ8xXhM4pUaKUxlVdxZ0EZhjvbopwnXmxRUfqDmwSpC2lAi/QXvx7NRdPCo1WKEcEF6mV64si1z4Ew==
dependencies:
- browserslist "^4.22.2"
- caniuse-lite "^1.0.30001578"
+ browserslist "^4.23.0"
+ caniuse-lite "^1.0.30001599"
fraction.js "^4.3.7"
normalize-range "^0.1.2"
picocolors "^1.0.0"
@@ -1291,13 +1291,13 @@ braces@^3.0.2:
dependencies:
fill-range "^7.0.1"
-browserslist@^4.0.0, browserslist@^4.16.0, browserslist@^4.16.6, browserslist@^4.22.2, browserslist@^4.6.6:
- version "4.22.3"
- resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.22.3.tgz#299d11b7e947a6b843981392721169e27d60c5a6"
- integrity sha512-UAp55yfwNv0klWNapjs/ktHoguxuQNGnOzxYmfnXIS+8AsRDZkSDxg7R1AX3GKzn078SBI5dzwzj/Yx0Or0e3A==
+browserslist@^4.0.0, browserslist@^4.16.0, browserslist@^4.16.6, browserslist@^4.23.0, browserslist@^4.6.6:
+ version "4.23.0"
+ resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.23.0.tgz#8f3acc2bbe73af7213399430890f86c63a5674ab"
+ integrity sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==
dependencies:
- caniuse-lite "^1.0.30001580"
- electron-to-chromium "^1.4.648"
+ caniuse-lite "^1.0.30001587"
+ electron-to-chromium "^1.4.668"
node-releases "^2.0.14"
update-browserslist-db "^1.0.13"
@@ -1347,10 +1347,10 @@ caniuse-api@^3.0.0:
lodash.memoize "^4.1.2"
lodash.uniq "^4.5.0"
-caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001578, caniuse-lite@^1.0.30001580, caniuse-lite@^1.0.30001585:
- version "1.0.30001585"
- resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001585.tgz#0b4e848d84919c783b2a41c13f7de8ce96744401"
- integrity sha512-yr2BWR1yLXQ8fMpdS/4ZZXpseBgE7o4g41x3a6AJOqZuOi+iE/WdJYAuZ6Y95i4Ohd2Y+9MzIWRR+uGABH4s3Q==
+caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001587, caniuse-lite@^1.0.30001599, caniuse-lite@^1.0.30001620:
+ version "1.0.30001620"
+ resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001620.tgz#78bb6f35b8fe315b96b8590597094145d0b146b4"
+ integrity sha512-WJvYsOjd1/BYUY6SNGUosK9DUidBPDTnOARHp3fSmFO1ekdxaY6nKRttEVrfMmYi80ctS0kz1wiWmm14fVc3ew==
chalk@^2.0.0:
version "2.4.2"
@@ -1727,10 +1727,10 @@ dotenv@^7.0.0:
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-7.0.0.tgz#a2be3cd52736673206e8a85fb5210eea29628e7c"
integrity sha512-M3NhsLbV1i6HuGzBUH8vXrtxOk+tWmzWKDMbAVSUp3Zsjm7ywFeuwrUXhmhQyRK1q5B5GGy7hcXPbj3bnfZg2g==
-electron-to-chromium@^1.4.648:
- version "1.4.664"
- resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.664.tgz#b00fc67d5d4f124e429b0dcce5a02ae18ef33ede"
- integrity sha512-k9VKKSkOSNPvSckZgDDl/IQx45E1quMjX8QfLzUsAs/zve8AyFDK+ByRynSP/OfEfryiKHpQeMf00z0leLCc3A==
+electron-to-chromium@^1.4.668:
+ version "1.4.773"
+ resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.773.tgz#49741af9bb4e712ad899e35d8344d8d59cdb7e12"
+ integrity sha512-87eHF+h3PlCRwbxVEAw9KtK3v7lWfc/sUDr0W76955AdYTG4bV/k0zrl585Qnj/skRMH2qOSiE+kqMeOQ+LOpw==
end-of-stream@^1.1.0, end-of-stream@^1.4.1:
version "1.4.4"
From f4408c7bdbbfa2c4cbdf5731b351811a0323fa8c Mon Sep 17 00:00:00 2001
From: Devon Govett
Date: Fri, 17 May 2024 09:58:42 -0700
Subject: [PATCH 010/152] Implement animation-timeline property and add to
animation shorthand
#572
---
node/ast.d.ts | 121 ++++++++++++--
scripts/build-prefixes.js | 1 +
src/compat.rs | 35 ++++
src/lib.rs | 150 ++++++++++++++++-
src/properties/animation.rs | 324 +++++++++++++++++++++++++++++++++++-
src/properties/custom.rs | 2 +-
src/properties/mod.rs | 2 +
7 files changed, 613 insertions(+), 22 deletions(-)
diff --git a/node/ast.d.ts b/node/ast.d.ts
index 6015ff44..8bc02772 100644
--- a/node/ast.d.ts
+++ b/node/ast.d.ts
@@ -504,6 +504,10 @@ export type TokenOrValue =
| {
type: "dashed-ident";
value: String;
+ }
+ | {
+ type: "animation-name";
+ value: AnimationName;
};
/**
* A raw CSS token.
@@ -1114,6 +1118,21 @@ export type Time =
type: "milliseconds";
value: number;
};
+/**
+ * A value for the [animation-name](https://drafts.csswg.org/css-animations/#animation-name) property.
+ */
+export type AnimationName =
+ | {
+ type: "none";
+ }
+ | {
+ type: "ident";
+ value: String;
+ }
+ | {
+ type: "string";
+ value: String;
+ };
/**
* A CSS environment variable name.
*/
@@ -1935,6 +1954,12 @@ export type PropertyId =
property: "animation-fill-mode";
vendorPrefix: VendorPrefix;
}
+ | {
+ property: "animation-composition";
+ }
+ | {
+ property: "animation-timeline";
+ }
| {
property: "animation";
vendorPrefix: VendorPrefix;
@@ -3283,6 +3308,14 @@ export type Declaration =
value: AnimationFillMode[];
vendorPrefix: VendorPrefix;
}
+ | {
+ property: "animation-composition";
+ value: AnimationComposition[];
+ }
+ | {
+ property: "animation-timeline";
+ value: AnimationTimeline[];
+ }
| {
property: "animation";
value: Animation[];
@@ -5442,21 +5475,6 @@ export type StepPosition =
| {
type: "jump-both";
};
-/**
- * A value for the [animation-name](https://drafts.csswg.org/css-animations/#animation-name) property.
- */
-export type AnimationName =
- | {
- type: "none";
- }
- | {
- type: "ident";
- value: String;
- }
- | {
- type: "string";
- value: String;
- };
/**
* A value for the [animation-iteration-count](https://drafts.csswg.org/css-animations/#animation-iteration-count) property.
*/
@@ -5480,6 +5498,49 @@ export type AnimationPlayState = "running" | "paused";
* A value for the [animation-fill-mode](https://drafts.csswg.org/css-animations/#animation-fill-mode) property.
*/
export type AnimationFillMode = "none" | "forwards" | "backwards" | "both";
+/**
+ * A value for the [animation-composition](https://drafts.csswg.org/css-animations-2/#animation-composition) property.
+ */
+export type AnimationComposition = "replace" | "add" | "accumulate";
+/**
+ * A value for the [animation-timeline](https://drafts.csswg.org/css-animations-2/#animation-timeline) property.
+ */
+export type AnimationTimeline =
+ | {
+ type: "auto";
+ }
+ | {
+ type: "none";
+ }
+ | {
+ type: "dashed-ident";
+ value: String;
+ }
+ | {
+ type: "scroll";
+ value: ScrollTimeline;
+ }
+ | {
+ type: "view";
+ value: ViewTimeline;
+ };
+/**
+ * A scroll axis, used in the `scroll()` function.
+ */
+export type ScrollAxis = "block" | "inline" | "x" | "y";
+/**
+ * A scroller, used in the `scroll()` function.
+ */
+export type Scroller = "root" | "nearest" | "self";
+/**
+ * A generic value that represents a value with two components, e.g. a border radius.
+ *
+ * When serialized, only a single component will be written if both are equal.
+ *
+ * @minItems 2
+ * @maxItems 2
+ */
+export type Size2DFor_LengthPercentageOrAuto = [LengthPercentageOrAuto, LengthPercentageOrAuto];
/**
* An individual [transform function](https://www.w3.org/TR/2019/CR-css-transforms-1-20190214/#two-d-transform-functions).
*/
@@ -8488,6 +8549,32 @@ export interface Transition {
*/
timingFunction: EasingFunction;
}
+/**
+ * The [scroll()](https://drafts.csswg.org/scroll-animations-1/#scroll-notation) function.
+ */
+export interface ScrollTimeline {
+ /**
+ * Specifies which axis of the scroll container to use as the progress for the timeline.
+ */
+ axis: ScrollAxis;
+ /**
+ * Specifies which element to use as the scroll container.
+ */
+ scroller: Scroller;
+}
+/**
+ * The [view()](https://drafts.csswg.org/scroll-animations-1/#view-notation) function.
+ */
+export interface ViewTimeline {
+ /**
+ * Specifies which axis of the scroll container to use as the progress for the timeline.
+ */
+ axis: ScrollAxis;
+ /**
+ * Provides an adjustment of the view progress visibility range.
+ */
+ inset: Size2DFor_LengthPercentageOrAuto;
+}
/**
* A value for the [animation](https://drafts.csswg.org/css-animations/#animation) shorthand property.
*/
@@ -8520,6 +8607,10 @@ export interface Animation {
* The current play state of the animation.
*/
playState: AnimationPlayState;
+ /**
+ * The animation timeline.
+ */
+ timeline: AnimationTimeline;
/**
* The easing function for the animation.
*/
diff --git a/scripts/build-prefixes.js b/scripts/build-prefixes.js
index 1533c8c9..8fc83ca2 100644
--- a/scripts/build-prefixes.js
+++ b/scripts/build-prefixes.js
@@ -329,6 +329,7 @@ let mdnFeatures = {
fontStretchPercentage: mdn.css.properties['font-stretch'].percentage.__compat.support,
lightDark: mdn.css.types.color['light-dark'].__compat.support,
accentSystemColor: mdn.css.types.color['system-color'].accentcolor_accentcolortext.__compat.support,
+ animationTimelineShorthand: mdn.css.properties.animation['animation-timeline_included'].__compat.support,
};
for (let key in mdn.css.types.length) {
diff --git a/src/compat.rs b/src/compat.rs
index 1d283cb3..b1846f34 100644
--- a/src/compat.rs
+++ b/src/compat.rs
@@ -11,6 +11,7 @@ pub enum Feature {
AmharicAbegedeListStyleType,
AmharicListStyleType,
AnchorSizeSize,
+ AnimationTimelineShorthand,
AnyLink,
AnyPseudo,
ArabicIndicListStyleType,
@@ -3414,6 +3415,40 @@ impl Feature {
return false;
}
}
+ Feature::AnimationTimelineShorthand => {
+ if let Some(version) = browsers.chrome {
+ if version < 7536640 {
+ return false;
+ }
+ }
+ if let Some(version) = browsers.edge {
+ if version < 7536640 {
+ return false;
+ }
+ }
+ if let Some(version) = browsers.opera {
+ if version < 5046272 {
+ return false;
+ }
+ }
+ if let Some(version) = browsers.samsung {
+ if version < 1507328 {
+ return false;
+ }
+ }
+ if let Some(version) = browsers.android {
+ if version < 7536640 {
+ return false;
+ }
+ }
+ if browsers.firefox.is_some()
+ || browsers.ie.is_some()
+ || browsers.ios_saf.is_some()
+ || browsers.safari.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 559f3197..d39c6ac4 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -11243,6 +11243,46 @@ mod tests {
".foo { animation: foo 0s 3s infinite }",
".foo{animation:0s 3s infinite foo}",
);
+ minify_test(".foo { animation: foo 3s --test }", ".foo{animation:3s foo --test}");
+ minify_test(".foo { animation: foo 3s scroll() }", ".foo{animation:3s foo scroll()}");
+ minify_test(
+ ".foo { animation: foo 3s scroll(block) }",
+ ".foo{animation:3s foo scroll()}",
+ );
+ minify_test(
+ ".foo { animation: foo 3s scroll(root inline) }",
+ ".foo{animation:3s foo scroll(root inline)}",
+ );
+ minify_test(
+ ".foo { animation: foo 3s scroll(inline root) }",
+ ".foo{animation:3s foo scroll(root inline)}",
+ );
+ minify_test(
+ ".foo { animation: foo 3s scroll(inline nearest) }",
+ ".foo{animation:3s foo scroll(inline)}",
+ );
+ minify_test(
+ ".foo { animation: foo 3s view(block) }",
+ ".foo{animation:3s foo view()}",
+ );
+ minify_test(
+ ".foo { animation: foo 3s view(inline) }",
+ ".foo{animation:3s foo view(inline)}",
+ );
+ minify_test(
+ ".foo { animation: foo 3s view(inline 10px 10px) }",
+ ".foo{animation:3s foo view(inline 10px)}",
+ );
+ minify_test(
+ ".foo { animation: foo 3s view(inline 10px 12px) }",
+ ".foo{animation:3s foo view(inline 10px 12px)}",
+ );
+ minify_test(
+ ".foo { animation: foo 3s view(inline auto auto) }",
+ ".foo{animation:3s foo view(inline)}",
+ );
+ minify_test(".foo { animation: foo 3s auto }", ".foo{animation:3s foo}");
+ minify_test(".foo { animation-composition: add }", ".foo{animation-composition:add}");
test(
r#"
.foo {
@@ -11254,6 +11294,7 @@ mod tests {
animation-play-state: running;
animation-delay: 100ms;
animation-fill-mode: forwards;
+ animation-timeline: auto;
}
"#,
indoc! {r#"
@@ -11273,6 +11314,7 @@ mod tests {
animation-play-state: running, paused;
animation-delay: 100ms, 0s;
animation-fill-mode: forwards, none;
+ animation-timeline: auto, auto;
}
"#,
indoc! {r#"
@@ -11319,6 +11361,7 @@ mod tests {
animation-play-state: running;
animation-delay: 100ms;
animation-fill-mode: forwards;
+ animation-timeline: auto;
}
"#,
indoc! {r#"
@@ -11331,6 +11374,55 @@ mod tests {
animation-play-state: running;
animation-delay: .1s;
animation-fill-mode: forwards;
+ animation-timeline: auto;
+ }
+ "#},
+ );
+ test(
+ r#"
+ .foo {
+ animation-name: foo;
+ animation-duration: 0.09s;
+ animation-timing-function: ease-in-out;
+ animation-iteration-count: 2;
+ animation-direction: alternate;
+ animation-play-state: running;
+ animation-delay: 100ms;
+ animation-fill-mode: forwards;
+ animation-timeline: scroll();
+ }
+ "#,
+ indoc! {r#"
+ .foo {
+ animation: 90ms ease-in-out .1s 2 alternate forwards foo scroll();
+ }
+ "#},
+ );
+ test(
+ r#"
+ .foo {
+ animation-name: foo;
+ animation-duration: 0.09s;
+ animation-timing-function: ease-in-out;
+ animation-iteration-count: 2;
+ animation-direction: alternate;
+ animation-play-state: running;
+ animation-delay: 100ms;
+ animation-fill-mode: forwards;
+ animation-timeline: scroll(), view();
+ }
+ "#,
+ indoc! {r#"
+ .foo {
+ animation-name: foo;
+ animation-duration: 90ms;
+ animation-timing-function: ease-in-out;
+ animation-iteration-count: 2;
+ animation-direction: alternate;
+ animation-play-state: running;
+ animation-delay: .1s;
+ animation-fill-mode: forwards;
+ animation-timeline: scroll(), view();
}
"#},
);
@@ -11441,6 +11533,58 @@ mod tests {
..Browsers::default()
},
);
+
+ prefix_test(
+ r#"
+ .foo {
+ animation: .2s ease-in-out bar scroll();
+ }
+ "#,
+ indoc! {r#"
+ .foo {
+ animation: .2s ease-in-out bar;
+ animation-timeline: scroll();
+ }
+ "#},
+ Browsers {
+ safari: Some(16 << 16),
+ ..Browsers::default()
+ },
+ );
+ prefix_test(
+ r#"
+ .foo {
+ animation: .2s ease-in-out bar scroll();
+ }
+ "#,
+ indoc! {r#"
+ .foo {
+ animation: .2s ease-in-out bar scroll();
+ }
+ "#},
+ Browsers {
+ chrome: Some(120 << 16),
+ ..Browsers::default()
+ },
+ );
+ prefix_test(
+ r#"
+ .foo {
+ animation: .2s ease-in-out bar scroll();
+ }
+ "#,
+ indoc! {r#"
+ .foo {
+ -webkit-animation: .2s ease-in-out bar;
+ animation: .2s ease-in-out bar;
+ animation-timeline: scroll();
+ }
+ "#},
+ Browsers {
+ safari: Some(6 << 16),
+ ..Browsers::default()
+ },
+ );
}
#[test]
@@ -23742,7 +23886,7 @@ mod tests {
crate::css_modules::Config {
animation: false,
..Default::default()
- }
+ },
);
css_modules_test(
r#"
@@ -23760,9 +23904,7 @@ mod tests {
"rotate" => "EgL3uq_rotate" referenced: true
},
HashMap::new(),
- crate::css_modules::Config {
- ..Default::default()
- }
+ crate::css_modules::Config { ..Default::default() },
);
// Stable hashes between project roots.
diff --git a/src/properties/animation.rs b/src/properties/animation.rs
index 2b2277d7..03b76166 100644
--- a/src/properties/animation.rs
+++ b/src/properties/animation.rs
@@ -10,7 +10,9 @@ use crate::prefixes::Feature;
use crate::printer::Printer;
use crate::properties::{Property, PropertyId, TokenOrValue, VendorPrefix};
use crate::traits::{Parse, PropertyHandler, Shorthand, ToCss, Zero};
+use crate::values::ident::DashedIdent;
use crate::values::number::CSSNumber;
+use crate::values::size::Size2D;
use crate::values::string::CowArcStr;
use crate::values::{easing::EasingFunction, ident::CustomIdent, time::Time};
#[cfg(feature = "visitor")]
@@ -19,6 +21,8 @@ use cssparser::*;
use itertools::izip;
use smallvec::SmallVec;
+use super::LengthPercentageOrAuto;
+
/// A value for the [animation-name](https://drafts.csswg.org/css-animations/#animation-name) property.
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "visitor", derive(Visit))]
@@ -200,6 +204,255 @@ impl Default for AnimationFillMode {
}
}
+enum_property! {
+ /// A value for the [animation-composition](https://drafts.csswg.org/css-animations-2/#animation-composition) property.
+ pub enum AnimationComposition {
+ /// The result of compositing the effect value with the underlying value is simply the effect value.
+ Replace,
+ /// The effect value is added to the underlying value.
+ Add,
+ /// The effect value is accumulated onto the underlying value.
+ Accumulate,
+ }
+}
+
+/// A value for the [animation-timeline](https://drafts.csswg.org/css-animations-2/#animation-timeline) property.
+#[derive(Debug, Clone, PartialEq)]
+#[cfg_attr(feature = "visitor", derive(Visit))]
+#[cfg_attr(
+ feature = "serde",
+ derive(serde::Serialize, serde::Deserialize),
+ serde(tag = "type", content = "value", rename_all = "kebab-case")
+)]
+#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
+#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
+pub enum AnimationTimeline<'i> {
+ /// The animation’s timeline is a DocumentTimeline, more specifically the default document timeline.
+ Auto,
+ /// The animation is not associated with a timeline.
+ None,
+ /// A timeline referenced by name.
+ #[cfg_attr(feature = "serde", serde(borrow))]
+ DashedIdent(DashedIdent<'i>),
+ /// The scroll() function.
+ Scroll(ScrollTimeline),
+ /// The view() function.
+ View(ViewTimeline),
+}
+
+impl<'i> Default for AnimationTimeline<'i> {
+ fn default() -> Self {
+ AnimationTimeline::Auto
+ }
+}
+
+/// The [scroll()](https://drafts.csswg.org/scroll-animations-1/#scroll-notation) function.
+#[derive(Debug, Clone, PartialEq)]
+#[cfg_attr(feature = "visitor", derive(Visit))]
+#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
+#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
+#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
+pub struct ScrollTimeline {
+ /// Specifies which element to use as the scroll container.
+ pub scroller: Scroller,
+ /// Specifies which axis of the scroll container to use as the progress for the timeline.
+ pub axis: ScrollAxis,
+}
+
+impl<'i> Parse<'i> for ScrollTimeline {
+ fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> {
+ let mut scroller = None;
+ let mut axis = None;
+ loop {
+ if scroller.is_none() {
+ scroller = input.try_parse(Scroller::parse).ok();
+ }
+
+ if axis.is_none() {
+ axis = input.try_parse(ScrollAxis::parse).ok();
+ if axis.is_some() {
+ continue;
+ }
+ }
+ break;
+ }
+
+ Ok(ScrollTimeline {
+ scroller: scroller.unwrap_or_default(),
+ axis: axis.unwrap_or_default(),
+ })
+ }
+}
+
+impl ToCss for ScrollTimeline {
+ fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError>
+ where
+ W: std::fmt::Write,
+ {
+ let mut needs_space = false;
+ if self.scroller != Scroller::default() {
+ self.scroller.to_css(dest)?;
+ needs_space = true;
+ }
+
+ if self.axis != ScrollAxis::default() {
+ if needs_space {
+ dest.write_char(' ')?;
+ }
+ self.axis.to_css(dest)?;
+ }
+
+ Ok(())
+ }
+}
+
+enum_property! {
+ /// A scroller, used in the `scroll()` function.
+ pub enum Scroller {
+ /// Specifies to use the document viewport as the scroll container.
+ "root": Root,
+ /// Specifies to use the nearest ancestor scroll container.
+ "nearest": Nearest,
+ /// Specifies to use the element’s own principal box as the scroll container.
+ "self": SelfElement,
+ }
+}
+
+impl Default for Scroller {
+ fn default() -> Self {
+ Scroller::Nearest
+ }
+}
+
+enum_property! {
+ /// A scroll axis, used in the `scroll()` function.
+ pub enum ScrollAxis {
+ /// Specifies to use the measure of progress along the block axis of the scroll container.
+ Block,
+ /// Specifies to use the measure of progress along the inline axis of the scroll container.
+ Inline,
+ /// Specifies to use the measure of progress along the horizontal axis of the scroll container.
+ X,
+ /// Specifies to use the measure of progress along the vertical axis of the scroll container.
+ Y,
+ }
+}
+
+impl Default for ScrollAxis {
+ fn default() -> Self {
+ ScrollAxis::Block
+ }
+}
+
+/// The [view()](https://drafts.csswg.org/scroll-animations-1/#view-notation) function.
+#[derive(Debug, Clone, PartialEq)]
+#[cfg_attr(feature = "visitor", derive(Visit))]
+#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
+#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
+#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
+pub struct ViewTimeline {
+ /// Specifies which axis of the scroll container to use as the progress for the timeline.
+ pub axis: ScrollAxis,
+ /// Provides an adjustment of the view progress visibility range.
+ pub inset: Size2D,
+}
+
+impl<'i> Parse<'i> for ViewTimeline {
+ fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> {
+ let mut axis = None;
+ let mut inset = None;
+ loop {
+ if axis.is_none() {
+ axis = input.try_parse(ScrollAxis::parse).ok();
+ }
+
+ if inset.is_none() {
+ inset = input.try_parse(Size2D::parse).ok();
+ if inset.is_some() {
+ continue;
+ }
+ }
+ break;
+ }
+
+ Ok(ViewTimeline {
+ axis: axis.unwrap_or_default(),
+ inset: inset.unwrap_or(Size2D(LengthPercentageOrAuto::Auto, LengthPercentageOrAuto::Auto)),
+ })
+ }
+}
+
+impl ToCss for ViewTimeline {
+ fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError>
+ where
+ W: std::fmt::Write,
+ {
+ let mut needs_space = false;
+ if self.axis != ScrollAxis::default() {
+ self.axis.to_css(dest)?;
+ needs_space = true;
+ }
+
+ if self.inset.0 != LengthPercentageOrAuto::Auto || self.inset.1 != LengthPercentageOrAuto::Auto {
+ if needs_space {
+ dest.write_char(' ')?;
+ }
+ self.inset.to_css(dest)?;
+ }
+
+ Ok(())
+ }
+}
+
+impl<'i> Parse<'i> for AnimationTimeline<'i> {
+ fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> {
+ if input.try_parse(|i| i.expect_ident_matching("auto")).is_ok() {
+ return Ok(AnimationTimeline::Auto);
+ }
+
+ if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() {
+ return Ok(AnimationTimeline::None);
+ }
+
+ if let Ok(name) = input.try_parse(DashedIdent::parse) {
+ return Ok(AnimationTimeline::DashedIdent(name));
+ }
+
+ let location = input.current_source_location();
+ let f = input.expect_function()?.clone();
+ input.parse_nested_block(move |input| {
+ match_ignore_ascii_case! { &f,
+ "scroll" => ScrollTimeline::parse(input).map(AnimationTimeline::Scroll),
+ "view" => ViewTimeline::parse(input).map(AnimationTimeline::View),
+ _ => Err(location.new_custom_error(ParserError::UnexpectedToken(crate::properties::custom::Token::Function(f.into()))))
+ }
+ })
+ }
+}
+
+impl<'i> ToCss for AnimationTimeline<'i> {
+ fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError>
+ where
+ W: std::fmt::Write,
+ {
+ match self {
+ AnimationTimeline::Auto => dest.write_str("auto"),
+ AnimationTimeline::None => dest.write_str("none"),
+ AnimationTimeline::DashedIdent(name) => name.to_css(dest),
+ AnimationTimeline::Scroll(scroll) => {
+ dest.write_str("scroll(")?;
+ scroll.to_css(dest)?;
+ dest.write_char(')')
+ }
+ AnimationTimeline::View(view) => {
+ dest.write_str("view(")?;
+ view.to_css(dest)?;
+ dest.write_char(')')
+ }
+ }
+ }
+}
+
define_list_shorthand! {
/// A value for the [animation](https://drafts.csswg.org/css-animations/#animation) shorthand property.
pub struct Animation<'i>(VendorPrefix) {
@@ -220,6 +473,8 @@ define_list_shorthand! {
delay: AnimationDelay(Time, VendorPrefix),
/// The animation fill mode.
fill_mode: AnimationFillMode(AnimationFillMode, VendorPrefix),
+ /// The animation timeline.
+ timeline: AnimationTimeline(AnimationTimeline<'i>),
}
}
@@ -233,6 +488,7 @@ impl<'i> Parse<'i> for Animation<'i> {
let mut play_state = None;
let mut delay = None;
let mut fill_mode = None;
+ let mut timeline = None;
macro_rules! parse_prop {
($var: ident, $type: ident) => {
@@ -254,6 +510,7 @@ impl<'i> Parse<'i> for Animation<'i> {
parse_prop!(fill_mode, AnimationFillMode);
parse_prop!(play_state, AnimationPlayState);
parse_prop!(name, AnimationName);
+ parse_prop!(timeline, AnimationTimeline);
break;
}
@@ -266,6 +523,7 @@ impl<'i> Parse<'i> for Animation<'i> {
play_state: play_state.unwrap_or(AnimationPlayState::Running),
delay: delay.unwrap_or(Time::Seconds(0.0)),
fill_mode: fill_mode.unwrap_or(AnimationFillMode::None),
+ timeline: timeline.unwrap_or(AnimationTimeline::Auto),
})
}
}
@@ -321,6 +579,11 @@ impl<'i> ToCss for Animation<'i> {
// Chrome does not yet support strings, however.
self.name.to_css(dest)?;
+ if self.name != AnimationName::None && self.timeline != AnimationTimeline::default() {
+ dest.write_char(' ')?;
+ self.timeline.to_css(dest)?;
+ }
+
Ok(())
}
}
@@ -338,6 +601,7 @@ pub(crate) struct AnimationHandler<'i> {
play_states: Option<(SmallVec<[AnimationPlayState; 1]>, VendorPrefix)>,
delays: Option<(SmallVec<[Time; 1]>, VendorPrefix)>,
fill_modes: Option<(SmallVec<[AnimationFillMode; 1]>, VendorPrefix)>,
+ timelines: Option<[AnimationTimeline<'i>; 1]>>,
has_any: bool,
}
@@ -384,6 +648,10 @@ impl<'i> PropertyHandler<'i> for AnimationHandler<'i> {
Property::AnimationPlayState(val, vp) => property!(play_states, val, vp),
Property::AnimationDelay(val, vp) => property!(delays, val, vp),
Property::AnimationFillMode(val, vp) => property!(fill_modes, val, vp),
+ Property::AnimationTimeline(val) => {
+ self.timelines = Some(val.clone());
+ self.has_any = true;
+ }
Property::Animation(val, vp) => {
let names = val.iter().map(|b| b.name.clone()).collect();
maybe_flush!(names, &names, vp);
@@ -409,6 +677,8 @@ impl<'i> PropertyHandler<'i> for AnimationHandler<'i> {
let fill_modes = val.iter().map(|b| b.fill_mode.clone()).collect();
maybe_flush!(fill_modes, &fill_modes, vp);
+ self.timelines = Some(val.iter().map(|b| b.timeline.clone()).collect());
+
property!(names, &names, vp);
property!(durations, &durations, vp);
property!(timing_functions, &timing_functions, vp);
@@ -433,6 +703,7 @@ impl<'i> PropertyHandler<'i> for AnimationHandler<'i> {
&& AnimationFillMode::parse_string(&id).is_err()
&& !EasingFunction::is_ident(&id)
&& id.as_ref() != "infinite"
+ && id.as_ref() != "auto"
{
*token = TokenOrValue::AnimationName(AnimationName::Ident(CustomIdent(id.clone())));
}
@@ -477,6 +748,7 @@ impl<'i> AnimationHandler<'i> {
let mut play_states = std::mem::take(&mut self.play_states);
let mut delays = std::mem::take(&mut self.delays);
let mut fill_modes = std::mem::take(&mut self.fill_modes);
+ let mut timelines_value = std::mem::take(&mut self.timelines);
if let (
Some((names, names_vp)),
@@ -507,6 +779,15 @@ impl<'i> AnimationHandler<'i> {
& *play_states_vp
& *delays_vp
& *fill_modes_vp;
+ let mut timelines = if let Some(timelines) = &mut timelines_value {
+ Cow::Borrowed(timelines)
+ } else if !intersection.contains(VendorPrefix::None) {
+ // Prefixed animation shorthand does not support animation-timeline
+ Cow::Owned(std::iter::repeat(AnimationTimeline::Auto).take(len).collect())
+ } else {
+ Cow::Owned(SmallVec::new())
+ };
+
if !intersection.is_empty()
&& durations.len() == len
&& timing_functions.len() == len
@@ -515,7 +796,19 @@ impl<'i> AnimationHandler<'i> {
&& play_states.len() == len
&& delays.len() == len
&& fill_modes.len() == len
+ && timelines.len() == len
{
+ let timeline_property = if timelines.iter().any(|t| *t != AnimationTimeline::Auto)
+ && (intersection != VendorPrefix::None
+ || !context
+ .targets
+ .is_compatible(crate::compat::Feature::AnimationTimelineShorthand))
+ {
+ Some(Property::AnimationTimeline(timelines.clone().into_owned()))
+ } else {
+ None
+ };
+
let animations = izip!(
names.drain(..),
durations.drain(..),
@@ -524,10 +817,21 @@ impl<'i> AnimationHandler<'i> {
directions.drain(..),
play_states.drain(..),
delays.drain(..),
- fill_modes.drain(..)
+ fill_modes.drain(..),
+ timelines.to_mut().drain(..)
)
.map(
- |(name, duration, timing_function, iteration_count, direction, play_state, delay, fill_mode)| {
+ |(
+ name,
+ duration,
+ timing_function,
+ iteration_count,
+ direction,
+ play_state,
+ delay,
+ fill_mode,
+ timeline,
+ )| {
Animation {
name,
duration,
@@ -537,6 +841,11 @@ impl<'i> AnimationHandler<'i> {
play_state,
delay,
fill_mode,
+ timeline: if timeline_property.is_some() {
+ AnimationTimeline::Auto
+ } else {
+ timeline
+ },
}
},
)
@@ -551,6 +860,11 @@ impl<'i> AnimationHandler<'i> {
play_states_vp.remove(intersection);
delays_vp.remove(intersection);
fill_modes_vp.remove(intersection);
+
+ if let Some(p) = timeline_property {
+ dest.push(p);
+ }
+ timelines_value = None;
}
}
@@ -573,6 +887,10 @@ impl<'i> AnimationHandler<'i> {
prop!(play_states, AnimationPlayState);
prop!(delays, AnimationDelay);
prop!(fill_modes, AnimationFillMode);
+
+ if let Some(val) = timelines_value {
+ dest.push(Property::AnimationTimeline(val));
+ }
}
}
@@ -587,6 +905,8 @@ fn is_animation_property(property_id: &PropertyId) -> bool {
| PropertyId::AnimationPlayState(_)
| PropertyId::AnimationDelay(_)
| PropertyId::AnimationFillMode(_)
+ | PropertyId::AnimationComposition
+ | PropertyId::AnimationTimeline
| PropertyId::Animation(_) => true,
_ => false,
}
diff --git a/src/properties/custom.rs b/src/properties/custom.rs
index c3621af6..3198422b 100644
--- a/src/properties/custom.rs
+++ b/src/properties/custom.rs
@@ -27,9 +27,9 @@ use crate::visitor::Visit;
use cssparser::color::parse_hash_color;
use cssparser::*;
+use super::AnimationName;
#[cfg(feature = "serde")]
use crate::serialization::ValueWrapper;
-use super::AnimationName;
/// A CSS custom property, representing any unknown property.
#[derive(Debug, Clone, PartialEq)]
diff --git a/src/properties/mod.rs b/src/properties/mod.rs
index 98756755..38ff8622 100644
--- a/src/properties/mod.rs
+++ b/src/properties/mod.rs
@@ -1491,6 +1491,8 @@ define_properties! {
"animation-play-state": AnimationPlayState(SmallVec<[AnimationPlayState; 1]>, VendorPrefix) / WebKit / Moz / O,
"animation-delay": AnimationDelay(SmallVec<[Time; 1]>, VendorPrefix) / WebKit / Moz / O,
"animation-fill-mode": AnimationFillMode(SmallVec<[AnimationFillMode; 1]>, VendorPrefix) / WebKit / Moz / O,
+ "animation-composition": AnimationComposition(SmallVec<[AnimationComposition; 1]>),
+ "animation-timeline": AnimationTimeline(SmallVec<[AnimationTimeline<'i>; 1]>),
"animation": Animation(AnimationList<'i>, VendorPrefix) / WebKit / Moz / O shorthand: true,
// https://drafts.csswg.org/css-transforms-2/
From 81d21b9f5201c6eb8365fbb4fa81cbd947c3474f Mon Sep 17 00:00:00 2001
From: Devon Govett
Date: Fri, 17 May 2024 11:57:10 -0700
Subject: [PATCH 011/152] v1.25.0
---
Cargo.lock | 6 +++---
Cargo.toml | 4 ++--
napi/Cargo.toml | 4 ++--
node/Cargo.toml | 2 +-
package.json | 2 +-
selectors/Cargo.toml | 2 +-
6 files changed, 10 insertions(+), 10 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
index eed17d06..b1b97132 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -766,7 +766,7 @@ dependencies = [
[[package]]
name = "lightningcss"
-version = "1.0.0-alpha.55"
+version = "1.0.0-alpha.56"
dependencies = [
"ahash 0.8.7",
"assert_cmd",
@@ -812,7 +812,7 @@ dependencies = [
[[package]]
name = "lightningcss-napi"
-version = "0.1.0"
+version = "0.2.0"
dependencies = [
"crossbeam-channel",
"cssparser",
@@ -1006,7 +1006,7 @@ checksum = "7f222829ae9293e33a9f5e9f440c6760a3d450a64affe1846486b140db81c1f4"
[[package]]
name = "parcel_selectors"
-version = "0.26.4"
+version = "0.26.5"
dependencies = [
"bitflags 2.4.1",
"cssparser",
diff --git a/Cargo.toml b/Cargo.toml
index a298e0b1..e77fa5a3 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -12,7 +12,7 @@ members = [
[package]
authors = ["Devon Govett "]
name = "lightningcss"
-version = "1.0.0-alpha.55"
+version = "1.0.0-alpha.56"
description = "A CSS parser, transformer, and minifier"
license = "MPL-2.0"
edition = "2021"
@@ -51,7 +51,7 @@ substitute_variables = ["visitor", "into_owned"]
serde = { version = "1.0.201", features = ["derive"], optional = true }
cssparser = "0.33.0"
cssparser-color = "0.1.0"
-parcel_selectors = { version = "0.26.4", path = "./selectors" }
+parcel_selectors = { version = "0.26.5", path = "./selectors" }
itertools = "0.10.1"
smallvec = { version = "1.7.0", features = ["union"] }
bitflags = "2.2.1"
diff --git a/napi/Cargo.toml b/napi/Cargo.toml
index b296c3b6..d8415acd 100644
--- a/napi/Cargo.toml
+++ b/napi/Cargo.toml
@@ -1,7 +1,7 @@
[package]
authors = ["Devon Govett "]
name = "lightningcss-napi"
-version = "0.1.0"
+version = "0.2.0"
description = "Node-API bindings for Lightning CSS"
license = "MPL-2.0"
repository = "https://github.com/parcel-bundler/lightningcss"
@@ -16,7 +16,7 @@ bundler = ["dep:crossbeam-channel", "dep:rayon"]
serde = { version = "1.0.201", features = ["derive"] }
serde_bytes = "0.11.5"
cssparser = "0.33.0"
-lightningcss = { version = "1.0.0-alpha.54", path = "../", features = ["nodejs", "serde"] }
+lightningcss = { version = "1.0.0-alpha.55", path = "../", features = ["nodejs", "serde"] }
parcel_sourcemap = { version = "2.1.1", features = ["json"] }
serde-detach = "0.0.1"
smallvec = { version = "1.7.0", features = ["union"] }
diff --git a/node/Cargo.toml b/node/Cargo.toml
index 4f9e8b75..bb472910 100644
--- a/node/Cargo.toml
+++ b/node/Cargo.toml
@@ -9,7 +9,7 @@ publish = false
crate-type = ["cdylib"]
[dependencies]
-lightningcss-napi = { version = "0.1.0", path = "../napi", features = ["bundler", "visitor"] }
+lightningcss-napi = { version = "0.2.0", path = "../napi", features = ["bundler", "visitor"] }
napi = {version = "2.15.4", default-features = false, features = ["compat-mode"]}
napi-derive = "2"
diff --git a/package.json b/package.json
index 740531f3..fa6a3b78 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "lightningcss",
- "version": "1.24.1",
+ "version": "1.25.0",
"license": "MPL-2.0",
"description": "A CSS parser, transformer, and minifier written in Rust",
"main": "node/index.js",
diff --git a/selectors/Cargo.toml b/selectors/Cargo.toml
index 481c468b..8cad46af 100644
--- a/selectors/Cargo.toml
+++ b/selectors/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "parcel_selectors"
-version = "0.26.4"
+version = "0.26.5"
authors = ["The Servo Project Developers"]
documentation = "https://docs.rs/parcel_selectors/"
description = "CSS Selectors matching for Rust - forked for lightningcss"
From 60622a1af51c0c6b83582fde60a244bfd9edf963 Mon Sep 17 00:00:00 2001
From: Devon Govett
Date: Fri, 24 May 2024 22:44:53 -0700
Subject: [PATCH 012/152] fix order of properties with all shorthand
fixes #746
---
Cargo.lock | 1 -
Cargo.toml | 1 -
node/test/visitor.test.mjs | 4 ++--
src/declaration.rs | 39 +++++++++++++++++++-------------------
src/lib.rs | 21 ++++++++++++--------
5 files changed, 35 insertions(+), 31 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
index b1b97132..9cb99076 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -781,7 +781,6 @@ dependencies = [
"dashmap",
"data-encoding",
"getrandom",
- "indexmap 2.2.6",
"indoc",
"itertools 0.10.5",
"jemallocator",
diff --git a/Cargo.toml b/Cargo.toml
index e77fa5a3..a71057a7 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -62,7 +62,6 @@ const-str = "0.3.1"
pathdiff = "0.2.1"
ahash = "0.8.7"
paste = "1.0.12"
-indexmap = "2.2.6"
# CLI deps
atty = { version = "0.2", optional = true }
clap = { version = "3.0.6", features = ["derive"], optional = true }
diff --git a/node/test/visitor.test.mjs b/node/test/visitor.test.mjs
index 87ee208d..3a42a696 100644
--- a/node/test/visitor.test.mjs
+++ b/node/test/visitor.test.mjs
@@ -116,7 +116,7 @@ test('custom units', () => {
}
});
- assert.equal(res.code.toString(), '.foo{font-size:calc(3*var(--step));--step:.25rem}');
+ assert.equal(res.code.toString(), '.foo{--step:.25rem;font-size:calc(3*var(--step))}');
});
test('design tokens', () => {
@@ -822,7 +822,7 @@ test('dashed idents', () => {
}
});
- assert.equal(res.code.toString(), '.foo{color:var(--prefix-foo);--prefix-foo:#ff0}');
+ assert.equal(res.code.toString(), '.foo{--prefix-foo:#ff0;color:var(--prefix-foo)}');
});
test('custom idents', () => {
diff --git a/src/declaration.rs b/src/declaration.rs
index 501e2f8c..da97e37d 100644
--- a/src/declaration.rs
+++ b/src/declaration.rs
@@ -1,6 +1,7 @@
//! CSS declarations.
use std::borrow::Cow;
+use std::collections::HashMap;
use std::ops::Range;
use crate::context::{DeclarationContext, PropertyHandlerContext};
@@ -33,14 +34,13 @@ use crate::properties::{
transition::TransitionHandler,
ui::ColorSchemeHandler,
};
-use crate::properties::{CSSWideKeyword, Property, PropertyId};
+use crate::properties::{Property, PropertyId};
use crate::traits::{PropertyHandler, ToCss};
use crate::values::ident::DashedIdent;
use crate::values::string::CowArcStr;
#[cfg(feature = "visitor")]
use crate::visitor::Visit;
use cssparser::*;
-use indexmap::IndexMap;
/// A CSS declaration block.
///
@@ -516,10 +516,9 @@ pub(crate) struct DeclarationHandler<'i> {
color_scheme: ColorSchemeHandler,
fallback: FallbackHandler,
prefix: PrefixHandler,
- all: Option,
direction: Option,
unicode_bidi: Option,
- custom_properties: IndexMap<'i>, CustomProperty<'i>>,
+ custom_properties: HashMap<'i>, usize>,
decls: DeclarationList<'i>,
}
@@ -571,13 +570,18 @@ impl<'i> DeclarationHandler<'i> {
}
if let CustomPropertyName::Custom(name) = &custom.name {
- if let Some(prev) = self.custom_properties.get_mut(name) {
- if prev.value == custom.value {
+ if let Some(index) = self.custom_properties.get(name) {
+ if self.decls[*index] == *property {
return true;
}
- *prev = custom.clone();
+ let mut custom = custom.clone();
+ self.add_conditional_fallbacks(&mut custom, context);
+ self.decls[*index] = Property::Custom(custom);
} else {
- self.custom_properties.insert(name.clone(), custom.clone());
+ self.custom_properties.insert(name.clone(), self.decls.len());
+ let mut custom = custom.clone();
+ self.add_conditional_fallbacks(&mut custom, context);
+ self.decls.push(Property::Custom(custom));
}
return true;
@@ -600,13 +604,17 @@ impl<'i> DeclarationHandler<'i> {
true
}
Property::All(keyword) => {
- *self = DeclarationHandler {
- custom_properties: std::mem::take(&mut self.custom_properties),
+ let mut handler = DeclarationHandler {
unicode_bidi: self.unicode_bidi.clone(),
direction: self.direction.clone(),
- all: Some(keyword.clone()),
..Default::default()
};
+ for (key, index) in self.custom_properties.drain() {
+ handler.custom_properties.insert(key, handler.decls.len());
+ handler.decls.push(self.decls[index].clone());
+ }
+ handler.decls.push(Property::All(keyword.clone()));
+ *self = handler;
true
}
_ => false,
@@ -633,20 +641,12 @@ impl<'i> DeclarationHandler<'i> {
}
pub fn finalize(&mut self, context: &mut PropertyHandlerContext<'i, '_>) {
- // Always place the `all` property first. Previous properties will have been omitted.
- if let Some(all) = std::mem::take(&mut self.all) {
- self.decls.push(Property::All(all));
- }
if let Some(direction) = std::mem::take(&mut self.direction) {
self.decls.push(Property::Direction(direction));
}
if let Some(unicode_bidi) = std::mem::take(&mut self.unicode_bidi) {
self.decls.push(Property::UnicodeBidi(unicode_bidi));
}
- for (_, mut value) in std::mem::take(&mut self.custom_properties) {
- self.add_conditional_fallbacks(&mut value, context);
- self.decls.push(Property::Custom(value));
- }
self.background.finalize(&mut self.decls, context);
self.border.finalize(&mut self.decls, context);
@@ -675,5 +675,6 @@ impl<'i> DeclarationHandler<'i> {
self.color_scheme.finalize(&mut self.decls, context);
self.fallback.finalize(&mut self.decls, context);
self.prefix.finalize(&mut self.decls, context);
+ self.custom_properties.clear();
}
}
diff --git a/src/lib.rs b/src/lib.rs
index d39c6ac4..27a7df16 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -21583,26 +21583,26 @@ mod tests {
indoc! {r#"
@keyframes foo {
from {
- opacity: 0;
--custom: #ff0;
+ opacity: 0;
}
to {
- opacity: 1;
--custom: #ee00be;
+ opacity: 1;
}
}
@supports (color: lab(0% 0 0)) {
@keyframes foo {
from {
- opacity: 0;
--custom: #ff0;
+ opacity: 0;
}
to {
- opacity: 1;
--custom: lab(50.998% 125.506 -50.7078);
+ opacity: 1;
}
}
}
@@ -23736,8 +23736,8 @@ mod tests {
}
.EgL3uq_foo {
- color: var(--foo);
--foo: red;
+ color: var(--foo);
}
"#},
map! {
@@ -23786,10 +23786,10 @@ mod tests {
}
.EgL3uq_foo {
- color: var(--EgL3uq_foo);
- font-palette: --EgL3uq_Cooler;
--EgL3uq_foo: red;
--EgL3uq_bar: green;
+ color: var(--EgL3uq_foo);
+ font-palette: --EgL3uq_Cooler;
}
.EgL3uq_bar {
@@ -27646,7 +27646,7 @@ mod tests {
);
minify_test(
".foo { --test: red; all: revert-layer }",
- ".foo{all:revert-layer;--test:red}",
+ ".foo{--test:red;all:revert-layer}",
);
minify_test(
".foo { unicode-bidi: embed; all: revert-layer }",
@@ -27660,5 +27660,10 @@ mod tests {
".foo { direction: rtl; all: revert-layer; direction: ltr }",
".foo{all:revert-layer;direction:ltr}",
);
+ minify_test(".foo { background: var(--foo); all: unset; }", ".foo{all:unset}");
+ minify_test(
+ ".foo { all: unset; background: var(--foo); }",
+ ".foo{all:unset;background:var(--foo)}",
+ );
}
}
From fa6c015317e5cca29fa8a09b12d09ac53dcfbc77 Mon Sep 17 00:00:00 2001
From: Devon Govett
Date: Fri, 24 May 2024 22:48:31 -0700
Subject: [PATCH 013/152] v1.25.1
---
Cargo.lock | 2 +-
Cargo.toml | 2 +-
package.json | 2 +-
3 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
index 9cb99076..a5d8b853 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -766,7 +766,7 @@ dependencies = [
[[package]]
name = "lightningcss"
-version = "1.0.0-alpha.56"
+version = "1.0.0-alpha.57"
dependencies = [
"ahash 0.8.7",
"assert_cmd",
diff --git a/Cargo.toml b/Cargo.toml
index a71057a7..7036f761 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -12,7 +12,7 @@ members = [
[package]
authors = ["Devon Govett "]
name = "lightningcss"
-version = "1.0.0-alpha.56"
+version = "1.0.0-alpha.57"
description = "A CSS parser, transformer, and minifier"
license = "MPL-2.0"
edition = "2021"
diff --git a/package.json b/package.json
index fa6a3b78..91ffd35f 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "lightningcss",
- "version": "1.25.0",
+ "version": "1.25.1",
"license": "MPL-2.0",
"description": "A CSS parser, transformer, and minifier written in Rust",
"main": "node/index.js",
From 76e31cf963cc0ef6ace0415664cc987c6977b07f Mon Sep 17 00:00:00 2001
From: Devon Govett
Date: Sun, 2 Jun 2024 21:17:43 -0700
Subject: [PATCH 014/152] Use macros to derive implementations of Parse and
ToCss for many enums
---
Cargo.lock | 1 +
Cargo.toml | 4 +-
derive/Cargo.toml | 1 +
derive/src/lib.rs | 295 ++-------------------------------
derive/src/parse.rs | 213 ++++++++++++++++++++++++
derive/src/to_css.rs | 156 +++++++++++++++++
derive/src/visit.rs | 292 ++++++++++++++++++++++++++++++++
src/macros.rs | 57 +------
src/properties/align.rs | 203 +++--------------------
src/properties/animation.rs | 182 ++++++--------------
src/properties/background.rs | 24 +--
src/properties/border.rs | 33 +---
src/properties/border_image.rs | 34 +---
src/properties/contain.rs | 6 +-
src/properties/custom.rs | 20 +--
src/properties/display.rs | 59 ++-----
src/properties/flex.rs | 16 +-
src/properties/font.rs | 182 +++-----------------
src/properties/grid.rs | 58 +------
src/properties/list.rs | 171 +++++++------------
src/properties/masking.rs | 77 +++------
src/properties/outline.rs | 25 +--
src/properties/position.rs | 25 +--
src/properties/size.rs | 4 +-
src/properties/svg.rs | 111 ++-----------
src/properties/text.rs | 161 +++++-------------
src/properties/transform.rs | 38 +----
src/properties/ui.rs | 97 +++++------
src/rules/keyframes.rs | 20 +--
src/rules/page.rs | 32 ++--
src/traits.rs | 37 +++++
src/values/calc.rs | 8 +-
src/values/color.rs | 1 +
src/values/easing.rs | 16 +-
src/values/gradient.rs | 42 +----
src/values/image.rs | 38 +----
src/values/length.rs | 62 +------
src/values/percentage.rs | 32 +---
src/values/shape.rs | 30 +---
39 files changed, 1100 insertions(+), 1763 deletions(-)
create mode 100644 derive/src/parse.rs
create mode 100644 derive/src/to_css.rs
create mode 100644 derive/src/visit.rs
diff --git a/Cargo.lock b/Cargo.lock
index a5d8b853..07d5f7e1 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -804,6 +804,7 @@ dependencies = [
name = "lightningcss-derive"
version = "1.0.0-alpha.42"
dependencies = [
+ "convert_case",
"proc-macro2",
"quote",
"syn 1.0.109",
diff --git a/Cargo.toml b/Cargo.toml
index 7036f761..75bc397c 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -43,7 +43,7 @@ jsonschema = ["schemars", "serde", "parcel_selectors/jsonschema"]
nodejs = ["dep:serde"]
serde = ["dep:serde", "smallvec/serde", "cssparser/serde", "parcel_selectors/serde", "into_owned"]
sourcemap = ["parcel_sourcemap"]
-visitor = ["lightningcss-derive"]
+visitor = []
into_owned = ["static-self", "static-self/smallvec", "parcel_selectors/into_owned"]
substitute_variables = ["visitor", "into_owned"]
@@ -69,7 +69,7 @@ browserslist-rs = { version = "0.15.0", optional = true }
rayon = { version = "1.5.1", optional = true }
dashmap = { version = "5.0.0", optional = true }
serde_json = { version = "1.0.78", optional = true }
-lightningcss-derive = { version = "=1.0.0-alpha.42", path = "./derive", optional = true }
+lightningcss-derive = { version = "=1.0.0-alpha.42", path = "./derive" }
schemars = { version = "0.8.19", features = ["smallvec"], optional = true }
static-self = { version = "0.1.0", path = "static-self", optional = true }
diff --git a/derive/Cargo.toml b/derive/Cargo.toml
index 1864748e..e1f82c70 100644
--- a/derive/Cargo.toml
+++ b/derive/Cargo.toml
@@ -14,3 +14,4 @@ proc-macro = true
syn = { version = "1.0", features = ["extra-traits"] }
quote = "1.0"
proc-macro2 = "1.0"
+convert_case = "0.6.0"
diff --git a/derive/src/lib.rs b/derive/src/lib.rs
index 00b77a26..12241491 100644
--- a/derive/src/lib.rs
+++ b/derive/src/lib.rs
@@ -1,293 +1,20 @@
-use std::collections::HashSet;
+use proc_macro::TokenStream;
-use proc_macro::{self, TokenStream};
-use proc_macro2::{Span, TokenStream as TokenStream2};
-use quote::quote;
-use syn::{
- parse::Parse, parse_macro_input, parse_quote, Attribute, Data, DataEnum, DeriveInput, Field, Fields,
- GenericParam, Generics, Ident, Member, Token, Type, Visibility,
-};
+mod parse;
+mod to_css;
+mod visit;
#[proc_macro_derive(Visit, attributes(visit, skip_visit, skip_type, visit_types))]
pub fn derive_visit_children(input: TokenStream) -> TokenStream {
- let DeriveInput {
- ident,
- data,
- generics,
- attrs,
- ..
- } = parse_macro_input!(input);
-
- let options: Vec = attrs
- .iter()
- .filter_map(|attr| {
- if attr.path.is_ident("visit") {
- let opts: VisitOptions = attr.parse_args().unwrap();
- Some(opts)
- } else {
- None
- }
- })
- .collect();
-
- let visit_types = if let Some(attr) = attrs.iter().find(|attr| attr.path.is_ident("visit_types")) {
- let types: VisitTypes = attr.parse_args().unwrap();
- let types = types.types;
- Some(quote! { crate::visit_types!(#(#types)|*) })
- } else {
- None
- };
-
- if options.is_empty() {
- derive(&ident, &data, &generics, None, visit_types)
- } else {
- options
- .into_iter()
- .map(|options| derive(&ident, &data, &generics, Some(options), visit_types.clone()))
- .collect()
- }
-}
-
-fn derive(
- ident: &Ident,
- data: &Data,
- generics: &Generics,
- options: Option,
- visit_types: Option,
-) -> TokenStream {
- let mut impl_generics = generics.clone();
- let mut type_defs = quote! {};
- let generics = if let Some(VisitOptions {
- generic: Some(generic), ..
- }) = &options
- {
- let mappings = generics
- .type_params()
- .zip(generic.type_params())
- .map(|(a, b)| quote! { type #a = #b; });
- type_defs = quote! { #(#mappings)* };
- impl_generics.params.clear();
- generic
- } else {
- &generics
- };
-
- if impl_generics.lifetimes().next().is_none() {
- impl_generics.params.insert(0, parse_quote! { 'i })
- }
-
- let lifetime = impl_generics.lifetimes().next().unwrap().clone();
- let t = impl_generics.type_params().find(|g| &g.ident.to_string() == "R");
- let v = quote! { __V };
- let t = if let Some(t) = t {
- GenericParam::Type(t.ident.clone().into())
- } else {
- let t: GenericParam = parse_quote! { __T };
- impl_generics
- .params
- .push(parse_quote! { #t: crate::visitor::Visit<#lifetime, __T, #v> });
- t
- };
-
- impl_generics
- .params
- .push(parse_quote! { #v: ?Sized + crate::visitor::Visitor<#lifetime, #t> });
-
- for ty in generics.type_params() {
- let name = &ty.ident;
- impl_generics.make_where_clause().predicates.push(parse_quote! {
- #name: Visit<#lifetime, #t, #v>
- })
- }
-
- let mut seen_types = HashSet::new();
- let mut child_types = Vec::new();
- let mut visit = Vec::new();
- match data {
- Data::Struct(s) => {
- for (
- index,
- Field {
- vis, ty, ident, attrs, ..
- },
- ) in s.fields.iter().enumerate()
- {
- if attrs.iter().any(|attr| attr.path.is_ident("skip_visit")) {
- continue;
- }
-
- if matches!(ty, Type::Reference(_)) || !matches!(vis, Visibility::Public(..)) {
- continue;
- }
-
- if visit_types.is_none() && !seen_types.contains(ty) && !skip_type(attrs) {
- seen_types.insert(ty.clone());
- child_types.push(quote! {
- <#ty as Visit<#lifetime, #t, #v>>::CHILD_TYPES.bits()
- });
- }
-
- let name = ident
- .as_ref()
- .map_or_else(|| Member::Unnamed(index.into()), |ident| Member::Named(ident.clone()));
- visit.push(quote! { self.#name.visit(visitor)?; })
- }
- }
- Data::Enum(DataEnum { variants, .. }) => {
- let variants = variants
- .iter()
- .map(|variant| {
- let name = &variant.ident;
- let mut field_names = Vec::new();
- let mut visit_fields = Vec::new();
- for (index, Field { ty, ident, attrs, .. }) in variant.fields.iter().enumerate() {
- let name = ident.as_ref().map_or_else(
- || Ident::new(&format!("_{}", index), Span::call_site()),
- |ident| ident.clone(),
- );
- field_names.push(name.clone());
-
- if matches!(ty, Type::Reference(_)) {
- continue;
- }
-
- if visit_types.is_none() && !seen_types.contains(ty) && !skip_type(attrs) && !skip_type(&variant.attrs)
- {
- seen_types.insert(ty.clone());
- child_types.push(quote! {
- <#ty as Visit<#lifetime, #t, #v>>::CHILD_TYPES.bits()
- });
- }
-
- visit_fields.push(quote! { #name.visit(visitor)?; })
- }
-
- match variant.fields {
- Fields::Unnamed(_) => {
- quote! {
- Self::#name(#(#field_names),*) => {
- #(#visit_fields)*
- }
- }
- }
- Fields::Named(_) => {
- quote! {
- Self::#name { #(#field_names),* } => {
- #(#visit_fields)*
- }
- }
- }
- Fields::Unit => quote! {},
- }
- })
- .collect::();
-
- visit.push(quote! {
- match self {
- #variants
- _ => {}
- }
- })
- }
- _ => {}
- }
-
- if visit_types.is_none() && child_types.is_empty() {
- child_types.push(quote! { crate::visitor::VisitTypes::empty().bits() });
- }
-
- let (_, ty_generics, _) = generics.split_for_impl();
- let (impl_generics, _, where_clause) = impl_generics.split_for_impl();
-
- let self_visit = if let Some(VisitOptions {
- visit: Some(visit),
- kind: Some(kind),
- ..
- }) = &options
- {
- child_types.push(quote! { crate::visitor::VisitTypes::#kind.bits() });
-
- quote! {
- fn visit(&mut self, visitor: &mut #v) -> Result<(), #v::Error> {
- if visitor.visit_types().contains(crate::visitor::VisitTypes::#kind) {
- visitor.#visit(self)
- } else {
- self.visit_children(visitor)
- }
- }
- }
- } else {
- quote! {}
- };
-
- let child_types = visit_types.unwrap_or_else(|| {
- quote! {
- {
- #type_defs
- crate::visitor::VisitTypes::from_bits_retain(#(#child_types)|*)
- }
- }
- });
-
- let output = quote! {
- impl #impl_generics Visit<#lifetime, #t, #v> for #ident #ty_generics #where_clause {
- const CHILD_TYPES: crate::visitor::VisitTypes = #child_types;
-
- #self_visit
-
- fn visit_children(&mut self, visitor: &mut #v) -> Result<(), #v::Error> {
- if !<#lifetime, #t, #v>>::CHILD_TYPES.intersects(visitor.visit_types()) {
- return Ok(())
- }
-
- #(#visit)*
-
- Ok(())
- }
- }
- };
-
- output.into()
-}
-
-fn skip_type(attrs: &Vec) -> bool {
- attrs.iter().any(|attr| attr.path.is_ident("skip_type"))
-}
-
-struct VisitOptions {
- visit: Option,
- kind: Option,
- generic: Option,
-}
-
-impl Parse for VisitOptions {
- fn parse(input: syn::parse::ParseStream) -> syn::Result {
- let (visit, kind, comma) = if input.peek(Ident) {
- let visit: Ident = input.parse()?;
- let _: Token![,] = input.parse()?;
- let kind: Ident = input.parse()?;
- let comma: Result = input.parse();
- (Some(visit), Some(kind), comma.is_ok())
- } else {
- (None, None, true)
- };
- let generic: Option = if comma { Some(input.parse()?) } else { None };
- Ok(Self { visit, kind, generic })
- }
+ visit::derive_visit_children(input)
}
-struct VisitTypes {
- types: Vec,
+#[proc_macro_derive(Parse, attributes(css))]
+pub fn derive_parse(input: TokenStream) -> TokenStream {
+ parse::derive_parse(input)
}
-impl Parse for VisitTypes {
- fn parse(input: syn::parse::ParseStream) -> syn::Result {
- let first: Ident = input.parse()?;
- let mut types = vec![first];
- while input.parse::().is_ok() {
- let id: Ident = input.parse()?;
- types.push(id);
- }
- Ok(Self { types })
- }
+#[proc_macro_derive(ToCss, attributes(css))]
+pub fn derive_to_css(input: TokenStream) -> TokenStream {
+ to_css::derive_to_css(input)
}
diff --git a/derive/src/parse.rs b/derive/src/parse.rs
new file mode 100644
index 00000000..995b344e
--- /dev/null
+++ b/derive/src/parse.rs
@@ -0,0 +1,213 @@
+use convert_case::Casing;
+use proc_macro::{self, TokenStream};
+use proc_macro2::{Literal, Span, TokenStream as TokenStream2};
+use quote::quote;
+use syn::{
+ parse::Parse, parse_macro_input, parse_quote, Attribute, Data, DataEnum, DeriveInput, Fields, Ident, Token,
+};
+
+pub fn derive_parse(input: TokenStream) -> TokenStream {
+ let DeriveInput {
+ ident,
+ data,
+ mut generics,
+ attrs,
+ ..
+ } = parse_macro_input!(input);
+ let opts = CssOptions::parse_attributes(&attrs).unwrap();
+ let cloned_generics = generics.clone();
+ let (_, ty_generics, _) = cloned_generics.split_for_impl();
+
+ if generics.lifetimes().next().is_none() {
+ generics.params.insert(0, parse_quote! { 'i })
+ }
+
+ let lifetime = generics.lifetimes().next().unwrap().clone();
+ let (impl_generics, _, where_clause) = generics.split_for_impl();
+
+ let imp = match &data {
+ Data::Enum(data) => derive_enum(&data, &ident, &opts),
+ _ => todo!(),
+ };
+
+ let output = quote! {
+ impl #impl_generics Parse<#lifetime> for #ident #ty_generics #where_clause {
+ fn parse<'t>(input: &mut Parser<#lifetime, 't>) -> Result<#lifetime, ParserError<#lifetime>>> {
+ #imp
+ }
+ }
+ };
+
+ output.into()
+}
+
+fn derive_enum(data: &DataEnum, ident: &Ident, opts: &CssOptions) -> TokenStream2 {
+ let mut idents = Vec::new();
+ let mut non_idents = Vec::new();
+ for (index, variant) in data.variants.iter().enumerate() {
+ let name = &variant.ident;
+ let fields = variant
+ .fields
+ .iter()
+ .enumerate()
+ .map(|(index, field)| {
+ field.ident.as_ref().map_or_else(
+ || Ident::new(&format!("_{}", index), Span::call_site()),
+ |ident| ident.clone(),
+ )
+ })
+ .collect::<_>>();
+
+ let mut expr = match &variant.fields {
+ Fields::Unit => {
+ idents.push((
+ Literal::string(&variant.ident.to_string().to_case(opts.case)),
+ name.clone(),
+ ));
+ continue;
+ }
+ Fields::Named(_) => {
+ quote! {
+ return Ok(#ident::#name { #(#fields),* })
+ }
+ }
+ Fields::Unnamed(_) => {
+ quote! {
+ return Ok(#ident::#name(#(#fields),*))
+ }
+ }
+ };
+
+ // Group multiple ident branches together.
+ if !idents.is_empty() {
+ if idents.len() == 1 {
+ let (s, name) = idents.remove(0);
+ non_idents.push(quote! {
+ if input.try_parse(|input| input.expect_ident_matching(#s)).is_ok() {
+ return Ok(#ident::#name)
+ }
+ });
+ } else {
+ let matches = idents
+ .iter()
+ .map(|(s, name)| {
+ quote! {
+ #s => return Ok(#ident::#name),
+ }
+ })
+ .collect::<_>>();
+ non_idents.push(quote! {
+ {
+ let state = input.state();
+ if let Ok(ident) = input.try_parse(|input| input.expect_ident_cloned()) {
+ cssparser::match_ignore_ascii_case! { &*ident,
+ #(#matches)*
+ _ => {}
+ }
+ input.reset(&state);
+ }
+ }
+ });
+ idents.clear();
+ }
+ }
+
+ let is_last = index == data.variants.len() - 1;
+
+ for (index, field) in variant.fields.iter().enumerate().rev() {
+ let ty = &field.ty;
+ let field_name = field.ident.as_ref().map_or_else(
+ || Ident::new(&format!("_{}", index), Span::call_site()),
+ |ident| ident.clone(),
+ );
+ if is_last {
+ expr = quote! {
+ let #field_name = <#ty>::parse(input)?;
+ #expr
+ };
+ } else {
+ expr = quote! {
+ if let Ok(#field_name) = input.try_parse(<#ty>::parse) {
+ #expr
+ }
+ };
+ }
+ }
+
+ non_idents.push(expr);
+ }
+
+ let idents = if idents.is_empty() {
+ quote! {}
+ } else if idents.len() == 1 {
+ let (s, name) = idents.remove(0);
+ quote! {
+ input.expect_ident_matching(#s)?;
+ Ok(#ident::#name)
+ }
+ } else {
+ let idents = idents
+ .into_iter()
+ .map(|(s, name)| {
+ quote! {
+ #s => Ok(#ident::#name),
+ }
+ })
+ .collect::<_>>();
+ quote! {
+ let location = input.current_source_location();
+ let ident = input.expect_ident()?;
+ cssparser::match_ignore_ascii_case! { &*ident,
+ #(#idents)*
+ _ => Err(location.new_unexpected_token_error(
+ cssparser::Token::Ident(ident.clone())
+ ))
+ }
+ }
+ };
+
+ let output = quote! {
+ #(#non_idents)*
+ #idents
+ };
+
+ output.into()
+}
+
+pub struct CssOptions {
+ pub case: convert_case::Case,
+}
+
+impl CssOptions {
+ pub fn parse_attributes(attrs: &Vec) -> syn::Result {
+ for attr in attrs {
+ if attr.path.is_ident("css") {
+ let opts: CssOptions = attr.parse_args()?;
+ return Ok(opts);
+ }
+ }
+
+ Ok(CssOptions {
+ case: convert_case::Case::Kebab,
+ })
+ }
+}
+
+impl Parse for CssOptions {
+ fn parse(input: syn::parse::ParseStream) -> syn::Result {
+ let mut case = convert_case::Case::Kebab;
+ while !input.is_empty() {
+ let k: Ident = input.parse()?;
+ let _: Token![=] = input.parse()?;
+ let v: Ident = input.parse()?;
+
+ if k == "case" {
+ if v == "lower" {
+ case = convert_case::Case::Flat;
+ }
+ }
+ }
+
+ Ok(Self { case })
+ }
+}
diff --git a/derive/src/to_css.rs b/derive/src/to_css.rs
new file mode 100644
index 00000000..739a16d4
--- /dev/null
+++ b/derive/src/to_css.rs
@@ -0,0 +1,156 @@
+use convert_case::Casing;
+use proc_macro::{self, TokenStream};
+use proc_macro2::{Literal, Span, TokenStream as TokenStream2};
+use quote::quote;
+use syn::{parse_macro_input, Data, DataEnum, DeriveInput, Fields, Ident, Type};
+
+use crate::parse::CssOptions;
+
+pub fn derive_to_css(input: TokenStream) -> TokenStream {
+ let DeriveInput {
+ ident,
+ data,
+ generics,
+ attrs,
+ ..
+ } = parse_macro_input!(input);
+
+ let opts = CssOptions::parse_attributes(&attrs).unwrap();
+ let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
+
+ let imp = match &data {
+ Data::Enum(data) => derive_enum(&data, &opts),
+ _ => todo!(),
+ };
+
+ let output = quote! {
+ impl #impl_generics ToCss for #ident #ty_generics #where_clause {
+ fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError>
+ where
+ W: std::fmt::Write,
+ {
+ #imp
+ }
+ }
+ };
+
+ output.into()
+}
+
+fn derive_enum(data: &DataEnum, opts: &CssOptions) -> TokenStream2 {
+ let variants = data
+ .variants
+ .iter()
+ .map(|variant| {
+ let name = &variant.ident;
+ let fields = variant
+ .fields
+ .iter()
+ .enumerate()
+ .map(|(index, field)| {
+ field.ident.as_ref().map_or_else(
+ || Ident::new(&format!("_{}", index), Span::call_site()),
+ |ident| ident.clone(),
+ )
+ })
+ .collect::<_>>();
+
+ #[derive(PartialEq)]
+ enum NeedsSpace {
+ Yes,
+ No,
+ Maybe,
+ }
+
+ let mut needs_space = NeedsSpace::No;
+ let mut fields_iter = variant.fields.iter().zip(fields.iter()).peekable();
+ let mut writes = Vec::new();
+ let mut has_needs_space = false;
+ while let Some((field, name)) = fields_iter.next() {
+ writes.push(if fields.len() > 1 {
+ let space = match needs_space {
+ NeedsSpace::Yes => quote! { dest.write_char(' ')?; },
+ NeedsSpace::No => quote! {},
+ NeedsSpace::Maybe => {
+ has_needs_space = true;
+ quote! {
+ if needs_space {
+ dest.write_char(' ')?;
+ }
+ }
+ }
+ };
+
+ if is_option(&field.ty) {
+ needs_space = NeedsSpace::Maybe;
+ let after_space = if matches!(fields_iter.peek(), Some((field, _)) if !is_option(&field.ty)) {
+ // If the next field is non-optional, just insert the space here.
+ needs_space = NeedsSpace::No;
+ quote! { dest.write_char(' ')?; }
+ } else {
+ quote! {}
+ };
+ quote! {
+ if let Some(v) = #name {
+ #space
+ v.to_css(dest)?;
+ #after_space
+ }
+ }
+ } else {
+ needs_space = NeedsSpace::Yes;
+ quote! {
+ #space
+ #name.to_css(dest)?;
+ }
+ }
+ } else {
+ quote! { #name.to_css(dest) }
+ });
+ }
+
+ if writes.len() > 1 {
+ writes.push(quote! { Ok(()) });
+ }
+
+ if has_needs_space {
+ writes.insert(0, quote! { let mut needs_space = false });
+ }
+
+ match variant.fields {
+ Fields::Unit => {
+ let s = Literal::string(&variant.ident.to_string().to_case(opts.case));
+ quote! {
+ Self::#name => dest.write_str(#s)
+ }
+ }
+ Fields::Named(_) => {
+ quote! {
+ Self::#name { #(#fields),* } => {
+ #(#writes)*
+ }
+ }
+ }
+ Fields::Unnamed(_) => {
+ quote! {
+ Self::#name(#(#fields),*) => {
+ #(#writes)*
+ }
+ }
+ }
+ }
+ })
+ .collect::<_>>();
+
+ let output = quote! {
+ match self {
+ #(#variants),*
+ }
+ };
+
+ output.into()
+}
+
+fn is_option(ty: &Type) -> bool {
+ matches!(&ty, Type::Path(p) if p.qself.is_none() && p.path.segments.iter().next().unwrap().ident == "Option")
+}
diff --git a/derive/src/visit.rs b/derive/src/visit.rs
new file mode 100644
index 00000000..02093364
--- /dev/null
+++ b/derive/src/visit.rs
@@ -0,0 +1,292 @@
+use std::collections::HashSet;
+
+use proc_macro::{self, TokenStream};
+use proc_macro2::{Span, TokenStream as TokenStream2};
+use quote::quote;
+use syn::{
+ parse::Parse, parse_macro_input, parse_quote, Attribute, Data, DataEnum, DeriveInput, Field, Fields,
+ GenericParam, Generics, Ident, Member, Token, Type, Visibility,
+};
+
+pub fn derive_visit_children(input: TokenStream) -> TokenStream {
+ let DeriveInput {
+ ident,
+ data,
+ generics,
+ attrs,
+ ..
+ } = parse_macro_input!(input);
+
+ let options: Vec = attrs
+ .iter()
+ .filter_map(|attr| {
+ if attr.path.is_ident("visit") {
+ let opts: VisitOptions = attr.parse_args().unwrap();
+ Some(opts)
+ } else {
+ None
+ }
+ })
+ .collect();
+
+ let visit_types = if let Some(attr) = attrs.iter().find(|attr| attr.path.is_ident("visit_types")) {
+ let types: VisitTypes = attr.parse_args().unwrap();
+ let types = types.types;
+ Some(quote! { crate::visit_types!(#(#types)|*) })
+ } else {
+ None
+ };
+
+ if options.is_empty() {
+ derive(&ident, &data, &generics, None, visit_types)
+ } else {
+ options
+ .into_iter()
+ .map(|options| derive(&ident, &data, &generics, Some(options), visit_types.clone()))
+ .collect()
+ }
+}
+
+fn derive(
+ ident: &Ident,
+ data: &Data,
+ generics: &Generics,
+ options: Option,
+ visit_types: Option,
+) -> TokenStream {
+ let mut impl_generics = generics.clone();
+ let mut type_defs = quote! {};
+ let generics = if let Some(VisitOptions {
+ generic: Some(generic), ..
+ }) = &options
+ {
+ let mappings = generics
+ .type_params()
+ .zip(generic.type_params())
+ .map(|(a, b)| quote! { type #a = #b; });
+ type_defs = quote! { #(#mappings)* };
+ impl_generics.params.clear();
+ generic
+ } else {
+ &generics
+ };
+
+ if impl_generics.lifetimes().next().is_none() {
+ impl_generics.params.insert(0, parse_quote! { 'i })
+ }
+
+ let lifetime = impl_generics.lifetimes().next().unwrap().clone();
+ let t = impl_generics.type_params().find(|g| &g.ident.to_string() == "R");
+ let v = quote! { __V };
+ let t = if let Some(t) = t {
+ GenericParam::Type(t.ident.clone().into())
+ } else {
+ let t: GenericParam = parse_quote! { __T };
+ impl_generics
+ .params
+ .push(parse_quote! { #t: crate::visitor::Visit<#lifetime, __T, #v> });
+ t
+ };
+
+ impl_generics
+ .params
+ .push(parse_quote! { #v: ?Sized + crate::visitor::Visitor<#lifetime, #t> });
+
+ for ty in generics.type_params() {
+ let name = &ty.ident;
+ impl_generics.make_where_clause().predicates.push(parse_quote! {
+ #name: Visit<#lifetime, #t, #v>
+ })
+ }
+
+ let mut seen_types = HashSet::new();
+ let mut child_types = Vec::new();
+ let mut visit = Vec::new();
+ match data {
+ Data::Struct(s) => {
+ for (
+ index,
+ Field {
+ vis, ty, ident, attrs, ..
+ },
+ ) in s.fields.iter().enumerate()
+ {
+ if attrs.iter().any(|attr| attr.path.is_ident("skip_visit")) {
+ continue;
+ }
+
+ if matches!(ty, Type::Reference(_)) || !matches!(vis, Visibility::Public(..)) {
+ continue;
+ }
+
+ if visit_types.is_none() && !seen_types.contains(ty) && !skip_type(attrs) {
+ seen_types.insert(ty.clone());
+ child_types.push(quote! {
+ <#ty as Visit<#lifetime, #t, #v>>::CHILD_TYPES.bits()
+ });
+ }
+
+ let name = ident
+ .as_ref()
+ .map_or_else(|| Member::Unnamed(index.into()), |ident| Member::Named(ident.clone()));
+ visit.push(quote! { self.#name.visit(visitor)?; })
+ }
+ }
+ Data::Enum(DataEnum { variants, .. }) => {
+ let variants = variants
+ .iter()
+ .map(|variant| {
+ let name = &variant.ident;
+ let mut field_names = Vec::new();
+ let mut visit_fields = Vec::new();
+ for (index, Field { ty, ident, attrs, .. }) in variant.fields.iter().enumerate() {
+ let name = ident.as_ref().map_or_else(
+ || Ident::new(&format!("_{}", index), Span::call_site()),
+ |ident| ident.clone(),
+ );
+ field_names.push(name.clone());
+
+ if matches!(ty, Type::Reference(_)) {
+ continue;
+ }
+
+ if visit_types.is_none() && !seen_types.contains(ty) && !skip_type(attrs) && !skip_type(&variant.attrs)
+ {
+ seen_types.insert(ty.clone());
+ child_types.push(quote! {
+ <#ty as Visit<#lifetime, #t, #v>>::CHILD_TYPES.bits()
+ });
+ }
+
+ visit_fields.push(quote! { #name.visit(visitor)?; })
+ }
+
+ match variant.fields {
+ Fields::Unnamed(_) => {
+ quote! {
+ Self::#name(#(#field_names),*) => {
+ #(#visit_fields)*
+ }
+ }
+ }
+ Fields::Named(_) => {
+ quote! {
+ Self::#name { #(#field_names),* } => {
+ #(#visit_fields)*
+ }
+ }
+ }
+ Fields::Unit => quote! {},
+ }
+ })
+ .collect::();
+
+ visit.push(quote! {
+ match self {
+ #variants
+ _ => {}
+ }
+ })
+ }
+ _ => {}
+ }
+
+ if visit_types.is_none() && child_types.is_empty() {
+ child_types.push(quote! { crate::visitor::VisitTypes::empty().bits() });
+ }
+
+ let (_, ty_generics, _) = generics.split_for_impl();
+ let (impl_generics, _, where_clause) = impl_generics.split_for_impl();
+
+ let self_visit = if let Some(VisitOptions {
+ visit: Some(visit),
+ kind: Some(kind),
+ ..
+ }) = &options
+ {
+ child_types.push(quote! { crate::visitor::VisitTypes::#kind.bits() });
+
+ quote! {
+ fn visit(&mut self, visitor: &mut #v) -> Result<(), #v::Error> {
+ if visitor.visit_types().contains(crate::visitor::VisitTypes::#kind) {
+ visitor.#visit(self)
+ } else {
+ self.visit_children(visitor)
+ }
+ }
+ }
+ } else {
+ quote! {}
+ };
+
+ let child_types = visit_types.unwrap_or_else(|| {
+ quote! {
+ {
+ #type_defs
+ crate::visitor::VisitTypes::from_bits_retain(#(#child_types)|*)
+ }
+ }
+ });
+
+ let output = quote! {
+ impl #impl_generics Visit<#lifetime, #t, #v> for #ident #ty_generics #where_clause {
+ const CHILD_TYPES: crate::visitor::VisitTypes = #child_types;
+
+ #self_visit
+
+ fn visit_children(&mut self, visitor: &mut #v) -> Result<(), #v::Error> {
+ if !<#lifetime, #t, #v>>::CHILD_TYPES.intersects(visitor.visit_types()) {
+ return Ok(())
+ }
+
+ #(#visit)*
+
+ Ok(())
+ }
+ }
+ };
+
+ output.into()
+}
+
+fn skip_type(attrs: &Vec) -> bool {
+ attrs.iter().any(|attr| attr.path.is_ident("skip_type"))
+}
+
+struct VisitOptions {
+ visit: Option,
+ kind: Option,
+ generic: Option,
+}
+
+impl Parse for VisitOptions {
+ fn parse(input: syn::parse::ParseStream) -> syn::Result {
+ let (visit, kind, comma) = if input.peek(Ident) {
+ let visit: Ident = input.parse()?;
+ let _: Token![,] = input.parse()?;
+ let kind: Ident = input.parse()?;
+ let comma: Result = input.parse();
+ (Some(visit), Some(kind), comma.is_ok())
+ } else {
+ (None, None, true)
+ };
+ let generic: Option = if comma { Some(input.parse()?) } else { None };
+ Ok(Self { visit, kind, generic })
+ }
+}
+
+struct VisitTypes {
+ types: Vec,
+}
+
+impl Parse for VisitTypes {
+ fn parse(input: syn::parse::ParseStream) -> syn::Result {
+ let first: Ident = input.parse()?;
+ let mut types = vec![first];
+ while input.parse::().is_ok() {
+ let id: Ident = input.parse()?;
+ types.push(id);
+ }
+ Ok(Self { types })
+ }
+}
diff --git a/src/macros.rs b/src/macros.rs
index e102676f..7e573816 100644
--- a/src/macros.rs
+++ b/src/macros.rs
@@ -8,12 +8,12 @@ macro_rules! enum_property {
)+
}
) => {
- $(#[$outer])*
- #[derive(Debug, Clone, Copy, PartialEq)]
+ #[derive(Debug, Clone, Copy, PartialEq, Parse, ToCss)]
#[cfg_attr(feature = "visitor", derive(Visit))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(rename_all = "lowercase"))]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
+ $(#[$outer])*
$vis enum $name {
$(
$(#[$meta])*
@@ -21,50 +21,17 @@ macro_rules! enum_property {
)+
}
- impl<'i> Parse<'i> for $name {
- fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> {
- let location = input.current_source_location();
- let ident = input.expect_ident()?;
- match &ident[..] {
- $(
- s if s.eq_ignore_ascii_case(stringify!($x)) => Ok($name::$x),
- )+
- _ => Err(location.new_unexpected_token_error(
- cssparser::Token::Ident(ident.clone())
- ))
- }
- }
-
- fn parse_string(input: &'i str) -> Result<'i, ParserError<'i>>> {
- match input {
- $(
- s if s.eq_ignore_ascii_case(stringify!($x)) => Ok($name::$x),
- )+
- _ => return Err(ParseError {
- kind: ParseErrorKind::Basic(BasicParseErrorKind::UnexpectedToken(cssparser::Token::Ident(input.into()))),
- location: cssparser::SourceLocation { line: 0, column: 1 }
- })
- }
- }
- }
-
impl $name {
/// Returns a string representation of the value.
pub fn as_str(&self) -> &str {
use $name::*;
match self {
$(
- $x => const_str::convert_ascii_case!(lower, stringify!($x)),
+ $x => const_str::convert_ascii_case!(kebab, stringify!($x)),
)+
}
}
}
-
- impl ToCss for $name {
- fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> where W: std::fmt::Write {
- dest.write_str(self.as_str())
- }
- }
};
(
$(#[$outer:meta])*
@@ -92,25 +59,13 @@ macro_rules! enum_property {
fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> {
let location = input.current_source_location();
let ident = input.expect_ident()?;
- match &ident[..] {
+ cssparser::match_ignore_ascii_case! { &*ident,
$(
- s if s.eq_ignore_ascii_case($str) => Ok($name::$id),
+ $str => Ok($name::$id),
)+
_ => Err(location.new_unexpected_token_error(
cssparser::Token::Ident(ident.clone())
- ))
- }
- }
-
- fn parse_string(input: &'i str) -> Result<'i, ParserError<'i>>> {
- match input {
- $(
- s if s.eq_ignore_ascii_case($str) => Ok($name::$id),
- )+
- _ => return Err(ParseError {
- kind: ParseErrorKind::Basic(BasicParseErrorKind::UnexpectedToken(cssparser::Token::Ident(input.into()))),
- location: cssparser::SourceLocation { line: 0, column: 1 }
- })
+ )),
}
}
}
diff --git a/src/properties/align.rs b/src/properties/align.rs
index 12917c18..819e9ac4 100644
--- a/src/properties/align.rs
+++ b/src/properties/align.rs
@@ -73,13 +73,13 @@ enum_property! {
/// A [``](https://www.w3.org/TR/css-align-3/#typedef-content-distribution) value.
pub enum ContentDistribution {
/// Items are spaced evenly, with the first and last items against the edge of the container.
- "space-between": SpaceBetween,
+ SpaceBetween,
/// Items are spaced evenly, with half-size spaces at the start and end.
- "space-around": SpaceAround,
+ SpaceAround,
/// Items are spaced evenly, with full-size spaces at the start and end.
- "space-evenly": SpaceEvenly,
+ SpaceEvenly,
/// Items are stretched evenly to fill free space.
- "stretch": Stretch,
+ Stretch,
}
}
@@ -99,20 +99,20 @@ enum_property! {
/// A [``](https://www.w3.org/TR/css-align-3/#typedef-content-position) value.
pub enum ContentPosition {
/// Content is centered within the container.
- "center": Center,
+ Center,
/// Content is aligned to the start of the container.
- "start": Start,
+ Start,
/// Content is aligned to the end of the container.
- "end": End,
+ End,
/// Same as `start` when within a flexbox container.
- "flex-start": FlexStart,
+ FlexStart,
/// Same as `end` when within a flexbox container.
- "flex-end": FlexEnd,
+ FlexEnd,
}
}
/// A value for the [align-content](https://www.w3.org/TR/css-align-3/#propdef-align-content) property.
-#[derive(Debug, Clone, PartialEq)]
+#[derive(Debug, Clone, PartialEq, Parse, ToCss)]
#[cfg_attr(feature = "visitor", derive(Visit))]
#[cfg_attr(
feature = "serde",
@@ -132,54 +132,13 @@ pub enum AlignContent {
ContentDistribution(ContentDistribution),
/// A content position keyword.
ContentPosition {
- /// A content position keyword.
- value: ContentPosition,
/// An overflow alignment mode.
overflow: Option,
+ /// A content position keyword.
+ value: ContentPosition,
},
}
-impl<'i> Parse<'i> for AlignContent {
- fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> {
- if input.try_parse(|input| input.expect_ident_matching("normal")).is_ok() {
- return Ok(AlignContent::Normal);
- }
-
- if let Ok(val) = input.try_parse(BaselinePosition::parse) {
- return Ok(AlignContent::BaselinePosition(val));
- }
-
- if let Ok(val) = input.try_parse(ContentDistribution::parse) {
- return Ok(AlignContent::ContentDistribution(val));
- }
-
- let overflow = input.try_parse(OverflowPosition::parse).ok();
- let value = ContentPosition::parse(input)?;
- Ok(AlignContent::ContentPosition { overflow, value })
- }
-}
-
-impl ToCss for AlignContent {
- fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError>
- where
- W: std::fmt::Write,
- {
- match self {
- AlignContent::Normal => dest.write_str("normal"),
- AlignContent::BaselinePosition(val) => val.to_css(dest),
- AlignContent::ContentDistribution(val) => val.to_css(dest),
- AlignContent::ContentPosition { overflow, value } => {
- if let Some(overflow) = overflow {
- overflow.to_css(dest)?;
- dest.write_str(" ")?;
- }
-
- value.to_css(dest)
- }
- }
- }
-}
-
/// A value for the [justify-content](https://www.w3.org/TR/css-align-3/#propdef-justify-content) property.
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "visitor", derive(Visit))]
@@ -349,24 +308,24 @@ enum_property! {
/// A [``](https://www.w3.org/TR/css-align-3/#typedef-self-position) value.
pub enum SelfPosition {
/// Item is centered within the container.
- "center": Center,
+ Center,
/// Item is aligned to the start of the container.
- "start": Start,
+ Start,
/// Item is aligned to the end of the container.
- "end": End,
+ End,
/// Item is aligned to the edge of the container corresponding to the start side of the item.
- "self-start": SelfStart,
+ SelfStart,
/// Item is aligned to the edge of the container corresponding to the end side of the item.
- "self-end": SelfEnd,
+ SelfEnd,
/// Item is aligned to the start of the container, within flexbox layouts.
- "flex-start": FlexStart,
+ FlexStart,
/// Item is aligned to the end of the container, within flexbox layouts.
- "flex-end": FlexEnd,
+ FlexEnd,
}
}
/// A value for the [align-self](https://www.w3.org/TR/css-align-3/#align-self-property) property.
-#[derive(Debug, Clone, PartialEq)]
+#[derive(Debug, Clone, PartialEq, Parse, ToCss)]
#[cfg_attr(feature = "visitor", derive(Visit))]
#[cfg_attr(
feature = "serde",
@@ -387,59 +346,13 @@ pub enum AlignSelf {
BaselinePosition(BaselinePosition),
/// A self position keyword.
SelfPosition {
- /// A self position keyword.
- value: SelfPosition,
/// An overflow alignment mode.
overflow: Option,
+ /// A self position keyword.
+ value: SelfPosition,
},
}
-impl<'i> Parse<'i> for AlignSelf {
- fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> {
- if input.try_parse(|input| input.expect_ident_matching("auto")).is_ok() {
- return Ok(AlignSelf::Auto);
- }
-
- if input.try_parse(|input| input.expect_ident_matching("normal")).is_ok() {
- return Ok(AlignSelf::Normal);
- }
-
- if input.try_parse(|input| input.expect_ident_matching("stretch")).is_ok() {
- return Ok(AlignSelf::Stretch);
- }
-
- if let Ok(val) = input.try_parse(BaselinePosition::parse) {
- return Ok(AlignSelf::BaselinePosition(val));
- }
-
- let overflow = input.try_parse(OverflowPosition::parse).ok();
- let value = SelfPosition::parse(input)?;
- Ok(AlignSelf::SelfPosition { overflow, value })
- }
-}
-
-impl ToCss for AlignSelf {
- fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError>
- where
- W: std::fmt::Write,
- {
- match self {
- AlignSelf::Auto => dest.write_str("auto"),
- AlignSelf::Normal => dest.write_str("normal"),
- AlignSelf::Stretch => dest.write_str("stretch"),
- AlignSelf::BaselinePosition(val) => val.to_css(dest),
- AlignSelf::SelfPosition { overflow, value } => {
- if let Some(overflow) = overflow {
- overflow.to_css(dest)?;
- dest.write_str(" ")?;
- }
-
- value.to_css(dest)
- }
- }
- }
-}
-
/// A value for the [justify-self](https://www.w3.org/TR/css-align-3/#justify-self-property) property.
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "visitor", derive(Visit))]
@@ -615,7 +528,7 @@ impl ToCss for PlaceSelf {
}
/// A value for the [align-items](https://www.w3.org/TR/css-align-3/#align-items-property) property.
-#[derive(Debug, Clone, PartialEq)]
+#[derive(Debug, Clone, PartialEq, Parse, ToCss)]
#[cfg_attr(feature = "visitor", derive(Visit))]
#[cfg_attr(
feature = "serde",
@@ -634,54 +547,13 @@ pub enum AlignItems {
BaselinePosition(BaselinePosition),
/// A self position keyword.
SelfPosition {
- /// A self position keyword.
- value: SelfPosition,
/// An overflow alignment mode.
overflow: Option,
+ /// A self position keyword.
+ value: SelfPosition,
},
}
-impl<'i> Parse<'i> for AlignItems {
- fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> {
- if input.try_parse(|input| input.expect_ident_matching("normal")).is_ok() {
- return Ok(AlignItems::Normal);
- }
-
- if input.try_parse(|input| input.expect_ident_matching("stretch")).is_ok() {
- return Ok(AlignItems::Stretch);
- }
-
- if let Ok(val) = input.try_parse(BaselinePosition::parse) {
- return Ok(AlignItems::BaselinePosition(val));
- }
-
- let overflow = input.try_parse(OverflowPosition::parse).ok();
- let value = SelfPosition::parse(input)?;
- Ok(AlignItems::SelfPosition { overflow, value })
- }
-}
-
-impl ToCss for AlignItems {
- fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError>
- where
- W: std::fmt::Write,
- {
- match self {
- AlignItems::Normal => dest.write_str("normal"),
- AlignItems::Stretch => dest.write_str("stretch"),
- AlignItems::BaselinePosition(val) => val.to_css(dest),
- AlignItems::SelfPosition { overflow, value } => {
- if let Some(overflow) = overflow {
- overflow.to_css(dest)?;
- dest.write_str(" ")?;
- }
-
- value.to_css(dest)
- }
- }
- }
-}
-
/// A legacy justification keyword, as used in the `justify-items` property.
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "visitor", derive(Visit))]
@@ -925,7 +797,7 @@ impl ToCss for PlaceItems {
/// A [gap](https://www.w3.org/TR/css-align-3/#column-row-gap) value, as used in the
/// `column-gap` and `row-gap` properties.
-#[derive(Debug, Clone, PartialEq)]
+#[derive(Debug, Clone, PartialEq, Parse, ToCss)]
#[cfg_attr(feature = "visitor", derive(Visit))]
#[cfg_attr(
feature = "serde",
@@ -941,29 +813,6 @@ pub enum GapValue {
LengthPercentage(LengthPercentage),
}
-impl<'i> Parse<'i> for GapValue {
- fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> {
- if input.try_parse(|input| input.expect_ident_matching("normal")).is_ok() {
- return Ok(GapValue::Normal);
- }
-
- let val = LengthPercentage::parse(input)?;
- Ok(GapValue::LengthPercentage(val))
- }
-}
-
-impl ToCss for GapValue {
- fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError>
- where
- W: std::fmt::Write,
- {
- match self {
- GapValue::Normal => dest.write_str("normal"),
- GapValue::LengthPercentage(lp) => lp.to_css(dest),
- }
- }
-}
-
define_shorthand! {
/// A value for the [gap](https://www.w3.org/TR/css-align-3/#gap-shorthand) shorthand property.
pub struct Gap {
diff --git a/src/properties/animation.rs b/src/properties/animation.rs
index 03b76166..de83b9fa 100644
--- a/src/properties/animation.rs
+++ b/src/properties/animation.rs
@@ -13,7 +13,7 @@ use crate::traits::{Parse, PropertyHandler, Shorthand, ToCss, Zero};
use crate::values::ident::DashedIdent;
use crate::values::number::CSSNumber;
use crate::values::size::Size2D;
-use crate::values::string::CowArcStr;
+use crate::values::string::CSSString;
use crate::values::{easing::EasingFunction, ident::CustomIdent, time::Time};
#[cfg(feature = "visitor")]
use crate::visitor::Visit;
@@ -24,7 +24,7 @@ use smallvec::SmallVec;
use super::LengthPercentageOrAuto;
/// A value for the [animation-name](https://drafts.csswg.org/css-animations/#animation-name) property.
-#[derive(Debug, Clone, PartialEq)]
+#[derive(Debug, Clone, PartialEq, Parse)]
#[cfg_attr(feature = "visitor", derive(Visit))]
#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
#[cfg_attr(
@@ -41,22 +41,7 @@ pub enum AnimationName<'i> {
Ident(CustomIdent<'i>),
/// A `` name of a `@keyframes` rule.
#[cfg_attr(feature = "serde", serde(borrow))]
- String(CowArcStr<'i>),
-}
-
-impl<'i> Parse<'i> for AnimationName<'i> {
- fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> {
- if input.try_parse(|input| input.expect_ident_matching("none")).is_ok() {
- return Ok(AnimationName::None);
- }
-
- if let Ok(s) = input.try_parse(|input| input.expect_string_cloned()) {
- return Ok(AnimationName::String(s.into()));
- }
-
- let ident = CustomIdent::parse(input)?;
- Ok(AnimationName::Ident(ident))
- }
+ String(CSSString<'i>),
}
impl<'i> ToCss for AnimationName<'i> {
@@ -103,7 +88,7 @@ impl<'i> ToCss for AnimationName<'i> {
pub type AnimationNameList<'i> = SmallVec<[AnimationName<'i>; 1]>;
/// A value for the [animation-iteration-count](https://drafts.csswg.org/css-animations/#animation-iteration-count) property.
-#[derive(Debug, Clone, PartialEq)]
+#[derive(Debug, Clone, PartialEq, Parse, ToCss)]
#[cfg_attr(feature = "visitor", derive(Visit))]
#[cfg_attr(
feature = "serde",
@@ -125,40 +110,17 @@ impl Default for AnimationIterationCount {
}
}
-impl<'i> Parse<'i> for AnimationIterationCount {
- fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> {
- if input.try_parse(|input| input.expect_ident_matching("infinite")).is_ok() {
- return Ok(AnimationIterationCount::Infinite);
- }
-
- let number = CSSNumber::parse(input)?;
- return Ok(AnimationIterationCount::Number(number));
- }
-}
-
-impl ToCss for AnimationIterationCount {
- fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError>
- where
- W: std::fmt::Write,
- {
- match self {
- AnimationIterationCount::Number(val) => val.to_css(dest),
- AnimationIterationCount::Infinite => dest.write_str("infinite"),
- }
- }
-}
-
enum_property! {
/// A value for the [animation-direction](https://drafts.csswg.org/css-animations/#animation-direction) property.
pub enum AnimationDirection {
/// The animation is played as specified
- "normal": Normal,
+ Normal,
/// The animation is played in reverse.
- "reverse": Reverse,
+ Reverse,
/// The animation iterations alternate between forward and reverse.
- "alternate": Alternate,
+ Alternate,
/// The animation iterations alternate between forward and reverse, with reverse occurring first.
- "alternate-reverse": AlternateReverse,
+ AlternateReverse,
}
}
@@ -217,7 +179,7 @@ enum_property! {
}
/// A value for the [animation-timeline](https://drafts.csswg.org/css-animations-2/#animation-timeline) property.
-#[derive(Debug, Clone, PartialEq)]
+#[derive(Debug, Clone, PartialEq, Parse, ToCss)]
#[cfg_attr(feature = "visitor", derive(Visit))]
#[cfg_attr(
feature = "serde",
@@ -261,25 +223,28 @@ pub struct ScrollTimeline {
impl<'i> Parse<'i> for ScrollTimeline {
fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> {
- let mut scroller = None;
- let mut axis = None;
- loop {
- if scroller.is_none() {
- scroller = input.try_parse(Scroller::parse).ok();
- }
+ input.expect_function_matching("scroll")?;
+ input.parse_nested_block(|input| {
+ let mut scroller = None;
+ let mut axis = None;
+ loop {
+ if scroller.is_none() {
+ scroller = input.try_parse(Scroller::parse).ok();
+ }
- if axis.is_none() {
- axis = input.try_parse(ScrollAxis::parse).ok();
- if axis.is_some() {
- continue;
+ if axis.is_none() {
+ axis = input.try_parse(ScrollAxis::parse).ok();
+ if axis.is_some() {
+ continue;
+ }
}
+ break;
}
- break;
- }
- Ok(ScrollTimeline {
- scroller: scroller.unwrap_or_default(),
- axis: axis.unwrap_or_default(),
+ Ok(ScrollTimeline {
+ scroller: scroller.unwrap_or_default(),
+ axis: axis.unwrap_or_default(),
+ })
})
}
}
@@ -289,6 +254,8 @@ impl ToCss for ScrollTimeline {
where
W: std::fmt::Write,
{
+ dest.write_str("scroll(")?;
+
let mut needs_space = false;
if self.scroller != Scroller::default() {
self.scroller.to_css(dest)?;
@@ -302,7 +269,7 @@ impl ToCss for ScrollTimeline {
self.axis.to_css(dest)?;
}
- Ok(())
+ dest.write_char(')')
}
}
@@ -359,25 +326,28 @@ pub struct ViewTimeline {
impl<'i> Parse<'i> for ViewTimeline {
fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> {
- let mut axis = None;
- let mut inset = None;
- loop {
- if axis.is_none() {
- axis = input.try_parse(ScrollAxis::parse).ok();
- }
+ input.expect_function_matching("view")?;
+ input.parse_nested_block(|input| {
+ let mut axis = None;
+ let mut inset = None;
+ loop {
+ if axis.is_none() {
+ axis = input.try_parse(ScrollAxis::parse).ok();
+ }
- if inset.is_none() {
- inset = input.try_parse(Size2D::parse).ok();
- if inset.is_some() {
- continue;
+ if inset.is_none() {
+ inset = input.try_parse(Size2D::parse).ok();
+ if inset.is_some() {
+ continue;
+ }
}
+ break;
}
- break;
- }
- Ok(ViewTimeline {
- axis: axis.unwrap_or_default(),
- inset: inset.unwrap_or(Size2D(LengthPercentageOrAuto::Auto, LengthPercentageOrAuto::Auto)),
+ Ok(ViewTimeline {
+ axis: axis.unwrap_or_default(),
+ inset: inset.unwrap_or(Size2D(LengthPercentageOrAuto::Auto, LengthPercentageOrAuto::Auto)),
+ })
})
}
}
@@ -387,6 +357,7 @@ impl ToCss for ViewTimeline {
where
W: std::fmt::Write,
{
+ dest.write_str("view(")?;
let mut needs_space = false;
if self.axis != ScrollAxis::default() {
self.axis.to_css(dest)?;
@@ -400,56 +371,7 @@ impl ToCss for ViewTimeline {
self.inset.to_css(dest)?;
}
- Ok(())
- }
-}
-
-impl<'i> Parse<'i> for AnimationTimeline<'i> {
- fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> {
- if input.try_parse(|i| i.expect_ident_matching("auto")).is_ok() {
- return Ok(AnimationTimeline::Auto);
- }
-
- if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() {
- return Ok(AnimationTimeline::None);
- }
-
- if let Ok(name) = input.try_parse(DashedIdent::parse) {
- return Ok(AnimationTimeline::DashedIdent(name));
- }
-
- let location = input.current_source_location();
- let f = input.expect_function()?.clone();
- input.parse_nested_block(move |input| {
- match_ignore_ascii_case! { &f,
- "scroll" => ScrollTimeline::parse(input).map(AnimationTimeline::Scroll),
- "view" => ViewTimeline::parse(input).map(AnimationTimeline::View),
- _ => Err(location.new_custom_error(ParserError::UnexpectedToken(crate::properties::custom::Token::Function(f.into()))))
- }
- })
- }
-}
-
-impl<'i> ToCss for AnimationTimeline<'i> {
- fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError>
- where
- W: std::fmt::Write,
- {
- match self {
- AnimationTimeline::Auto => dest.write_str("auto"),
- AnimationTimeline::None => dest.write_str("none"),
- AnimationTimeline::DashedIdent(name) => name.to_css(dest),
- AnimationTimeline::Scroll(scroll) => {
- dest.write_str("scroll(")?;
- scroll.to_css(dest)?;
- dest.write_char(')')
- }
- AnimationTimeline::View(view) => {
- dest.write_str("view(")?;
- view.to_css(dest)?;
- dest.write_char(')')
- }
- }
+ dest.write_char(')')
}
}
@@ -535,7 +457,7 @@ impl<'i> ToCss for Animation<'i> {
{
match &self.name {
AnimationName::None => {}
- AnimationName::Ident(CustomIdent(name)) | AnimationName::String(name) => {
+ AnimationName::Ident(CustomIdent(name)) | AnimationName::String(CSSString(name)) => {
if !self.duration.is_zero() || !self.delay.is_zero() {
self.duration.to_css(dest)?;
dest.write_char(' ')?;
@@ -709,7 +631,7 @@ impl<'i> PropertyHandler<'i> for AnimationHandler<'i> {
}
}
TokenOrValue::Token(Token::String(s)) => {
- *token = TokenOrValue::AnimationName(AnimationName::String(s.clone()));
+ *token = TokenOrValue::AnimationName(AnimationName::String(CSSString(s.clone())));
}
_ => {}
}
diff --git a/src/properties/background.rs b/src/properties/background.rs
index 1dc1aeb3..f47821db 100644
--- a/src/properties/background.rs
+++ b/src/properties/background.rs
@@ -113,13 +113,13 @@ enum_property! {
/// See [BackgroundRepeat](BackgroundRepeat).
pub enum BackgroundRepeatKeyword {
/// The image is repeated in this direction.
- "repeat": Repeat,
+ Repeat,
/// The image is repeated so that it fits, and then spaced apart evenly.
- "space": Space,
+ Space,
/// The image is scaled so that it repeats an even number of times.
- "round": Round,
+ Round,
/// The image is placed once and not repeated in this direction.
- "no-repeat": NoRepeat,
+ NoRepeat,
}
}
@@ -214,11 +214,11 @@ enum_property! {
/// A value for the [background-origin](https://www.w3.org/TR/css-backgrounds-3/#background-origin) property.
pub enum BackgroundOrigin {
/// The position is relative to the border box.
- "border-box": BorderBox,
+ BorderBox,
/// The position is relative to the padding box.
- "padding-box": PaddingBox,
+ PaddingBox,
/// The position is relative to the content box.
- "content-box": ContentBox,
+ ContentBox,
}
}
@@ -226,15 +226,15 @@ enum_property! {
/// A value for the [background-clip](https://drafts.csswg.org/css-backgrounds-4/#background-clip) property.
pub enum BackgroundClip {
/// The background is clipped to the border box.
- "border-box": BorderBox,
+ BorderBox,
/// The background is clipped to the padding box.
- "padding-box": PaddingBox,
+ PaddingBox,
/// The background is clipped to the content box.
- "content-box": ContentBox,
+ ContentBox,
/// The background is clipped to the area painted by the border.
- "border": Border,
+ Border,
/// The background is clipped to the text content of the element.
- "text": Text,
+ Text,
}
}
diff --git a/src/properties/border.rs b/src/properties/border.rs
index ee2c4102..9308eebb 100644
--- a/src/properties/border.rs
+++ b/src/properties/border.rs
@@ -23,7 +23,7 @@ use crate::visitor::Visit;
use cssparser::*;
/// A value for the [border-width](https://www.w3.org/TR/css-backgrounds-3/#border-width) property.
-#[derive(Debug, Clone, PartialEq)]
+#[derive(Debug, Clone, PartialEq, Parse, ToCss)]
#[cfg_attr(feature = "visitor", derive(Visit))]
#[cfg_attr(
feature = "serde",
@@ -58,37 +58,6 @@ impl IsCompatible for BorderSideWidth {
}
}
-impl<'i> Parse<'i> for BorderSideWidth {
- fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> {
- if let Ok(length) = input.try_parse(|i| Length::parse(i)) {
- return Ok(BorderSideWidth::Length(length));
- }
- let location = input.current_source_location();
- let ident = input.expect_ident()?;
- match_ignore_ascii_case! { &ident,
- "thin" => Ok(BorderSideWidth::Thin),
- "medium" => Ok(BorderSideWidth::Medium),
- "thick" => Ok(BorderSideWidth::Thick),
- _ => return Err(location.new_unexpected_token_error(Token::Ident(ident.clone())))
- }
- }
-}
-
-impl ToCss for BorderSideWidth {
- fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError>
- where
- W: std::fmt::Write,
- {
- use BorderSideWidth::*;
- match self {
- Thin => dest.write_str("thin"),
- Medium => dest.write_str("medium"),
- Thick => dest.write_str("thick"),
- Length(length) => length.to_css(dest),
- }
- }
-}
-
enum_property! {
/// A [``](https://drafts.csswg.org/css-backgrounds/#typedef-line-style) value, used in the `border-style` property.
pub enum LineStyle {
diff --git a/src/properties/border_image.rs b/src/properties/border_image.rs
index 8f444efe..300d458e 100644
--- a/src/properties/border_image.rs
+++ b/src/properties/border_image.rs
@@ -102,7 +102,7 @@ impl IsCompatible for BorderImageRepeat {
}
/// A value for the [border-image-width](https://www.w3.org/TR/css-backgrounds-3/#border-image-width) property.
-#[derive(Debug, Clone, PartialEq)]
+#[derive(Debug, Clone, PartialEq, Parse, ToCss)]
#[cfg_attr(feature = "visitor", derive(Visit))]
#[cfg_attr(
feature = "serde",
@@ -126,38 +126,6 @@ impl Default for BorderImageSideWidth {
}
}
-impl<'i> Parse<'i> for BorderImageSideWidth {
- fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> {
- if input.try_parse(|i| i.expect_ident_matching("auto")).is_ok() {
- return Ok(BorderImageSideWidth::Auto);
- }
-
- if let Ok(number) = input.try_parse(CSSNumber::parse) {
- return Ok(BorderImageSideWidth::Number(number));
- }
-
- if let Ok(percent) = input.try_parse(|input| LengthPercentage::parse(input)) {
- return Ok(BorderImageSideWidth::LengthPercentage(percent));
- }
-
- Err(input.new_error_for_next_token())
- }
-}
-
-impl ToCss for BorderImageSideWidth {
- fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError>
- where
- W: std::fmt::Write,
- {
- use BorderImageSideWidth::*;
- match self {
- Auto => dest.write_str("auto"),
- LengthPercentage(l) => l.to_css(dest),
- Number(n) => n.to_css(dest),
- }
- }
-}
-
impl IsCompatible for BorderImageSideWidth {
fn is_compatible(&self, browsers: Browsers) -> bool {
match self {
diff --git a/src/properties/contain.rs b/src/properties/contain.rs
index 761e2d92..1c57bab4 100644
--- a/src/properties/contain.rs
+++ b/src/properties/contain.rs
@@ -25,11 +25,11 @@ enum_property! {
pub enum ContainerType {
/// The element is not a query container for any container size queries,
/// but remains a query container for container style queries.
- "normal": Normal,
+ Normal,
/// Establishes a query container for container size queries on the container’s own inline axis.
- "inline-size": InlineSize,
+ InlineSize,
/// Establishes a query container for container size queries on both the inline and block axis.
- "size": Size,
+ Size,
}
}
diff --git a/src/properties/custom.rs b/src/properties/custom.rs
index 3198422b..40b522e8 100644
--- a/src/properties/custom.rs
+++ b/src/properties/custom.rs
@@ -1329,25 +1329,25 @@ enum_property! {
/// A UA-defined environment variable name.
pub enum UAEnvironmentVariable {
/// The safe area inset from the top of the viewport.
- "safe-area-inset-top": SafeAreaInsetTop,
+ SafeAreaInsetTop,
/// The safe area inset from the right of the viewport.
- "safe-area-inset-right": SafeAreaInsetRight,
+ SafeAreaInsetRight,
/// The safe area inset from the bottom of the viewport.
- "safe-area-inset-bottom": SafeAreaInsetBottom,
+ SafeAreaInsetBottom,
/// The safe area inset from the left of the viewport.
- "safe-area-inset-left": SafeAreaInsetLeft,
+ SafeAreaInsetLeft,
/// The viewport segment width.
- "viewport-segment-width": ViewportSegmentWidth,
+ ViewportSegmentWidth,
/// The viewport segment height.
- "viewport-segment-height": ViewportSegmentHeight,
+ ViewportSegmentHeight,
/// The viewport segment top position.
- "viewport-segment-top": ViewportSegmentTop,
+ ViewportSegmentTop,
/// The viewport segment left position.
- "viewport-segment-left": ViewportSegmentLeft,
+ ViewportSegmentLeft,
/// The viewport segment bottom position.
- "viewport-segment-bottom": ViewportSegmentBottom,
+ ViewportSegmentBottom,
/// The viewport segment right position.
- "viewport-segment-right": ViewportSegmentRight,
+ ViewportSegmentRight,
}
}
diff --git a/src/properties/display.rs b/src/properties/display.rs
index d5a53d13..3df7c819 100644
--- a/src/properties/display.rs
+++ b/src/properties/display.rs
@@ -18,9 +18,9 @@ enum_property! {
/// A [``](https://drafts.csswg.org/css-display-3/#typedef-display-outside) value.
#[allow(missing_docs)]
pub enum DisplayOutside {
- "block": Block,
- "inline": Inline,
- "run-in": RunIn,
+ Block,
+ Inline,
+ RunIn,
}
}
@@ -309,25 +309,25 @@ enum_property! {
/// See [Display](Display).
#[allow(missing_docs)]
pub enum DisplayKeyword {
- "none": None,
- "contents": Contents,
- "table-row-group": TableRowGroup,
- "table-header-group": TableHeaderGroup,
- "table-footer-group": TableFooterGroup,
- "table-row": TableRow,
- "table-cell": TableCell,
- "table-column-group": TableColumnGroup,
- "table-column": TableColumn,
- "table-caption": TableCaption,
- "ruby-base": RubyBase,
- "ruby-text": RubyText,
- "ruby-base-container": RubyBaseContainer,
- "ruby-text-container": RubyTextContainer,
+ None,
+ Contents,
+ TableRowGroup,
+ TableHeaderGroup,
+ TableFooterGroup,
+ TableRow,
+ TableCell,
+ TableColumnGroup,
+ TableColumn,
+ TableCaption,
+ RubyBase,
+ RubyText,
+ RubyBaseContainer,
+ RubyTextContainer,
}
}
/// A value for the [display](https://drafts.csswg.org/css-display-3/#the-display-properties) property.
-#[derive(Debug, Clone, PartialEq)]
+#[derive(Debug, Clone, PartialEq, Parse, ToCss)]
#[cfg_attr(feature = "visitor", derive(Visit))]
#[cfg_attr(
feature = "serde",
@@ -347,29 +347,6 @@ pub enum Display {
Pair(DisplayPair),
}
-impl<'i> Parse<'i> for Display {
- fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> {
- if let Ok(pair) = input.try_parse(DisplayPair::parse) {
- return Ok(Display::Pair(pair));
- }
-
- let keyword = DisplayKeyword::parse(input)?;
- Ok(Display::Keyword(keyword))
- }
-}
-
-impl ToCss for Display {
- fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError>
- where
- W: std::fmt::Write,
- {
- match self {
- Display::Keyword(keyword) => keyword.to_css(dest),
- Display::Pair(pair) => pair.to_css(dest),
- }
- }
-}
-
enum_property! {
/// A value for the [visibility](https://drafts.csswg.org/css-display-3/#visibility) property.
pub enum Visibility {
diff --git a/src/properties/flex.rs b/src/properties/flex.rs
index cf631993..e885c597 100644
--- a/src/properties/flex.rs
+++ b/src/properties/flex.rs
@@ -25,13 +25,13 @@ enum_property! {
/// A value for the [flex-direction](https://www.w3.org/TR/2018/CR-css-flexbox-1-20181119/#propdef-flex-direction) property.
pub enum FlexDirection {
/// Flex items are laid out in a row.
- "row": Row,
+ Row,
/// Flex items are laid out in a row, and reversed.
- "row-reverse": RowReverse,
+ RowReverse,
/// Flex items are laid out in a column.
- "column": Column,
+ Column,
/// Flex items are laid out in a column, and reversed.
- "column-reverse": ColumnReverse,
+ ColumnReverse,
}
}
@@ -233,13 +233,13 @@ enum_property! {
/// Partially equivalent to `flex-direction` in the standard syntax.
pub enum BoxOrient {
/// Items are laid out horizontally.
- "horizontal": Horizontal,
+ Horizontal,
/// Items are laid out vertically.
- "vertical": Vertical,
+ Vertical,
/// Items are laid out along the inline axis, according to the writing direction.
- "inline-axis": InlineAxis,
+ InlineAxis,
/// Items are laid out along the block axis, according to the writing direction.
- "block-axis": BlockAxis,
+ BlockAxis,
}
}
diff --git a/src/properties/font.rs b/src/properties/font.rs
index 84396cdb..377ea77c 100644
--- a/src/properties/font.rs
+++ b/src/properties/font.rs
@@ -20,7 +20,7 @@ use crate::visitor::Visit;
use cssparser::*;
/// A value for the [font-weight](https://www.w3.org/TR/css-fonts-4/#font-weight-prop) property.
-#[derive(Debug, Clone, PartialEq)]
+#[derive(Debug, Clone, PartialEq, Parse, ToCss)]
#[cfg_attr(feature = "visitor", derive(Visit))]
#[cfg_attr(
feature = "serde",
@@ -44,38 +44,6 @@ impl Default for FontWeight {
}
}
-impl<'i> Parse<'i> for FontWeight {
- fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> {
- if let Ok(val) = input.try_parse(AbsoluteFontWeight::parse) {
- return Ok(FontWeight::Absolute(val));
- }
-
- let location = input.current_source_location();
- let ident = input.expect_ident()?;
- match_ignore_ascii_case! { &*ident,
- "bolder" => Ok(FontWeight::Bolder),
- "lighter" => Ok(FontWeight::Lighter),
- _ => Err(location.new_unexpected_token_error(
- cssparser::Token::Ident(ident.clone())
- ))
- }
- }
-}
-
-impl ToCss for FontWeight {
- fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError>
- where
- W: std::fmt::Write,
- {
- use FontWeight::*;
- match self {
- Absolute(val) => val.to_css(dest),
- Bolder => dest.write_str("bolder"),
- Lighter => dest.write_str("lighter"),
- }
- }
-}
-
impl IsCompatible for FontWeight {
fn is_compatible(&self, browsers: crate::targets::Browsers) -> bool {
match self {
@@ -89,7 +57,7 @@ impl IsCompatible for FontWeight {
/// as used in the `font-weight` property.
///
/// See [FontWeight](FontWeight).
-#[derive(Debug, Clone, PartialEq)]
+#[derive(Debug, Clone, PartialEq, Parse)]
#[cfg_attr(feature = "visitor", derive(Visit))]
#[cfg_attr(
feature = "serde",
@@ -112,24 +80,6 @@ impl Default for AbsoluteFontWeight {
}
}
-impl<'i> Parse<'i> for AbsoluteFontWeight {
- fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> {
- if let Ok(val) = input.try_parse(CSSNumber::parse) {
- return Ok(AbsoluteFontWeight::Weight(val));
- }
-
- let location = input.current_source_location();
- let ident = input.expect_ident()?;
- match_ignore_ascii_case! { &*ident,
- "normal" => Ok(AbsoluteFontWeight::Normal),
- "bold" => Ok(AbsoluteFontWeight::Bold),
- _ => Err(location.new_unexpected_token_error(
- cssparser::Token::Ident(ident.clone())
- ))
- }
- }
-}
-
impl ToCss for AbsoluteFontWeight {
fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError>
where
@@ -197,7 +147,7 @@ enum_property! {
}
/// A value for the [font-size](https://www.w3.org/TR/css-fonts-4/#font-size-prop) property.
-#[derive(Debug, Clone, PartialEq)]
+#[derive(Debug, Clone, PartialEq, Parse, ToCss)]
#[cfg_attr(feature = "visitor", derive(Visit))]
#[cfg_attr(
feature = "serde",
@@ -215,35 +165,6 @@ pub enum FontSize {
Relative(RelativeFontSize),
}
-impl<'i> Parse<'i> for FontSize {
- fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> {
- if let Ok(val) = input.try_parse(LengthPercentage::parse) {
- return Ok(FontSize::Length(val));
- }
-
- if let Ok(val) = input.try_parse(AbsoluteFontSize::parse) {
- return Ok(FontSize::Absolute(val));
- }
-
- let val = RelativeFontSize::parse(input)?;
- Ok(FontSize::Relative(val))
- }
-}
-
-impl ToCss for FontSize {
- fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError>
- where
- W: std::fmt::Write,
- {
- use FontSize::*;
- match self {
- Absolute(val) => val.to_css(dest),
- Length(val) => val.to_css(dest),
- Relative(val) => val.to_css(dest),
- }
- }
-}
-
impl IsCompatible for FontSize {
fn is_compatible(&self, browsers: crate::targets::Browsers) -> bool {
match self {
@@ -309,7 +230,7 @@ impl Into for &FontStretchKeyword {
}
/// A value for the [font-stretch](https://www.w3.org/TR/css-fonts-4/#font-stretch-prop) property.
-#[derive(Debug, Clone, PartialEq)]
+#[derive(Debug, Clone, PartialEq, Parse)]
#[cfg_attr(feature = "visitor", derive(Visit))]
#[cfg_attr(
feature = "serde",
@@ -331,17 +252,6 @@ impl Default for FontStretch {
}
}
-impl<'i> Parse<'i> for FontStretch {
- fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> {
- if let Ok(val) = input.try_parse(Percentage::parse) {
- return Ok(FontStretch::Percentage(val));
- }
-
- let keyword = FontStretchKeyword::parse(input)?;
- Ok(FontStretch::Keyword(keyword))
- }
-}
-
impl Into for &FontStretch {
fn into(self) -> Percentage {
match self {
@@ -601,19 +511,19 @@ enum_property! {
/// A value for the [font-variant-caps](https://www.w3.org/TR/css-fonts-4/#font-variant-caps-prop) property.
pub enum FontVariantCaps {
/// No special capitalization features are applied.
- "normal": Normal,
+ Normal,
/// The small capitals feature is used for lower case letters.
- "small-caps": SmallCaps,
+ SmallCaps,
/// Small capitals are used for both upper and lower case letters.
- "all-small-caps": AllSmallCaps,
+ AllSmallCaps,
/// Petite capitals are used.
- "petite-caps": PetiteCaps,
+ PetiteCaps,
/// Petite capitals are used for both upper and lower case letters.
- "all-petite-caps": AllPetiteCaps,
+ AllPetiteCaps,
/// Enables display of mixture of small capitals for uppercase letters with normal lowercase letters.
- "unicase": Unicase,
+ Unicase,
/// Uses titling capitals.
- "titling-caps": TitlingCaps,
+ TitlingCaps,
}
}
@@ -644,7 +554,7 @@ impl IsCompatible for FontVariantCaps {
}
/// A value for the [line-height](https://www.w3.org/TR/2020/WD-css-inline-3-20200827/#propdef-line-height) property.
-#[derive(Debug, Clone, PartialEq)]
+#[derive(Debug, Clone, PartialEq, Parse, ToCss)]
#[cfg_attr(feature = "visitor", derive(Visit))]
#[cfg_attr(
feature = "serde",
@@ -668,33 +578,6 @@ impl Default for LineHeight {
}
}
-impl<'i> Parse<'i> for LineHeight {
- fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> {
- if input.try_parse(|input| input.expect_ident_matching("normal")).is_ok() {
- return Ok(LineHeight::Normal);
- }
-
- if let Ok(val) = input.try_parse(CSSNumber::parse) {
- return Ok(LineHeight::Number(val));
- }
-
- Ok(LineHeight::Length(LengthPercentage::parse(input)?))
- }
-}
-
-impl ToCss for LineHeight {
- fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError>
- where
- W: std::fmt::Write,
- {
- match self {
- LineHeight::Normal => dest.write_str("normal"),
- LineHeight::Number(val) => val.to_css(dest),
- LineHeight::Length(val) => val.to_css(dest),
- }
- }
-}
-
impl IsCompatible for LineHeight {
fn is_compatible(&self, browsers: crate::targets::Browsers) -> bool {
match self {
@@ -708,27 +591,27 @@ enum_property! {
/// A keyword for the [vertical align](https://drafts.csswg.org/css2/#propdef-vertical-align) property.
pub enum VerticalAlignKeyword {
/// Align the baseline of the box with the baseline of the parent box.
- "baseline": Baseline,
+ Baseline,
/// Lower the baseline of the box to the proper position for subscripts of the parent’s box.
- "sub": Sub,
+ Sub,
/// Raise the baseline of the box to the proper position for superscripts of the parent’s box.
- "super": Super,
+ Super,
/// Align the top of the aligned subtree with the top of the line box.
- "top": Top,
+ Top,
/// Align the top of the box with the top of the parent’s content area.
- "text-top": TextTop,
+ TextTop,
/// Align the vertical midpoint of the box with the baseline of the parent box plus half the x-height of the parent.
- "middle": Middle,
+ Middle,
/// Align the bottom of the aligned subtree with the bottom of the line box.
- "bottom": Bottom,
+ Bottom,
/// Align the bottom of the box with the bottom of the parent’s content area.
- "text-bottom": TextBottom,
+ TextBottom,
}
}
/// A value for the [vertical align](https://drafts.csswg.org/css2/#propdef-vertical-align) property.
// TODO: there is a more extensive spec in CSS3 but it doesn't seem any browser implements it? https://www.w3.org/TR/css-inline-3/#transverse-alignment
-#[derive(Debug, Clone, PartialEq)]
+#[derive(Debug, Clone, PartialEq, Parse, ToCss)]
#[cfg_attr(feature = "visitor", derive(Visit))]
#[cfg_attr(
feature = "serde",
@@ -744,29 +627,6 @@ pub enum VerticalAlign {
Length(LengthPercentage),
}
-impl<'i> Parse<'i> for VerticalAlign {
- fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> {
- if let Ok(len) = input.try_parse(LengthPercentage::parse) {
- return Ok(VerticalAlign::Length(len));
- }
-
- let kw = VerticalAlignKeyword::parse(input)?;
- Ok(VerticalAlign::Keyword(kw))
- }
-}
-
-impl ToCss for VerticalAlign {
- fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError>
- where
- W: std::fmt::Write,
- {
- match self {
- VerticalAlign::Keyword(kw) => kw.to_css(dest),
- VerticalAlign::Length(len) => len.to_css(dest),
- }
- }
-}
-
define_shorthand! {
/// A value for the [font](https://www.w3.org/TR/css-fonts-4/#font-prop) shorthand property.
pub struct Font<'i> {
diff --git a/src/properties/grid.rs b/src/properties/grid.rs
index 912f563d..6e706319 100644
--- a/src/properties/grid.rs
+++ b/src/properties/grid.rs
@@ -24,7 +24,7 @@ use crate::serialization::ValueWrapper;
/// A [track sizing](https://drafts.csswg.org/css-grid-2/#track-sizing) value
/// for the `grid-template-rows` and `grid-template-columns` properties.
-#[derive(Debug, Clone, PartialEq)]
+#[derive(Debug, Clone, PartialEq, Parse, ToCss)]
#[cfg_attr(feature = "visitor", derive(Visit))]
#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
#[cfg_attr(
@@ -178,7 +178,7 @@ pub struct TrackRepeat<'i> {
/// used in the `repeat()` function.
///
/// See [TrackRepeat](TrackRepeat).
-#[derive(Debug, Clone, PartialEq)]
+#[derive(Debug, Clone, PartialEq, Parse, ToCss)]
#[cfg_attr(feature = "visitor", derive(Visit))]
#[cfg_attr(
feature = "serde",
@@ -365,37 +365,6 @@ impl<'i> ToCss for TrackRepeat<'i> {
}
}
-impl<'i> Parse<'i> for RepeatCount {
- fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> {
- if let Ok(num) = input.try_parse(CSSInteger::parse) {
- return Ok(RepeatCount::Number(num));
- }
-
- let location = input.current_source_location();
- let ident = input.expect_ident()?;
- match_ignore_ascii_case! { &*ident,
- "auto-fill" => Ok(RepeatCount::AutoFill),
- "auto-fit" => Ok(RepeatCount::AutoFit),
- _ => Err(location.new_unexpected_token_error(
- cssparser::Token::Ident(ident.clone())
- ))
- }
- }
-}
-
-impl ToCss for RepeatCount {
- fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError>
- where
- W: std::fmt::Write,
- {
- match self {
- RepeatCount::AutoFill => dest.write_str("auto-fill"),
- RepeatCount::AutoFit => dest.write_str("auto-fit"),
- RepeatCount::Number(num) => num.to_css(dest),
- }
- }
-}
-
fn parse_line_names<'i, 't>(
input: &mut Parser<'i, 't>,
) -> Result<'i>, ParseError<'i, ParserError<'i>>> {
@@ -519,29 +488,6 @@ impl<'i> TrackList<'i> {
}
}
-impl<'i> Parse<'i> for TrackSizing<'i> {
- fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> {
- if input.try_parse(|input| input.expect_ident_matching("none")).is_ok() {
- return Ok(TrackSizing::None);
- }
-
- let track_list = TrackList::parse(input)?;
- Ok(TrackSizing::TrackList(track_list))
- }
-}
-
-impl<'i> ToCss for TrackSizing<'i> {
- fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError>
- where
- W: std::fmt::Write,
- {
- match self {
- TrackSizing::None => dest.write_str("none"),
- TrackSizing::TrackList(list) => list.to_css(dest),
- }
- }
-}
-
impl<'i> TrackSizing<'i> {
fn is_explicit(&self) -> bool {
match self {
diff --git a/src/properties/list.rs b/src/properties/list.rs
index 4112ddfe..54a0c052 100644
--- a/src/properties/list.rs
+++ b/src/properties/list.rs
@@ -15,7 +15,7 @@ use crate::visitor::Visit;
use cssparser::*;
/// A value for the [list-style-type](https://www.w3.org/TR/2020/WD-css-lists-3-20201117/#text-markers) property.
-#[derive(Debug, Clone, PartialEq)]
+#[derive(Debug, Clone, PartialEq, Parse, ToCss)]
#[cfg_attr(feature = "visitor", derive(Visit))]
#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
#[cfg_attr(
@@ -40,34 +40,6 @@ impl Default for ListStyleType<'_> {
}
}
-impl<'i> Parse<'i> for ListStyleType<'i> {
- fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> {
- if input.try_parse(|input| input.expect_ident_matching("none")).is_ok() {
- return Ok(ListStyleType::None);
- }
-
- if let Ok(val) = input.try_parse(CounterStyle::parse) {
- return Ok(ListStyleType::CounterStyle(val));
- }
-
- let s = CSSString::parse(input)?;
- Ok(ListStyleType::String(s))
- }
-}
-
-impl ToCss for ListStyleType<'_> {
- fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError>
- where
- W: std::fmt::Write,
- {
- match self {
- ListStyleType::None => dest.write_str("none"),
- ListStyleType::CounterStyle(style) => style.to_css(dest),
- ListStyleType::String(s) => s.to_css(dest),
- }
- }
-}
-
impl IsCompatible for ListStyleType<'_> {
fn is_compatible(&self, browsers: Browsers) -> bool {
match self {
@@ -117,7 +89,7 @@ macro_rules! counter_styles {
$vis:vis enum $name:ident {
$(
$(#[$meta: meta])*
- $str: literal: $id: ident,
+ $id: ident,
)+
}
) => {
@@ -127,7 +99,7 @@ macro_rules! counter_styles {
pub enum PredefinedCounterStyle {
$(
$(#[$meta])*
- $str: $id,
+ $id,
)+
}
}
@@ -151,68 +123,68 @@ counter_styles! {
#[allow(missing_docs)]
pub enum PredefinedCounterStyle {
// https://www.w3.org/TR/css-counter-styles-3/#simple-numeric
- "decimal": Decimal,
- "decimal-leading-zero": DecimalLeadingZero,
- "arabic-indic": ArabicIndic,
- "armenian": Armenian,
- "upper-armenian": UpperArmenian,
- "lower-armenian": LowerArmenian,
- "bengali": Bengali,
- "cambodian": Cambodian,
- "khmer": Khmer,
- "cjk-decimal": CjkDecimal,
- "devanagari": Devanagari,
- "georgian": Georgian,
- "gujarati": Gujarati,
- "gurmukhi": Gurmukhi,
- "hebrew": Hebrew,
- "kannada": Kannada,
- "lao": Lao,
- "malayalam": Malayalam,
- "mongolian": Mongolian,
- "myanmar": Myanmar,
- "oriya": Oriya,
- "persian": Persian,
- "lower-roman": LowerRoman,
- "upper-roman": UpperRoman,
- "tamil": Tamil,
- "telugu": Telugu,
- "thai": Thai,
- "tibetan": Tibetan,
+ Decimal,
+ DecimalLeadingZero,
+ ArabicIndic,
+ Armenian,
+ UpperArmenian,
+ LowerArmenian,
+ Bengali,
+ Cambodian,
+ Khmer,
+ CjkDecimal,
+ Devanagari,
+ Georgian,
+ Gujarati,
+ Gurmukhi,
+ Hebrew,
+ Kannada,
+ Lao,
+ Malayalam,
+ Mongolian,
+ Myanmar,
+ Oriya,
+ Persian,
+ LowerRoman,
+ UpperRoman,
+ Tamil,
+ Telugu,
+ Thai,
+ Tibetan,
// https://www.w3.org/TR/css-counter-styles-3/#simple-alphabetic
- "lower-alpha": LowerAlpha,
- "lower-latin": LowerLatin,
- "upper-alpha": UpperAlpha,
- "upper-latin": UpperLatin,
- "lower-greek": LowerGreek,
- "hiragana": Hiragana,
- "hiragana-iroha": HiraganaIroha,
- "katakana": Katakana,
- "katakana-iroha": KatakanaIroha,
+ LowerAlpha,
+ LowerLatin,
+ UpperAlpha,
+ UpperLatin,
+ LowerGreek,
+ Hiragana,
+ HiraganaIroha,
+ Katakana,
+ KatakanaIroha,
// https://www.w3.org/TR/css-counter-styles-3/#simple-symbolic
- "disc": Disc,
- "circle": Circle,
- "square": Square,
- "disclosure-open": DisclosureOpen,
- "disclosure-closed": DisclosureClosed,
+ Disc,
+ Circle,
+ Square,
+ DisclosureOpen,
+ DisclosureClosed,
// https://www.w3.org/TR/css-counter-styles-3/#simple-fixed
- "cjk-earthly-branch": CjkEarthlyBranch,
- "cjk-heavenly-stem": CjkHeavenlyStem,
+ CjkEarthlyBranch,
+ CjkHeavenlyStem,
// https://www.w3.org/TR/css-counter-styles-3/#complex-cjk
- "japanese-informal": JapaneseInformal,
- "japanese-formal": JapaneseFormal,
- "korean-hangul-formal": KoreanHangulFormal,
- "korean-hanja-informal": KoreanHanjaInformal,
- "korean-hanja-formal": KoreanHanjaFormal,
- "simp-chinese-informal": SimpChineseInformal,
- "simp-chinese-formal": SimpChineseFormal,
- "trad-chinese-informal": TradChineseInformal,
- "trad-chinese-formal": TradChineseFormal,
- "ethiopic-numeric": EthiopicNumeric,
+ JapaneseInformal,
+ JapaneseFormal,
+ KoreanHangulFormal,
+ KoreanHanjaInformal,
+ KoreanHanjaFormal,
+ SimpChineseInformal,
+ SimpChineseFormal,
+ TradChineseInformal,
+ TradChineseFormal,
+ EthiopicNumeric,
}
}
@@ -309,7 +281,7 @@ impl Default for SymbolsType {
/// `symbols()` function.
///
/// See [CounterStyle](CounterStyle).
-#[derive(Debug, Clone, PartialEq)]
+#[derive(Debug, Clone, PartialEq, Parse, ToCss)]
#[cfg_attr(feature = "visitor", derive(Visit))]
#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
#[cfg_attr(
@@ -326,29 +298,6 @@ pub enum Symbol<'i> {
Image(Image<'i>),
}
-impl<'i> Parse<'i> for Symbol<'i> {
- fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> {
- if let Ok(img) = input.try_parse(Image::parse) {
- return Ok(Symbol::Image(img));
- }
-
- let s = CSSString::parse(input)?;
- Ok(Symbol::String(s.into()))
- }
-}
-
-impl<'i> ToCss for Symbol<'i> {
- fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError>
- where
- W: std::fmt::Write,
- {
- match self {
- Symbol::String(s) => s.to_css(dest),
- Symbol::Image(img) => img.to_css(dest),
- }
- }
-}
-
enum_property! {
/// A value for the [list-style-position](https://www.w3.org/TR/2020/WD-css-lists-3-20201117/#list-style-position-property) property.
pub enum ListStylePosition {
@@ -375,8 +324,8 @@ enum_property! {
/// A value for the [marker-side](https://www.w3.org/TR/2020/WD-css-lists-3-20201117/#marker-side) property.
#[allow(missing_docs)]
pub enum MarkerSide {
- "match-self": MatchSelf,
- "match-parent": MatchParent,
+ MatchSelf,
+ MatchParent,
}
}
diff --git a/src/properties/masking.rs b/src/properties/masking.rs
index 35560c6e..ff05e1f6 100644
--- a/src/properties/masking.rs
+++ b/src/properties/masking.rs
@@ -37,11 +37,11 @@ enum_property! {
/// A value for the [mask-mode](https://www.w3.org/TR/css-masking-1/#the-mask-mode) property.
pub enum MaskMode {
/// The luminance values of the mask image is used.
- "luminance": Luminance,
+ Luminance,
/// The alpha values of the mask image is used.
- "alpha": Alpha,
+ Alpha,
/// If an SVG source is used, the value matches the `mask-type` property. Otherwise, the alpha values are used.
- "match-source": MatchSource,
+ MatchSource,
}
}
@@ -58,11 +58,11 @@ enum_property! {
/// See also [MaskMode](MaskMode).
pub enum WebKitMaskSourceType {
/// Equivalent to `match-source` in the standard `mask-mode` syntax.
- "auto": Auto,
+ Auto,
/// The luminance values of the mask image is used.
- "luminance": Luminance,
+ Luminance,
/// The alpha values of the mask image is used.
- "alpha": Alpha,
+ Alpha,
}
}
@@ -81,19 +81,19 @@ enum_property! {
/// as used in the `mask-clip` and `clip-path` properties.
pub enum GeometryBox {
/// The painted content is clipped to the content box.
- "border-box": BorderBox,
+ BorderBox,
/// The painted content is clipped to the padding box.
- "padding-box": PaddingBox,
+ PaddingBox,
/// The painted content is clipped to the border box.
- "content-box": ContentBox,
+ ContentBox,
/// The painted content is clipped to the margin box.
- "margin-box": MarginBox,
+ MarginBox,
/// The painted content is clipped to the object bounding box.
- "fill-box": FillBox,
+ FillBox,
/// The painted content is clipped to the stroke bounding box.
- "stroke-box": StrokeBox,
+ StrokeBox,
/// Uses the nearest SVG viewport as reference box.
- "view-box": ViewBox,
+ ViewBox,
}
}
@@ -104,7 +104,7 @@ impl Default for GeometryBox {
}
/// A value for the [mask-clip](https://www.w3.org/TR/css-masking-1/#the-mask-clip) property.
-#[derive(Debug, Clone, PartialEq)]
+#[derive(Debug, Clone, PartialEq, Parse, ToCss)]
#[cfg_attr(feature = "visitor", derive(Visit))]
#[cfg_attr(
feature = "serde",
@@ -120,29 +120,6 @@ pub enum MaskClip {
NoClip,
}
-impl<'i> Parse<'i> for MaskClip {
- fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> {
- if let Ok(b) = input.try_parse(GeometryBox::parse) {
- return Ok(MaskClip::GeometryBox(b));
- }
-
- input.expect_ident_matching("no-clip")?;
- Ok(MaskClip::NoClip)
- }
-}
-
-impl ToCss for MaskClip {
- fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError>
- where
- W: std::fmt::Write,
- {
- match self {
- MaskClip::GeometryBox(b) => b.to_css(dest),
- MaskClip::NoClip => dest.write_str("no-clip"),
- }
- }
-}
-
impl IsCompatible for MaskClip {
fn is_compatible(&self, browsers: Browsers) -> bool {
match self {
@@ -191,21 +168,21 @@ enum_property! {
/// See also [MaskComposite](MaskComposite).
#[allow(missing_docs)]
pub enum WebKitMaskComposite {
- "clear": Clear,
- "copy": Copy,
+ Clear,
+ Copy,
/// Equivalent to `add` in the standard `mask-composite` syntax.
- "source-over": SourceOver,
+ SourceOver,
/// Equivalent to `intersect` in the standard `mask-composite` syntax.
- "source-in": SourceIn,
+ SourceIn,
/// Equivalent to `subtract` in the standard `mask-composite` syntax.
- "source-out": SourceOut,
- "source-atop": SourceAtop,
- "destination-over": DestinationOver,
- "destination-in": DestinationIn,
- "destination-out": DestinationOut,
- "destination-atop": DestinationAtop,
+ SourceOut,
+ SourceAtop,
+ DestinationOver,
+ DestinationIn,
+ DestinationOut,
+ DestinationAtop,
/// Equivalent to `exclude` in the standard `mask-composite` syntax.
- "xor": Xor,
+ Xor,
}
}
@@ -477,9 +454,9 @@ enum_property! {
/// A value for the [mask-border-mode](https://www.w3.org/TR/css-masking-1/#the-mask-border-mode) property.
pub enum MaskBorderMode {
/// The luminance values of the mask image is used.
- "luminance": Luminance,
+ Luminance,
/// The alpha values of the mask image is used.
- "alpha": Alpha,
+ Alpha,
}
}
diff --git a/src/properties/outline.rs b/src/properties/outline.rs
index e0a710e2..2ecdb202 100644
--- a/src/properties/outline.rs
+++ b/src/properties/outline.rs
@@ -15,7 +15,7 @@ use crate::visitor::Visit;
use cssparser::*;
/// A value for the [outline-style](https://drafts.csswg.org/css-ui/#outline-style) property.
-#[derive(Debug, Clone, PartialEq)]
+#[derive(Debug, Clone, PartialEq, Parse, ToCss)]
#[cfg_attr(feature = "visitor", derive(Visit))]
#[cfg_attr(
feature = "serde",
@@ -31,29 +31,6 @@ pub enum OutlineStyle {
LineStyle(LineStyle),
}
-impl<'i> Parse<'i> for OutlineStyle {
- fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> {
- if let Ok(border_style) = input.try_parse(LineStyle::parse) {
- return Ok(OutlineStyle::LineStyle(border_style));
- }
-
- input.expect_ident_matching("auto")?;
- Ok(OutlineStyle::Auto)
- }
-}
-
-impl ToCss for OutlineStyle {
- fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError>
- where
- W: std::fmt::Write,
- {
- match self {
- OutlineStyle::Auto => dest.write_str("auto"),
- OutlineStyle::LineStyle(border_style) => border_style.to_css(dest),
- }
- }
-}
-
impl Default for OutlineStyle {
fn default() -> OutlineStyle {
OutlineStyle::LineStyle(LineStyle::None)
diff --git a/src/properties/position.rs b/src/properties/position.rs
index ace9a40d..34a0cf3b 100644
--- a/src/properties/position.rs
+++ b/src/properties/position.rs
@@ -73,7 +73,7 @@ impl ToCss for Position {
}
/// A value for the [z-index](https://drafts.csswg.org/css2/#z-index) property.
-#[derive(Debug, Clone, PartialEq)]
+#[derive(Debug, Clone, PartialEq, Parse, ToCss)]
#[cfg_attr(feature = "visitor", derive(Visit))]
#[cfg_attr(
feature = "serde",
@@ -89,29 +89,6 @@ pub enum ZIndex {
Integer(CSSInteger),
}
-impl<'i> Parse<'i> for ZIndex {
- fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> {
- if let Ok(value) = input.expect_integer() {
- return Ok(ZIndex::Integer(value));
- }
-
- input.expect_ident_matching("auto")?;
- Ok(ZIndex::Auto)
- }
-}
-
-impl ToCss for ZIndex {
- fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError>
- where
- W: std::fmt::Write,
- {
- match self {
- ZIndex::Auto => dest.write_str("auto"),
- ZIndex::Integer(value) => value.to_css(dest),
- }
- }
-}
-
#[derive(Default)]
pub(crate) struct PositionHandler {
position: Option,
diff --git a/src/properties/size.rs b/src/properties/size.rs
index 54cc134d..2475773b 100644
--- a/src/properties/size.rs
+++ b/src/properties/size.rs
@@ -300,9 +300,9 @@ enum_property! {
/// A value for the [box-sizing](https://drafts.csswg.org/css-sizing-3/#box-sizing) property.
pub enum BoxSizing {
/// Exclude the margin/border/padding from the width and height.
- "content-box": ContentBox,
+ ContentBox,
/// Include the padding and border (but not the margin) in the width and height.
- "border-box": BorderBox,
+ BorderBox,
}
}
diff --git a/src/properties/svg.rs b/src/properties/svg.rs
index 26109941..150e5a25 100644
--- a/src/properties/svg.rs
+++ b/src/properties/svg.rs
@@ -13,7 +13,7 @@ use cssparser::*;
/// An SVG [``](https://www.w3.org/TR/SVG2/painting.html#SpecifyingPaint) value
/// used in the `fill` and `stroke` properties.
-#[derive(Debug, Clone, PartialEq)]
+#[derive(Debug, Clone, PartialEq, Parse, ToCss)]
#[cfg_attr(feature = "visitor", derive(Visit))]
#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
#[cfg_attr(
@@ -23,8 +23,6 @@ use cssparser::*;
)]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
pub enum SVGPaint<'i> {
- /// No paint.
- None,
/// A URL reference to a paint server element, e.g. `linearGradient`, `radialGradient`, and `pattern`.
Url {
#[cfg_attr(feature = "serde", serde(borrow))]
@@ -40,12 +38,14 @@ pub enum SVGPaint<'i> {
ContextFill,
/// Use the paint value of stroke from a context element.
ContextStroke,
+ /// No paint.
+ None,
}
/// A fallback for an SVG paint in case a paint server `url()` cannot be resolved.
///
/// See [SVGPaint](SVGPaint).
-#[derive(Debug, Clone, PartialEq)]
+#[derive(Debug, Clone, PartialEq, Parse, ToCss)]
#[cfg_attr(feature = "visitor", derive(Visit))]
#[cfg_attr(
feature = "serde",
@@ -61,74 +61,6 @@ pub enum SVGPaintFallback {
Color(CssColor),
}
-impl<'i> Parse<'i> for SVGPaint<'i> {
- fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> {
- if let Ok(url) = input.try_parse(Url::parse) {
- let fallback = input.try_parse(SVGPaintFallback::parse).ok();
- return Ok(SVGPaint::Url { url, fallback });
- }
-
- if let Ok(color) = input.try_parse(CssColor::parse) {
- return Ok(SVGPaint::Color(color));
- }
-
- let location = input.current_source_location();
- let keyword = input.expect_ident()?;
- match_ignore_ascii_case! { &keyword,
- "none" => Ok(SVGPaint::None),
- "context-fill" => Ok(SVGPaint::ContextFill),
- "context-stroke" => Ok(SVGPaint::ContextStroke),
- _ => Err(location.new_unexpected_token_error(
- cssparser::Token::Ident(keyword.clone())
- ))
- }
- }
-}
-
-impl<'i> ToCss for SVGPaint<'i> {
- fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError>
- where
- W: std::fmt::Write,
- {
- match self {
- SVGPaint::None => dest.write_str("none"),
- SVGPaint::Url { url, fallback } => {
- url.to_css(dest)?;
- if let Some(fallback) = fallback {
- dest.write_char(' ')?;
- fallback.to_css(dest)?;
- }
- Ok(())
- }
- SVGPaint::Color(color) => color.to_css(dest),
- SVGPaint::ContextFill => dest.write_str("context-fill"),
- SVGPaint::ContextStroke => dest.write_str("context-stroke"),
- }
- }
-}
-
-impl<'i> Parse<'i> for SVGPaintFallback {
- fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> {
- if input.try_parse(|input| input.expect_ident_matching("none")).is_ok() {
- return Ok(SVGPaintFallback::None);
- }
-
- Ok(SVGPaintFallback::Color(CssColor::parse(input)?))
- }
-}
-
-impl ToCss for SVGPaintFallback {
- fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError>
- where
- W: std::fmt::Write,
- {
- match self {
- SVGPaintFallback::None => dest.write_str("none"),
- SVGPaintFallback::Color(color) => color.to_css(dest),
- }
- }
-}
-
impl<'i> FallbackValues for SVGPaint<'i> {
fn get_fallbacks(&mut self, targets: Targets) -> Vec {
match self {
@@ -182,15 +114,15 @@ enum_property! {
/// A value for the [stroke-linejoin](https://www.w3.org/TR/SVG2/painting.html#LineJoin) property.
pub enum StrokeLinejoin {
/// A sharp corner is to be used to join path segments.
- "miter": Miter,
+ Miter,
/// Same as `miter` but clipped beyond `stroke-miterlimit`.
- "miter-clip": MiterClip,
+ MiterClip,
/// A round corner is to be used to join path segments.
- "round": Round,
+ Round,
/// A bevelled corner is to be used to join path segments.
- "bevel": Bevel,
+ Bevel,
/// An arcs corner is to be used to join path segments.
- "arcs": Arcs,
+ Arcs,
}
}
@@ -260,7 +192,7 @@ impl ToCss for StrokeDasharray {
}
/// A value for the [marker](https://www.w3.org/TR/SVG2/painting.html#VertexMarkerProperties) properties.
-#[derive(Debug, Clone, PartialEq)]
+#[derive(Debug, Clone, PartialEq, Parse, ToCss)]
#[cfg_attr(feature = "visitor", derive(Visit))]
#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
#[cfg_attr(
@@ -277,29 +209,6 @@ pub enum Marker<'i> {
Url(Url<'i>),
}
-impl<'i> Parse<'i> for Marker<'i> {
- fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> {
- if let Ok(url) = input.try_parse(Url::parse) {
- return Ok(Marker::Url(url));
- }
-
- input.expect_ident_matching("none")?;
- Ok(Marker::None)
- }
-}
-
-impl<'i> ToCss for Marker<'i> {
- fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError>
- where
- W: std::fmt::Write,
- {
- match self {
- Marker::None => dest.write_str("none"),
- Marker::Url(url) => url.to_css(dest),
- }
- }
-}
-
enum_property! {
/// A value for the [color-interpolation](https://www.w3.org/TR/SVG2/painting.html#ColorInterpolation) property.
pub enum ColorInterpolation {
diff --git a/src/properties/text.rs b/src/properties/text.rs
index e22f6fab..622b870f 100644
--- a/src/properties/text.rs
+++ b/src/properties/text.rs
@@ -238,13 +238,13 @@ enum_property! {
/// A value for the [word-break](https://www.w3.org/TR/2021/CRD-css-text-3-20210422/#word-break-property) property.
pub enum WordBreak {
/// Words break according to their customary rules.
- "normal": Normal,
+ Normal,
/// Breaking is forbidden within “words”.
- "keep-all": KeepAll,
+ KeepAll,
/// Breaking is allowed within “words”.
- "break-all": BreakAll,
+ BreakAll,
/// Breaking is allowed if there is no otherwise acceptable break points in a line.
- "break-word": BreakWord,
+ BreakWord,
}
}
@@ -279,12 +279,12 @@ enum_property! {
/// A value for the [overflow-wrap](https://www.w3.org/TR/2021/CRD-css-text-3-20210422/#overflow-wrap-property) property.
pub enum OverflowWrap {
/// Lines may break only at allowed break points.
- "normal": Normal,
+ Normal,
/// Breaking is allowed if there is no otherwise acceptable break points in a line.
- "anywhere": Anywhere,
+ Anywhere,
/// As for anywhere except that soft wrap opportunities introduced by break-word are
/// not considered when calculating min-content intrinsic sizes.
- "break-word": BreakWord,
+ BreakWord,
}
}
@@ -292,21 +292,21 @@ enum_property! {
/// A value for the [text-align](https://www.w3.org/TR/2021/CRD-css-text-3-20210422/#text-align-property) property.
pub enum TextAlign {
/// Inline-level content is aligned to the start edge of the line box.
- "start": Start,
+ Start,
/// Inline-level content is aligned to the end edge of the line box.
- "end": End,
+ End,
/// Inline-level content is aligned to the line-left edge of the line box.
- "left": Left,
+ Left,
/// Inline-level content is aligned to the line-right edge of the line box.
- "right": Right,
+ Right,
/// Inline-level content is centered within the line box.
- "center": Center,
+ Center,
/// Text is justified according to the method specified by the text-justify property.
- "justify": Justify,
+ Justify,
/// Matches the parent element.
- "match-parent": MatchParent,
+ MatchParent,
/// Same as justify, but also justifies the last line.
- "justify-all": JustifyAll,
+ JustifyAll,
}
}
@@ -314,21 +314,21 @@ enum_property! {
/// A value for the [text-align-last](https://www.w3.org/TR/2021/CRD-css-text-3-20210422/#text-align-last-property) property.
pub enum TextAlignLast {
/// Content on the affected line is aligned per `text-align-all` unless set to `justify`, in which case it is start-aligned.
- "auto": Auto,
+ Auto,
/// Inline-level content is aligned to the start edge of the line box.
- "start": Start,
+ Start,
/// Inline-level content is aligned to the end edge of the line box.
- "end": End,
+ End,
/// Inline-level content is aligned to the line-left edge of the line box.
- "left": Left,
+ Left,
/// Inline-level content is aligned to the line-right edge of the line box.
- "right": Right,
+ Right,
/// Inline-level content is centered within the line box.
- "center": Center,
+ Center,
/// Text is justified according to the method specified by the text-justify property.
- "justify": Justify,
+ Justify,
/// Matches the parent element.
- "match-parent": MatchParent,
+ MatchParent,
}
}
@@ -336,19 +336,19 @@ enum_property! {
/// A value for the [text-justify](https://www.w3.org/TR/2021/CRD-css-text-3-20210422/#text-justify-property) property.
pub enum TextJustify {
/// The UA determines the justification algorithm to follow.
- "auto": Auto,
+ Auto,
/// Justification is disabled.
- "none": None,
+ None,
/// Justification adjusts spacing at word separators only.
- "inter-word": InterWord,
+ InterWord,
/// Justification adjusts spacing between each character.
- "inter-character": InterCharacter,
+ InterCharacter,
}
}
/// A value for the [word-spacing](https://www.w3.org/TR/2021/CRD-css-text-3-20210422/#word-spacing-property)
/// and [letter-spacing](https://www.w3.org/TR/2021/CRD-css-text-3-20210422/#letter-spacing-property) properties.
-#[derive(Debug, Clone, PartialEq)]
+#[derive(Debug, Clone, PartialEq, Parse, ToCss)]
#[cfg_attr(feature = "visitor", derive(Visit))]
#[cfg_attr(
feature = "serde",
@@ -364,29 +364,6 @@ pub enum Spacing {
Length(Length),
}
-impl<'i> Parse<'i> for Spacing {
- fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> {
- if input.try_parse(|input| input.expect_ident_matching("normal")).is_ok() {
- return Ok(Spacing::Normal);
- }
-
- let length = Length::parse(input)?;
- Ok(Spacing::Length(length))
- }
-}
-
-impl ToCss for Spacing {
- fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError>
- where
- W: std::fmt::Write,
- {
- match self {
- Spacing::Normal => dest.write_str("normal"),
- Spacing::Length(len) => len.to_css(dest),
- }
- }
-}
-
/// A value for the [text-indent](https://www.w3.org/TR/2021/CRD-css-text-3-20210422/#text-indent-property) property.
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "visitor", derive(Visit))]
@@ -466,7 +443,7 @@ impl ToCss for TextIndent {
}
/// A value for the [text-size-adjust](https://w3c.github.io/csswg-drafts/css-size-adjust/#adjustment-control) property.
-#[derive(Debug, Clone, PartialEq)]
+#[derive(Debug, Clone, PartialEq, Parse, ToCss)]
#[cfg_attr(feature = "visitor", derive(Visit))]
#[cfg_attr(
feature = "serde",
@@ -484,34 +461,6 @@ pub enum TextSizeAdjust {
Percentage(Percentage),
}
-impl<'i> Parse<'i> for TextSizeAdjust {
- fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> {
- if let Ok(p) = input.try_parse(Percentage::parse) {
- return Ok(TextSizeAdjust::Percentage(p));
- }
-
- let ident = input.expect_ident_cloned()?;
- match_ignore_ascii_case! {&*ident,
- "auto" => Ok(TextSizeAdjust::Auto),
- "none" => Ok(TextSizeAdjust::None),
- _ => Err(input.new_unexpected_token_error(Token::Ident(ident.clone())))
- }
- }
-}
-
-impl ToCss for TextSizeAdjust {
- fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError>
- where
- W: std::fmt::Write,
- {
- match self {
- TextSizeAdjust::Auto => dest.write_str("auto"),
- TextSizeAdjust::None => dest.write_str("none"),
- TextSizeAdjust::Percentage(p) => p.to_css(dest),
- }
- }
-}
-
bitflags! {
/// A value for the [text-decoration-line](https://www.w3.org/TR/2020/WD-css-text-decor-4-20200506/#text-decoration-line-property) property.
///
@@ -749,7 +698,7 @@ impl Default for TextDecorationStyle {
}
/// A value for the [text-decoration-thickness](https://www.w3.org/TR/2020/WD-css-text-decor-4-20200506/#text-decoration-width-property) property.
-#[derive(Debug, Clone, PartialEq)]
+#[derive(Debug, Clone, PartialEq, Parse, ToCss)]
#[cfg_attr(feature = "visitor", derive(Visit))]
#[cfg_attr(
feature = "serde",
@@ -773,34 +722,6 @@ impl Default for TextDecorationThickness {
}
}
-impl<'i> Parse<'i> for TextDecorationThickness {
- fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> {
- if input.try_parse(|input| input.expect_ident_matching("auto")).is_ok() {
- return Ok(TextDecorationThickness::Auto);
- }
-
- if input.try_parse(|input| input.expect_ident_matching("from-font")).is_ok() {
- return Ok(TextDecorationThickness::FromFont);
- }
-
- let lp = LengthPercentage::parse(input)?;
- Ok(TextDecorationThickness::LengthPercentage(lp))
- }
-}
-
-impl ToCss for TextDecorationThickness {
- fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError>
- where
- W: std::fmt::Write,
- {
- match self {
- TextDecorationThickness::Auto => dest.write_str("auto"),
- TextDecorationThickness::FromFont => dest.write_str("from-font"),
- TextDecorationThickness::LengthPercentage(lp) => lp.to_css(dest),
- }
- }
-}
-
define_shorthand! {
/// A value for the [text-decoration](https://www.w3.org/TR/2020/WD-css-text-decor-4-20200506/#text-decoration-property) shorthand property.
pub struct TextDecoration(VendorPrefix) {
@@ -927,15 +848,15 @@ enum_property! {
/// See [TextEmphasisStyle](TextEmphasisStyle).
pub enum TextEmphasisShape {
/// Display small circles as marks.
- "dot": Dot,
+ Dot,
/// Display large circles as marks.
- "circle": Circle,
+ Circle,
/// Display double circles as marks.
- "double-circle": DoubleCircle,
+ DoubleCircle,
/// Display triangles as marks.
- "triangle": Triangle,
+ Triangle,
/// Display sesames as marks.
- "sesame": Sesame,
+ Sesame,
}
}
@@ -1614,16 +1535,16 @@ enum_property! {
/// A value for the [unicode-bidi](https://drafts.csswg.org/css-writing-modes-3/#unicode-bidi) property.
pub enum UnicodeBidi {
/// The box does not open an additional level of embedding.
- "normal": Normal,
+ Normal,
/// If the box is inline, this value creates a directional embedding by opening an additional level of embedding.
- "embed": Embed,
+ Embed,
/// On an inline box, this bidi-isolates its contents.
- "isolate": Isolate,
+ Isolate,
/// This value puts the box’s immediate inline content in a directional override.
- "bidi-override": BidiOverride,
+ BidiOverride,
/// This combines the isolation behavior of isolate with the directional override behavior of bidi-override.
- "isolate-override": IsolateOverride,
+ IsolateOverride,
/// This value behaves as isolate except that the base directionality is determined using a heuristic rather than the direction property.
- "plaintext": Plaintext,
+ Plaintext,
}
}
diff --git a/src/properties/transform.rs b/src/properties/transform.rs
index d58a08fe..c8f5a7f7 100644
--- a/src/properties/transform.rs
+++ b/src/properties/transform.rs
@@ -1382,8 +1382,8 @@ enum_property! {
/// A value for the [transform-style](https://drafts.csswg.org/css-transforms-2/#transform-style-property) property.
#[allow(missing_docs)]
pub enum TransformStyle {
- "flat": Flat,
- "preserve-3d": Preserve3d,
+ Flat,
+ Preserve3d,
}
}
@@ -1391,15 +1391,15 @@ enum_property! {
/// A value for the [transform-box](https://drafts.csswg.org/css-transforms-1/#transform-box) property.
pub enum TransformBox {
/// Uses the content box as reference box.
- "content-box": ContentBox,
+ ContentBox,
/// Uses the border box as reference box.
- "border-box": BorderBox,
+ BorderBox,
/// Uses the object bounding box as reference box.
- "fill-box": FillBox,
+ FillBox,
/// Uses the stroke bounding box as reference box.
- "stroke-box": StrokeBox,
+ StrokeBox,
/// Uses the nearest SVG viewport as reference box.
- "view-box": ViewBox,
+ ViewBox,
}
}
@@ -1413,7 +1413,7 @@ enum_property! {
}
/// A value for the [perspective](https://drafts.csswg.org/css-transforms-2/#perspective-property) property.
-#[derive(Debug, Clone, PartialEq)]
+#[derive(Debug, Clone, PartialEq, Parse, ToCss)]
#[cfg_attr(feature = "visitor", derive(Visit))]
#[cfg_attr(
feature = "serde",
@@ -1429,28 +1429,6 @@ pub enum Perspective {
Length(Length),
}
-impl<'i> Parse<'i> for Perspective {
- fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> {
- if input.try_parse(|input| input.expect_ident_matching("none")).is_ok() {
- return Ok(Perspective::None);
- }
-
- Ok(Perspective::Length(Length::parse(input)?))
- }
-}
-
-impl ToCss for Perspective {
- fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError>
- where
- W: std::fmt::Write,
- {
- match self {
- Perspective::None => dest.write_str("none"),
- Perspective::Length(len) => len.to_css(dest),
- }
- }
-}
-
/// A value for the [translate](https://drafts.csswg.org/css-transforms-2/#propdef-translate) property.
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "visitor", derive(Visit))]
diff --git a/src/properties/ui.rs b/src/properties/ui.rs
index 4952082e..11e1eade 100644
--- a/src/properties/ui.rs
+++ b/src/properties/ui.rs
@@ -94,42 +94,42 @@ enum_property! {
/// See [Cursor](Cursor).
#[allow(missing_docs)]
pub enum CursorKeyword {
- "auto": Auto,
- "default": Default,
- "none": None,
- "context-menu": ContextMenu,
- "help": Help,
- "pointer": Pointer,
- "progress": Progress,
- "wait": Wait,
- "cell": Cell,
- "crosshair": Crosshair,
- "text": Text,
- "vertical-text": VerticalText,
- "alias": Alias,
- "copy": Copy,
- "move": Move,
- "no-drop": NoDrop,
- "not-allowed": NotAllowed,
- "grab": Grab,
- "grabbing": Grabbing,
- "e-resize": EResize,
- "n-resize": NResize,
- "ne-resize": NeResize,
- "nw-resize": NwResize,
- "s-resize": SResize,
- "se-resize": SeResize,
- "sw-resize": SwResize,
- "w-resize": WResize,
- "ew-resize": EwResize,
- "ns-resize": NsResize,
- "nesw-resize": NeswResize,
- "nwse-resize": NwseResize,
- "col-resize": ColResize,
- "row-resize": RowResize,
- "all-scroll": AllScroll,
- "zoom-in": ZoomIn,
- "zoom-out": ZoomOut,
+ Auto,
+ Default,
+ None,
+ ContextMenu,
+ Help,
+ Pointer,
+ Progress,
+ Wait,
+ Cell,
+ Crosshair,
+ Text,
+ VerticalText,
+ Alias,
+ Copy,
+ Move,
+ NoDrop,
+ NotAllowed,
+ Grab,
+ Grabbing,
+ EResize,
+ NResize,
+ NeResize,
+ NwResize,
+ SResize,
+ SeResize,
+ SwResize,
+ WResize,
+ EwResize,
+ NsResize,
+ NeswResize,
+ NwseResize,
+ ColResize,
+ RowResize,
+ AllScroll,
+ ZoomIn,
+ ZoomOut,
}
}
@@ -179,7 +179,7 @@ impl<'i> ToCss for Cursor<'i> {
}
/// A value for the [caret-color](https://www.w3.org/TR/2021/WD-css-ui-4-20210316/#caret-color) property.
-#[derive(Debug, Clone, PartialEq)]
+#[derive(Debug, Clone, PartialEq, Parse, ToCss)]
#[cfg_attr(feature = "visitor", derive(Visit))]
#[cfg_attr(
feature = "serde",
@@ -201,29 +201,6 @@ impl Default for ColorOrAuto {
}
}
-impl<'i> Parse<'i> for ColorOrAuto {
- fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> {
- if input.try_parse(|input| input.expect_ident_matching("auto")).is_ok() {
- return Ok(ColorOrAuto::Auto);
- }
-
- let color = CssColor::parse(input)?;
- Ok(ColorOrAuto::Color(color))
- }
-}
-
-impl ToCss for ColorOrAuto {
- fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError>
- where
- W: std::fmt::Write,
- {
- match self {
- ColorOrAuto::Auto => dest.write_str("auto"),
- ColorOrAuto::Color(color) => color.to_css(dest),
- }
- }
-}
-
impl FallbackValues for ColorOrAuto {
fn get_fallbacks(&mut self, targets: Targets) -> Vec {
match self {
diff --git a/src/rules/keyframes.rs b/src/rules/keyframes.rs
index 511a3172..adf2fb5b 100644
--- a/src/rules/keyframes.rs
+++ b/src/rules/keyframes.rs
@@ -267,7 +267,7 @@ impl<'i> ToCss for KeyframesRule<'i> {
/// A [keyframe selector](https://drafts.csswg.org/css-animations/#typedef-keyframe-selector)
/// within an `@keyframes` rule.
-#[derive(Debug, PartialEq, Clone)]
+#[derive(Debug, PartialEq, Clone, Parse)]
#[cfg_attr(feature = "visitor", derive(Visit))]
#[cfg_attr(
feature = "serde",
@@ -285,24 +285,6 @@ pub enum KeyframeSelector {
To,
}
-impl<'i> Parse<'i> for KeyframeSelector {
- fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> {
- if let Ok(val) = input.try_parse(Percentage::parse) {
- return Ok(KeyframeSelector::Percentage(val));
- }
-
- let location = input.current_source_location();
- let ident = input.expect_ident()?;
- match_ignore_ascii_case! { &*ident,
- "from" => Ok(KeyframeSelector::From),
- "to" => Ok(KeyframeSelector::To),
- _ => Err(location.new_unexpected_token_error(
- cssparser::Token::Ident(ident.clone())
- ))
- }
- }
-}
-
impl ToCss for KeyframeSelector {
fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError>
where
diff --git a/src/rules/page.rs b/src/rules/page.rs
index e4897cd3..d95c94c2 100644
--- a/src/rules/page.rs
+++ b/src/rules/page.rs
@@ -81,41 +81,41 @@ enum_property! {
/// A [page margin box](https://www.w3.org/TR/css-page-3/#margin-boxes).
pub enum PageMarginBox {
/// A fixed-size box defined by the intersection of the top and left margins of the page box.
- "top-left-corner": TopLeftCorner,
+ TopLeftCorner,
/// A variable-width box filling the top page margin between the top-left-corner and top-center page-margin boxes.
- "top-left": TopLeft,
+ TopLeft,
/// A variable-width box centered horizontally between the page’s left and right border edges and filling the
/// page top margin between the top-left and top-right page-margin boxes.
- "top-center": TopCenter,
+ TopCenter,
/// A variable-width box filling the top page margin between the top-center and top-right-corner page-margin boxes.
- "top-right": TopRight,
+ TopRight,
/// A fixed-size box defined by the intersection of the top and right margins of the page box.
- "top-right-corner": TopRightCorner,
+ TopRightCorner,
/// A variable-height box filling the left page margin between the top-left-corner and left-middle page-margin boxes.
- "left-top": LeftTop,
+ LeftTop,
/// A variable-height box centered vertically between the page’s top and bottom border edges and filling the
/// left page margin between the left-top and left-bottom page-margin boxes.
- "left-middle": LeftMiddle,
+ LeftMiddle,
/// A variable-height box filling the left page margin between the left-middle and bottom-left-corner page-margin boxes.
- "left-bottom": LeftBottom,
+ LeftBottom,
/// A variable-height box filling the right page margin between the top-right-corner and right-middle page-margin boxes.
- "right-top": RightTop,
+ RightTop,
/// A variable-height box centered vertically between the page’s top and bottom border edges and filling the right
/// page margin between the right-top and right-bottom page-margin boxes.
- "right-middle": RightMiddle,
+ RightMiddle,
/// A variable-height box filling the right page margin between the right-middle and bottom-right-corner page-margin boxes.
- "right-bottom": RightBottom,
+ RightBottom,
/// A fixed-size box defined by the intersection of the bottom and left margins of the page box.
- "bottom-left-corner": BottomLeftCorner,
+ BottomLeftCorner,
/// A variable-width box filling the bottom page margin between the bottom-left-corner and bottom-center page-margin boxes.
- "bottom-left": BottomLeft,
+ BottomLeft,
/// A variable-width box centered horizontally between the page’s left and right border edges and filling the bottom
/// page margin between the bottom-left and bottom-right page-margin boxes.
- "bottom-center": BottomCenter,
+ BottomCenter,
/// A variable-width box filling the bottom page margin between the bottom-center and bottom-right-corner page-margin boxes.
- "bottom-right": BottomRight,
+ BottomRight,
/// A fixed-size box defined by the intersection of the bottom and right margins of the page box.
- "bottom-right-corner": BottomRightCorner,
+ BottomRightCorner,
}
}
diff --git a/src/traits.rs b/src/traits.rs
index 1d520421..4aecaf7c 100644
--- a/src/traits.rs
+++ b/src/traits.rs
@@ -30,6 +30,20 @@ pub trait Parse<'i>: Sized {
}
}
+pub(crate) use lightningcss_derive::Parse;
+
+impl<'i, T: Parse<'i>> Parse<'i> for Option {
+ fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> {
+ Ok(input.try_parse(T::parse).ok())
+ }
+}
+
+impl<'i, T: Parse<'i>> Parse<'i> for Box {
+ fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> {
+ Ok(Box::new(T::parse(input)?))
+ }
+}
+
/// Trait for things that can be parsed from CSS syntax and require ParserOptions.
pub trait ParseWithOptions<'i>: Sized {
/// Parse a value of this type with the given options.
@@ -78,6 +92,8 @@ pub trait ToCss {
}
}
+pub(crate) use lightningcss_derive::ToCss;
+
impl<'a, T> ToCss for &'a T
where
T: ToCss + ?Sized,
@@ -90,6 +106,27 @@ where
}
}
+impl ToCss for Box {
+ fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError>
+ where
+ W: std::fmt::Write,
+ {
+ (**self).to_css(dest)
+ }
+}
+
+impl ToCss for Option {
+ fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError>
+ where
+ W: std::fmt::Write,
+ {
+ if let Some(v) = self {
+ v.to_css(dest)?;
+ }
+ Ok(())
+ }
+}
+
pub(crate) trait PropertyHandler<'i>: Sized {
fn handle_property(
&mut self,
diff --git a/src/values/calc.rs b/src/values/calc.rs
index 95a0bb59..742c5184 100644
--- a/src/values/calc.rs
+++ b/src/values/calc.rs
@@ -92,13 +92,13 @@ enum_property! {
/// as used in the `round()` function.
pub enum RoundingStrategy {
/// Round to the nearest integer.
- "nearest": Nearest,
+ Nearest,
/// Round up (ceil).
- "up": Up,
+ Up,
/// Round down (floor).
- "down": Down,
+ Down,
/// Round toward zero (truncate).
- "to-zero": ToZero,
+ ToZero,
}
}
diff --git a/src/values/color.rs b/src/values/color.rs
index efa2bd26..b91c1426 100644
--- a/src/values/color.rs
+++ b/src/values/color.rs
@@ -3581,6 +3581,7 @@ impl<'i, V: ?Sized + Visitor<'i, T>, T: Visit<'i, T, V>> Visit<'i, T, V> for RGB
}
enum_property! {
+ #[css(case = lower)]
/// A CSS [system color](https://drafts.csswg.org/css-color/#css-system-colors) keyword.
pub enum SystemColor {
/// Background of accented user interface controls.
diff --git a/src/values/easing.rs b/src/values/easing.rs
index c1e834a0..4db5c063 100644
--- a/src/values/easing.rs
+++ b/src/values/easing.rs
@@ -192,7 +192,7 @@ impl EasingFunction {
}
/// A [step position](https://www.w3.org/TR/css-easing-1/#step-position), used within the `steps()` function.
-#[derive(Debug, Clone, PartialEq)]
+#[derive(Debug, Clone, PartialEq, ToCss)]
#[cfg_attr(feature = "visitor", derive(Visit))]
#[cfg_attr(
feature = "serde",
@@ -233,17 +233,3 @@ impl<'i> Parse<'i> for StepPosition {
Ok(keyword)
}
}
-
-impl ToCss for StepPosition {
- fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError>
- where
- W: std::fmt::Write,
- {
- match self {
- StepPosition::Start => dest.write_str("start"),
- StepPosition::End => dest.write_str("end"),
- StepPosition::JumpNone => dest.write_str("jump-none"),
- StepPosition::JumpBoth => dest.write_str("jump-both"),
- }
- }
-}
diff --git a/src/values/gradient.rs b/src/values/gradient.rs
index 01054b44..fec51938 100644
--- a/src/values/gradient.rs
+++ b/src/values/gradient.rs
@@ -533,7 +533,7 @@ impl LineDirection {
/// A `radial-gradient()` [ending shape](https://www.w3.org/TR/css-images-3/#valdef-radial-gradient-ending-shape).
///
/// See [RadialGradient](RadialGradient).
-#[derive(Debug, Clone, PartialEq)]
+#[derive(Debug, Clone, PartialEq, Parse, ToCss)]
#[cfg_attr(feature = "visitor", derive(Visit))]
#[cfg_attr(
feature = "serde",
@@ -542,10 +542,11 @@ impl LineDirection {
)]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
pub enum EndingShape {
- /// A circle.
- Circle(Circle),
+ // Note: Ellipse::parse MUST run before Circle::parse for this to be correct.
/// An ellipse.
Ellipse(Ellipse),
+ /// A circle.
+ Circle(Circle),
}
impl Default for EndingShape {
@@ -554,33 +555,6 @@ impl Default for EndingShape {
}
}
-impl<'i> Parse<'i> for EndingShape {
- fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> {
- // Note: Ellipse::parse MUST run before Circle::parse for this to be correct.
- if let Ok(ellipse) = input.try_parse(Ellipse::parse) {
- return Ok(EndingShape::Ellipse(ellipse));
- }
-
- if let Ok(circle) = input.try_parse(Circle::parse) {
- return Ok(EndingShape::Circle(circle));
- }
-
- return Err(input.new_error_for_next_token());
- }
-}
-
-impl ToCss for EndingShape {
- fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError>
- where
- W: std::fmt::Write,
- {
- match self {
- EndingShape::Circle(circle) => circle.to_css(dest),
- EndingShape::Ellipse(ellipse) => ellipse.to_css(dest),
- }
- }
-}
-
/// A circle ending shape for a `radial-gradient()`.
///
/// See [RadialGradient](RadialGradient).
@@ -734,13 +708,13 @@ enum_property! {
/// See [RadialGradient](RadialGradient).
pub enum ShapeExtent {
/// The closest side of the box to the gradient's center.
- "closest-side": ClosestSide,
+ ClosestSide,
/// The farthest side of the box from the gradient's center.
- "farthest-side": FarthestSide,
+ FarthestSide,
/// The closest cornder of the box to the gradient's center.
- "closest-corner": ClosestCorner,
+ ClosestCorner,
/// The farthest corner of the box from the gradient's center.
- "farthest-corner": FarthestCorner,
+ FarthestCorner,
}
}
diff --git a/src/values/image.rs b/src/values/image.rs
index fa778cc2..e6ca26c5 100644
--- a/src/values/image.rs
+++ b/src/values/image.rs
@@ -19,7 +19,7 @@ use cssparser::*;
use smallvec::SmallVec;
/// A CSS [``](https://www.w3.org/TR/css-images-3/#image-values) value.
-#[derive(Debug, Clone, PartialEq)]
+#[derive(Debug, Clone, PartialEq, Parse, ToCss)]
#[cfg_attr(feature = "visitor", derive(Visit))]
#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
#[cfg_attr(feature = "visitor", visit(visit_image, IMAGES))]
@@ -309,42 +309,6 @@ impl<'i, T: ImageFallback<'i>> FallbackValues for SmallVec<[T; 1]> {
}
}
-impl<'i> Parse<'i> for Image<'i> {
- fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> {
- if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() {
- return Ok(Image::None);
- }
-
- if let Ok(url) = input.try_parse(Url::parse) {
- return Ok(Image::Url(url));
- }
-
- if let Ok(grad) = input.try_parse(Gradient::parse) {
- return Ok(Image::Gradient(Box::new(grad)));
- }
-
- if let Ok(image_set) = input.try_parse(ImageSet::parse) {
- return Ok(Image::ImageSet(image_set));
- }
-
- Err(input.new_error_for_next_token())
- }
-}
-
-impl<'i> ToCss for Image<'i> {
- fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError>
- where
- W: std::fmt::Write,
- {
- match self {
- Image::None => dest.write_str("none"),
- Image::Url(url) => url.to_css(dest),
- Image::Gradient(grad) => grad.to_css(dest),
- Image::ImageSet(image_set) => image_set.to_css(dest),
- }
- }
-}
-
/// A CSS [`image-set()`](https://drafts.csswg.org/css-images-4/#image-set-notation) value.
///
/// `image-set()` allows the user agent to choose between multiple versions of an image to
diff --git a/src/values/length.rs b/src/values/length.rs
index 58967f08..149f1dd1 100644
--- a/src/values/length.rs
+++ b/src/values/length.rs
@@ -49,7 +49,7 @@ impl IsCompatible for LengthPercentage {
}
/// Either a [``](https://www.w3.org/TR/css-values-4/#typedef-length-percentage), or the `auto` keyword.
-#[derive(Debug, Clone, PartialEq)]
+#[derive(Debug, Clone, PartialEq, Parse, ToCss)]
#[cfg_attr(feature = "visitor", derive(Visit))]
#[cfg_attr(
feature = "serde",
@@ -65,33 +65,6 @@ pub enum LengthPercentageOrAuto {
LengthPercentage(LengthPercentage),
}
-impl<'i> Parse<'i> for LengthPercentageOrAuto {
- fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> {
- if input.try_parse(|i| i.expect_ident_matching("auto")).is_ok() {
- return Ok(LengthPercentageOrAuto::Auto);
- }
-
- if let Ok(percent) = input.try_parse(|input| LengthPercentage::parse(input)) {
- return Ok(LengthPercentageOrAuto::LengthPercentage(percent));
- }
-
- Err(input.new_error_for_next_token())
- }
-}
-
-impl ToCss for LengthPercentageOrAuto {
- fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError>
- where
- W: std::fmt::Write,
- {
- use LengthPercentageOrAuto::*;
- match self {
- Auto => dest.write_str("auto"),
- LengthPercentage(l) => l.to_css(dest),
- }
- }
-}
-
impl IsCompatible for LengthPercentageOrAuto {
fn is_compatible(&self, browsers: Browsers) -> bool {
match self {
@@ -830,7 +803,7 @@ impl TrySign for Length {
impl_try_from_angle!(Length);
/// Either a [``](https://www.w3.org/TR/css-values-4/#lengths) or a [``](https://www.w3.org/TR/css-values-4/#numbers).
-#[derive(Debug, Clone, PartialEq)]
+#[derive(Debug, Clone, PartialEq, Parse, ToCss)]
#[cfg_attr(feature = "visitor", derive(Visit))]
#[cfg_attr(
feature = "serde",
@@ -840,10 +813,10 @@ impl_try_from_angle!(Length);
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
pub enum LengthOrNumber {
- /// A length.
- Length(Length),
/// A number.
Number(CSSNumber),
+ /// A length.
+ Length(Length),
}
impl Default for LengthOrNumber {
@@ -865,33 +838,6 @@ impl Zero for LengthOrNumber {
}
}
-impl<'i> Parse<'i> for LengthOrNumber {
- fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> {
- // Parse number first so unitless numbers are not parsed as lengths.
- if let Ok(number) = input.try_parse(CSSNumber::parse) {
- return Ok(LengthOrNumber::Number(number));
- }
-
- if let Ok(length) = Length::parse(input) {
- return Ok(LengthOrNumber::Length(length));
- }
-
- Err(input.new_error_for_next_token())
- }
-}
-
-impl ToCss for LengthOrNumber {
- fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError>
- where
- W: std::fmt::Write,
- {
- match self {
- LengthOrNumber::Length(length) => length.to_css(dest),
- LengthOrNumber::Number(number) => number.to_css(dest),
- }
- }
-}
-
impl IsCompatible for LengthOrNumber {
fn is_compatible(&self, browsers: Browsers) -> bool {
match self {
diff --git a/src/values/percentage.rs b/src/values/percentage.rs
index c51be75e..54352750 100644
--- a/src/values/percentage.rs
+++ b/src/values/percentage.rs
@@ -144,7 +144,7 @@ impl_op!(Percentage, std::ops::Add, add);
impl_try_from_angle!(Percentage);
/// Either a `` or ``.
-#[derive(Debug, Clone, PartialEq)]
+#[derive(Debug, Clone, PartialEq, Parse, ToCss)]
#[cfg_attr(feature = "visitor", derive(Visit))]
#[cfg_attr(
feature = "serde",
@@ -154,36 +154,10 @@ impl_try_from_angle!(Percentage);
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
pub enum NumberOrPercentage {
- /// A percentage.
- Percentage(Percentage),
/// A number.
Number(CSSNumber),
-}
-
-impl<'i> Parse<'i> for NumberOrPercentage {
- fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> {
- if let Ok(number) = input.try_parse(CSSNumber::parse) {
- return Ok(NumberOrPercentage::Number(number));
- }
-
- if let Ok(percent) = input.try_parse(|input| Percentage::parse(input)) {
- return Ok(NumberOrPercentage::Percentage(percent));
- }
-
- Err(input.new_error_for_next_token())
- }
-}
-
-impl ToCss for NumberOrPercentage {
- fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError>
- where
- W: std::fmt::Write,
- {
- match self {
- NumberOrPercentage::Percentage(percent) => percent.to_css(dest),
- NumberOrPercentage::Number(number) => number.to_css(dest),
- }
- }
+ /// A percentage.
+ Percentage(Percentage),
}
impl std::convert::Into for &NumberOrPercentage {
diff --git a/src/values/shape.rs b/src/values/shape.rs
index c5f4cc31..9cb5f585 100644
--- a/src/values/shape.rs
+++ b/src/values/shape.rs
@@ -59,7 +59,7 @@ pub struct Circle {
/// A [``](https://www.w3.org/TR/css-shapes-1/#typedef-shape-radius) value
/// that defines the radius of a `circle()` or `ellipse()` shape.
-#[derive(Debug, Clone, PartialEq)]
+#[derive(Debug, Clone, PartialEq, Parse, ToCss)]
#[cfg_attr(feature = "visitor", derive(Visit))]
#[cfg_attr(
feature = "serde",
@@ -182,21 +182,6 @@ impl<'i> Parse<'i> for Circle {
}
}
-impl<'i> Parse<'i> for ShapeRadius {
- fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<'i, ParserError<'i>>> {
- if let Ok(len) = input.try_parse(LengthPercentage::parse) {
- return Ok(ShapeRadius::LengthPercentage(len));
- }
-
- if input.try_parse(|input| input.expect_ident_matching("closest-side")).is_ok() {
- return Ok(ShapeRadius::ClosestSide);
- }
-
- input.expect_ident_matching("farthest-side")?;
- Ok(ShapeRadius::FarthestSide)
- }
-}
-
impl Default for ShapeRadius {
fn default() -> ShapeRadius {
ShapeRadius::ClosestSide
@@ -315,19 +300,6 @@ impl ToCss for Circle {
}
}
-impl ToCss for ShapeRadius {
- fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError>
- where
- W: std::fmt::Write,
- {
- match self {
- ShapeRadius::LengthPercentage(len) => len.to_css(dest),
- ShapeRadius::ClosestSide => dest.write_str("closest-side"),
- ShapeRadius::FarthestSide => dest.write_str("farthest-side"),
- }
- }
-}
-
impl ToCss for Ellipse {
fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError>
where
From 39964f1d78d237bb90006d1517e05b15c4b10514 Mon Sep 17 00:00:00 2001
From: Devon Govett
Date: Sun, 2 Jun 2024 23:36:22 -0700
Subject: [PATCH 015/152] Implement animation-range properties
#572
---
node/ast.d.ts | 66 ++++++++++
src/lib.rs | 243 ++++++++++++++++++++++++++++++++++
src/properties/animation.rs | 252 +++++++++++++++++++++++++++++++++++-
src/properties/mod.rs | 3 +
4 files changed, 563 insertions(+), 1 deletion(-)
diff --git a/node/ast.d.ts b/node/ast.d.ts
index 8bc02772..fba3f69e 100644
--- a/node/ast.d.ts
+++ b/node/ast.d.ts
@@ -1960,6 +1960,15 @@ export type PropertyId =
| {
property: "animation-timeline";
}
+ | {
+ property: "animation-range-start";
+ }
+ | {
+ property: "animation-range-end";
+ }
+ | {
+ property: "animation-range";
+ }
| {
property: "animation";
vendorPrefix: VendorPrefix;
@@ -3316,6 +3325,18 @@ export type Declaration =
property: "animation-timeline";
value: AnimationTimeline[];
}
+ | {
+ property: "animation-range-start";
+ value: AnimationRangeStart[];
+ }
+ | {
+ property: "animation-range-end";
+ value: AnimationRangeEnd[];
+ }
+ | {
+ property: "animation-range";
+ value: AnimationRange[];
+ }
| {
property: "animation";
value: Animation[];
@@ -5541,6 +5562,38 @@ export type Scroller = "root" | "nearest" | "self";
* @maxItems 2
*/
export type Size2DFor_LengthPercentageOrAuto = [LengthPercentageOrAuto, LengthPercentageOrAuto];
+/**
+ * A value for the [animation-range-start](https://drafts.csswg.org/scroll-animations/#animation-range-start) property.
+ */
+export type AnimationRangeStart = AnimationAttachmentRange;
+/**
+ * A value for the [animation-range-start](https://drafts.csswg.org/scroll-animations/#animation-range-start) or [animation-range-end](https://drafts.csswg.org/scroll-animations/#animation-range-end) property.
+ */
+export type AnimationAttachmentRange =
+ | "Normal"
+ | {
+ LengthPercentage: DimensionPercentageFor_LengthValue;
+ }
+ | {
+ TimelineRange: {
+ /**
+ * The name of the timeline range.
+ */
+ name: TimelineRangeName;
+ /**
+ * The offset from the start of the named timeline range.
+ */
+ offset: DimensionPercentageFor_LengthValue;
+ };
+ };
+/**
+ * A [view progress timeline range](https://drafts.csswg.org/scroll-animations/#view-timelines-ranges)
+ */
+export type TimelineRangeName = "cover" | "contain" | "entry" | "exit" | "entry-crossing" | "exit-crossing";
+/**
+ * A value for the [animation-range-end](https://drafts.csswg.org/scroll-animations/#animation-range-end) property.
+ */
+export type AnimationRangeEnd = AnimationAttachmentRange;
/**
* An individual [transform function](https://www.w3.org/TR/2019/CR-css-transforms-1-20190214/#two-d-transform-functions).
*/
@@ -8575,6 +8628,19 @@ export interface ViewTimeline {
*/
inset: Size2DFor_LengthPercentageOrAuto;
}
+/**
+ * A value for the [animation-range](https://drafts.csswg.org/scroll-animations/#animation-range) shorthand property.
+ */
+export interface AnimationRange {
+ /**
+ * The end of the animation's attachment range.
+ */
+ end: AnimationRangeEnd;
+ /**
+ * The start of the animation's attachment range.
+ */
+ start: AnimationRangeStart;
+}
/**
* A value for the [animation](https://drafts.csswg.org/css-animations/#animation) shorthand property.
*/
diff --git a/src/lib.rs b/src/lib.rs
index 27a7df16..8b9535f0 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -11585,6 +11585,249 @@ mod tests {
..Browsers::default()
},
);
+
+ minify_test(
+ ".foo { animation-range-start: entry 10% }",
+ ".foo{animation-range-start:entry 10%}",
+ );
+ minify_test(
+ ".foo { animation-range-start: entry 0% }",
+ ".foo{animation-range-start:entry}",
+ );
+ minify_test(
+ ".foo { animation-range-start: entry }",
+ ".foo{animation-range-start:entry}",
+ );
+ minify_test(".foo { animation-range-start: 50% }", ".foo{animation-range-start:50%}");
+ minify_test(
+ ".foo { animation-range-end: exit 10% }",
+ ".foo{animation-range-end:exit 10%}",
+ );
+ minify_test(
+ ".foo { animation-range-end: exit 100% }",
+ ".foo{animation-range-end:exit}",
+ );
+ minify_test(".foo { animation-range-end: exit }", ".foo{animation-range-end:exit}");
+ minify_test(".foo { animation-range-end: 50% }", ".foo{animation-range-end:50%}");
+ minify_test(
+ ".foo { animation-range: entry 10% exit 90% }",
+ ".foo{animation-range:entry 10% exit 90%}",
+ );
+ minify_test(
+ ".foo { animation-range: entry 0% exit 100% }",
+ ".foo{animation-range:entry exit}",
+ );
+ minify_test(".foo { animation-range: entry }", ".foo{animation-range:entry}");
+ minify_test(
+ ".foo { animation-range: entry 0% entry 100% }",
+ ".foo{animation-range:entry}",
+ );
+ minify_test(".foo { animation-range: 50% normal }", ".foo{animation-range:50%}");
+ minify_test(
+ ".foo { animation-range: normal normal }",
+ ".foo{animation-range:normal}",
+ );
+ test(
+ r#"
+ .foo {
+ animation-range-start: entry 10%;
+ animation-range-end: exit 90%;
+ }
+ "#,
+ indoc! {r#"
+ .foo {
+ animation-range: entry 10% exit 90%;
+ }
+ "#},
+ );
+ test(
+ r#"
+ .foo {
+ animation-range-start: entry 0%;
+ animation-range-end: entry 100%;
+ }
+ "#,
+ indoc! {r#"
+ .foo {
+ animation-range: entry;
+ }
+ "#},
+ );
+ test(
+ r#"
+ .foo {
+ animation-range-start: entry 0%;
+ animation-range-end: exit 100%;
+ }
+ "#,
+ indoc! {r#"
+ .foo {
+ animation-range: entry exit;
+ }
+ "#},
+ );
+ test(
+ r#"
+ .foo {
+ animation-range-start: 10%;
+ animation-range-end: normal;
+ }
+ "#,
+ indoc! {r#"
+ .foo {
+ animation-range: 10%;
+ }
+ "#},
+ );
+ test(
+ r#"
+ .foo {
+ animation-range-start: 10%;
+ animation-range-end: 90%;
+ }
+ "#,
+ indoc! {r#"
+ .foo {
+ animation-range: 10% 90%;
+ }
+ "#},
+ );
+ test(
+ r#"
+ .foo {
+ animation-range-start: entry 10%;
+ animation-range-end: exit 100%;
+ }
+ "#,
+ indoc! {r#"
+ .foo {
+ animation-range: entry 10% exit;
+ }
+ "#},
+ );
+ test(
+ r#"
+ .foo {
+ animation-range-start: 10%;
+ animation-range-end: exit 90%;
+ }
+ "#,
+ indoc! {r#"
+ .foo {
+ animation-range: 10% exit 90%;
+ }
+ "#},
+ );
+ test(
+ r#"
+ .foo {
+ animation-range-start: entry 10%;
+ animation-range-end: 90%;
+ }
+ "#,
+ indoc! {r#"
+ .foo {
+ animation-range: entry 10% 90%;
+ }
+ "#},
+ );
+ test(
+ r#"
+ .foo {
+ animation-range: entry;
+ animation-range-end: 90%;
+ }
+ "#,
+ indoc! {r#"
+ .foo {
+ animation-range: entry 90%;
+ }
+ "#},
+ );
+ test(
+ r#"
+ .foo {
+ animation-range: entry;
+ animation-range-end: var(--end);
+ }
+ "#,
+ indoc! {r#"
+ .foo {
+ animation-range: entry;
+ animation-range-end: var(--end);
+ }
+ "#},
+ );
+ test(
+ r#"
+ .foo {
+ animation-range-start: entry 10%, entry 50%;
+ animation-range-end: exit 90%;
+ }
+ "#,
+ indoc! {r#"
+ .foo {
+ animation-range-start: entry 10%, entry 50%;
+ animation-range-end: exit 90%;
+ }
+ "#},
+ );
+ test(
+ r#"
+ .foo {
+ animation-range-start: entry 10%, entry 50%;
+ animation-range-end: exit 90%, exit 100%;
+ }
+ "#,
+ indoc! {r#"
+ .foo {
+ animation-range: entry 10% exit 90%, entry 50% exit;
+ }
+ "#},
+ );
+ test(
+ r#"
+ .foo {
+ animation-range: entry;
+ animation-range-end: 90%;
+ animation: spin 100ms;
+ }
+ "#,
+ indoc! {r#"
+ .foo {
+ animation: .1s spin;
+ }
+ "#},
+ );
+ test(
+ r#"
+ .foo {
+ animation: spin 100ms;
+ animation-range: entry;
+ animation-range-end: 90%;
+ }
+ "#,
+ indoc! {r#"
+ .foo {
+ animation: .1s spin;
+ animation-range: entry 90%;
+ }
+ "#},
+ );
+ test(
+ r#"
+ .foo {
+ animation-range: entry;
+ animation-range-end: 90%;
+ animation: var(--animation) 100ms;
+ }
+ "#,
+ indoc! {r#"
+ .foo {
+ animation: var(--animation) .1s;
+ }
+ "#},
+ );
}
#[test]
diff --git a/src/properties/animation.rs b/src/properties/animation.rs
index de83b9fa..9855127c 100644
--- a/src/properties/animation.rs
+++ b/src/properties/animation.rs
@@ -12,6 +12,7 @@ use crate::properties::{Property, PropertyId, TokenOrValue, VendorPrefix};
use crate::traits::{Parse, PropertyHandler, Shorthand, ToCss, Zero};
use crate::values::ident::DashedIdent;
use crate::values::number::CSSNumber;
+use crate::values::percentage::Percentage;
use crate::values::size::Size2D;
use crate::values::string::CSSString;
use crate::values::{easing::EasingFunction, ident::CustomIdent, time::Time};
@@ -21,7 +22,7 @@ use cssparser::*;
use itertools::izip;
use smallvec::SmallVec;
-use super::LengthPercentageOrAuto;
+use super::{LengthPercentage, LengthPercentageOrAuto};
/// A value for the [animation-name](https://drafts.csswg.org/css-animations/#animation-name) property.
#[derive(Debug, Clone, PartialEq, Parse)]
@@ -375,6 +376,201 @@ impl ToCss for ViewTimeline {
}
}
+/// A [view progress timeline range](https://drafts.csswg.org/scroll-animations/#view-timelines-ranges)
+#[derive(Debug, Clone, PartialEq, Parse, ToCss)]
+#[cfg_attr(feature = "visitor", derive(Visit))]
+#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
+#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
+#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
+pub enum TimelineRangeName {
+ /// Represents the full range of the view progress timeline.
+ Cover,
+ /// Represents the range during which the principal box is either fully contained by,
+ /// or fully covers, its view progress visibility range within the scrollport.
+ Contain,
+ /// Represents the range during which the principal box is entering the view progress visibility range.
+ Entry,
+ /// Represents the range during which the principal box is exiting the view progress visibility range.
+ Exit,
+ /// Represents the range during which the principal box crosses the end border edge.
+ EntryCrossing,
+ /// Represents the range during which the principal box crosses the start border edge.
+ ExitCrossing,
+}
+
+/// A value for the [animation-range-start](https://drafts.csswg.org/scroll-animations/#animation-range-start)
+/// or [animation-range-end](https://drafts.csswg.org/scroll-animations/#animation-range-end) property.
+#[derive(Debug, Clone, PartialEq)]
+#[cfg_attr(feature = "visitor", derive(Visit))]
+#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
+#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
+#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
+pub enum AnimationAttachmentRange {
+ /// The start of the animation’s attachment range is the start of its associated timeline.
+ Normal,
+ /// The animation attachment range starts at the specified point on the timeline measuring from the start of the timeline.
+ LengthPercentage(LengthPercentage),
+ /// The animation attachment range starts at the specified point on the timeline measuring from the start of the specified named timeline range.
+ TimelineRange {
+ /// The name of the timeline range.
+ name: TimelineRangeName,
+ /// The offset from the start of the named timeline range.
+ offset: LengthPercentage,
+ },
+}
+
+impl<'i> AnimationAttachmentRange {
+ fn parse<'t>(input: &mut Parser<'i, 't>, default: f32) -> Result