From bdf4a7754fef331bfe372ee2446428924ce4a1ac Mon Sep 17 00:00:00 2001
From: OJ Kwon <1210596+kwonoj@users.noreply.github.com>
Date: Tue, 27 Feb 2024 12:04:10 -0800
Subject: [PATCH 001/160] fix(visitor): check type for the underlying js object
(#684)
---
napi/src/transformer.rs | 33 +++++++++++++++++++++++++++------
1 file changed, 27 insertions(+), 6 deletions(-)
diff --git a/napi/src/transformer.rs b/napi/src/transformer.rs
index 111ff2cd..044daf5d 100644
--- a/napi/src/transformer.rs
+++ b/napi/src/transformer.rs
@@ -99,7 +99,14 @@ impl Visitors {
fn named(&self, stage: VisitStage, name: &str) -> Option {
self
.for_stage(stage)
- .and_then(|m| m.get_named_property::(name).ok())
+ .and_then(|m| m.get_named_property::(name).ok())
+ .and_then(|v| {
+ if let Ok(ValueType::Function) = v.get_type() {
+ v.try_into().ok()
+ } else {
+ None
+ }
+ })
}
fn custom(&self, stage: VisitStage, obj: &str, name: &str) -> Option {
@@ -177,7 +184,14 @@ impl JsVisitor {
let mut types = VisitTypes::empty();
macro_rules! get {
($name: literal, $( $t: ident )|+) => {{
- let res: Option = visitor.get_named_property($name).ok();
+ let res: Option = visitor.get_named_property::($name).ok().and_then(|v| {
+ if let Ok(ValueType::Function) = v.get_type() {
+ v.try_into().ok()
+ } else {
+ None
+ }
+ });
+
if res.is_some() {
types |= $( VisitTypes::$t )|+;
}
@@ -190,12 +204,19 @@ impl JsVisitor {
macro_rules! map {
($name: literal, $( $t: ident )|+) => {{
- if let Ok(obj) = visitor.get_named_property::($name) {
+ let obj: Option = visitor.get_named_property::($name).ok().and_then(|v| {
+ if let Ok(ValueType::Object) = v.get_type() {
+ v.try_into().ok()
+ } else {
+ None
+ }
+ });
+
+ if obj.is_some() {
types |= $( VisitTypes::$t )|+;
- env.create_reference(obj).ok()
- } else {
- None
}
+
+ obj.and_then(|obj| env.create_reference(obj).ok())
}};
}
From a493a8b5b92b9ca11473581cb6be115b02b367cf Mon Sep 17 00:00:00 2001
From: Devon Govett
Date: Tue, 27 Feb 2024 20:58:20 -0500
Subject: [PATCH 002/160] update napi and fix more get_named_property calls
---
Cargo.lock | 14 +++++++-------
napi/src/lib.rs | 19 +++++++++++--------
napi/src/transformer.rs | 29 +++++------------------------
napi/src/utils.rs | 7 +++++++
node/Cargo.toml | 2 +-
5 files changed, 31 insertions(+), 40 deletions(-)
create mode 100644 napi/src/utils.rs
diff --git a/Cargo.lock b/Cargo.lock
index 97cbc6b1..a4e19974 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -408,12 +408,12 @@ dependencies = [
[[package]]
name = "ctor"
-version = "0.1.26"
+version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096"
+checksum = "ad291aa74992b9b7a7e88c38acbbf6ad7e107f1d90ee8775b7bc1fc3394f485c"
dependencies = [
"quote",
- "syn 1.0.109",
+ "syn 2.0.39",
]
[[package]]
@@ -890,17 +890,17 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "napi"
-version = "2.10.3"
+version = "2.15.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a967e17e9ba4e015a7bf9b92f90aa8dc321c6d913f6a6d2afd5b66a8ab36fc81"
+checksum = "72e0dc78e0524286630914db66e31bad70160e379705a9ce92e0161ce2389d89"
dependencies = [
- "bitflags 1.3.2",
+ "bitflags 2.4.1",
"ctor",
+ "napi-derive",
"napi-sys",
"once_cell",
"serde",
"serde_json",
- "thread_local",
]
[[package]]
diff --git a/napi/src/lib.rs b/napi/src/lib.rs
index 8127b18e..e756f591 100644
--- a/napi/src/lib.rs
+++ b/napi/src/lib.rs
@@ -24,6 +24,7 @@ mod at_rule_parser;
mod threadsafe_function;
#[cfg(feature = "visitor")]
mod transformer;
+mod utils;
#[cfg(feature = "visitor")]
use transformer::JsVisitor;
@@ -34,6 +35,8 @@ struct JsVisitor;
#[cfg(feature = "visitor")]
use lightningcss::visitor::Visit;
+use utils::get_named_property;
+
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
struct TransformResult<'i> {
@@ -73,7 +76,7 @@ impl<'i> TransformResult<'i> {
#[cfg(feature = "visitor")]
fn get_visitor(env: Env, opts: &JsObject) -> Option {
- if let Ok(visitor) = opts.get_named_property::("visitor") {
+ if let Ok(visitor) = get_named_property::(opts, "visitor") {
Some(JsVisitor::new(env, visitor))
} else {
None
@@ -243,7 +246,7 @@ mod bundle {
// Otherwise, send the result immediately.
if result.is_promise()? {
let result: JsObject = result.try_into()?;
- let then: JsFunction = result.get_named_property("then")?;
+ let then: JsFunction = get_named_property(&result, "then")?;
let tx2 = tx.clone();
let cb = env.create_function_from_closure("callback", move |ctx| {
let res = ctx.get::(0)?.into_utf8()?;
@@ -306,9 +309,9 @@ mod bundle {
let config: BundleConfig = ctx.env.from_js_value(&opts)?;
- if let Ok(resolver) = opts.get_named_property::("resolver") {
+ if let Ok(resolver) = get_named_property::(&opts, "resolver") {
let read = if resolver.has_named_property("read")? {
- let read = resolver.get_named_property::("read")?;
+ let read = get_named_property::(&resolver, "read")?;
Some(ThreadsafeFunction::create(
ctx.env.raw(),
unsafe { read.raw() },
@@ -320,7 +323,7 @@ mod bundle {
};
let resolve = if resolver.has_named_property("resolve")? {
- let resolve = resolver.get_named_property::("resolve")?;
+ let resolve = get_named_property::(&resolver, "resolve")?;
Some(ThreadsafeFunction::create(
ctx.env.raw(),
unsafe { resolve.raw() },
@@ -427,10 +430,10 @@ mod bundle {
let opts = ctx.get::(0)?;
let mut visitor = get_visitor(*ctx.env, &opts);
- let resolver = opts.get_named_property::("resolver")?;
- let read = resolver.get_named_property::("read")?;
+ let resolver = get_named_property::(opts, "resolver")?;
+ let read = get_named_property::(resolver, "read")?;
let resolve = if resolver.has_named_property("resolve")? {
- let resolve = resolver.get_named_property::("resolve")?;
+ let resolve = get_named_property::(resolver, "resolve")?;
Some(ctx.env.create_reference(resolve)?)
} else {
None
diff --git a/napi/src/transformer.rs b/napi/src/transformer.rs
index 044daf5d..ad20c611 100644
--- a/napi/src/transformer.rs
+++ b/napi/src/transformer.rs
@@ -24,7 +24,7 @@ use napi::{Env, JsFunction, JsObject, JsUnknown, Ref, ValueType};
use serde::{Deserialize, Serialize};
use smallvec::SmallVec;
-use crate::at_rule_parser::AtRule;
+use crate::{at_rule_parser::AtRule, utils::get_named_property};
pub struct JsVisitor {
env: Env,
@@ -99,14 +99,7 @@ impl Visitors {
fn named(&self, stage: VisitStage, name: &str) -> Option {
self
.for_stage(stage)
- .and_then(|m| m.get_named_property::(name).ok())
- .and_then(|v| {
- if let Ok(ValueType::Function) = v.get_type() {
- v.try_into().ok()
- } else {
- None
- }
- })
+ .and_then(|m| get_named_property::(m, name).ok())
}
fn custom(&self, stage: VisitStage, obj: &str, name: &str) -> Option {
@@ -119,7 +112,7 @@ impl Visitors {
Ok(ValueType::Object) => {
let o: napi::Result = v.try_into();
if let Ok(o) = o {
- return o.get_named_property::(name).ok();
+ return get_named_property::(&o, name).ok();
}
}
_ => {}
@@ -184,13 +177,7 @@ impl JsVisitor {
let mut types = VisitTypes::empty();
macro_rules! get {
($name: literal, $( $t: ident )|+) => {{
- let res: Option = visitor.get_named_property::($name).ok().and_then(|v| {
- if let Ok(ValueType::Function) = v.get_type() {
- v.try_into().ok()
- } else {
- None
- }
- });
+ let res: Option = get_named_property(&visitor, $name).ok();
if res.is_some() {
types |= $( VisitTypes::$t )|+;
@@ -204,13 +191,7 @@ impl JsVisitor {
macro_rules! map {
($name: literal, $( $t: ident )|+) => {{
- let obj: Option = visitor.get_named_property::($name).ok().and_then(|v| {
- if let Ok(ValueType::Object) = v.get_type() {
- v.try_into().ok()
- } else {
- None
- }
- });
+ let obj: Option = get_named_property(&visitor, $name).ok();
if obj.is_some() {
types |= $( VisitTypes::$t )|+;
diff --git a/napi/src/utils.rs b/napi/src/utils.rs
new file mode 100644
index 00000000..90ac1495
--- /dev/null
+++ b/napi/src/utils.rs
@@ -0,0 +1,7 @@
+use napi::{Error, JsObject, JsUnknown, Result};
+
+// Workaround for https://github.com/napi-rs/napi-rs/issues/1641
+pub fn get_named_property>(obj: &JsObject, property: &str) -> Result {
+ let unknown = obj.get_named_property::(property)?;
+ T::try_from(unknown)
+}
diff --git a/node/Cargo.toml b/node/Cargo.toml
index b85a64a9..4f9e8b75 100644
--- a/node/Cargo.toml
+++ b/node/Cargo.toml
@@ -10,7 +10,7 @@ crate-type = ["cdylib"]
[dependencies]
lightningcss-napi = { version = "0.1.0", path = "../napi", features = ["bundler", "visitor"] }
-napi = {version = "=2.10.3", default-features = false, features = ["compat-mode"]}
+napi = {version = "2.15.4", default-features = false, features = ["compat-mode"]}
napi-derive = "2"
[target.'cfg(target_os = "macos")'.dependencies]
From 362bce5bc47d6420fdb20e7bfc4228a8bd80c5d2 Mon Sep 17 00:00:00 2001
From: Devon Govett
Date: Tue, 27 Feb 2024 22:28:30 -0500
Subject: [PATCH 003/160] fix wasm
---
napi/src/lib.rs | 8 ++++----
node/src/lib.rs | 14 +++++++++-----
wasm/index.mjs | 1 +
wasm/wasm-node.mjs | 1 +
4 files changed, 15 insertions(+), 9 deletions(-)
diff --git a/napi/src/lib.rs b/napi/src/lib.rs
index e756f591..cc3ce78e 100644
--- a/napi/src/lib.rs
+++ b/napi/src/lib.rs
@@ -430,10 +430,10 @@ mod bundle {
let opts = ctx.get::(0)?;
let mut visitor = get_visitor(*ctx.env, &opts);
- let resolver = get_named_property::(opts, "resolver")?;
- let read = get_named_property::(resolver, "read")?;
+ let resolver = get_named_property::(&opts, "resolver")?;
+ let read = get_named_property::(&resolver, "read")?;
let resolve = if resolver.has_named_property("resolve")? {
- let resolve = get_named_property::(resolver, "resolve")?;
+ let resolve = get_named_property::(&resolver, "resolve")?;
Some(ctx.env.create_reference(resolve)?)
} else {
None
@@ -507,7 +507,7 @@ mod bundle {
return Err(napi::Error::from(error));
}
if result.is_null() {
- return Err(napi::Error::new(napi::Status::GenericFailure, "No result".into()));
+ return Err(napi::Error::new(napi::Status::GenericFailure, "No result".to_string()));
}
value = unsafe { JsUnknown::from_raw(env.raw(), result)? };
diff --git a/node/src/lib.rs b/node/src/lib.rs
index 16030b51..e429b0f2 100644
--- a/node/src/lib.rs
+++ b/node/src/lib.rs
@@ -41,12 +41,16 @@ fn init(mut exports: JsObject) -> napi::Result<()> {
#[cfg(target_arch = "wasm32")]
#[no_mangle]
-pub unsafe fn napi_register_wasm_v1(raw_env: napi::sys::napi_env, raw_exports: napi::sys::napi_value) {
- use napi::{Env, JsObject, NapiValue};
+pub fn register_module() {
+ unsafe fn register(raw_env: napi::sys::napi_env, raw_exports: napi::sys::napi_value) -> napi::Result<()> {
+ use napi::{Env, JsObject, NapiValue};
- let env = Env::from_raw(raw_env);
- let exports = JsObject::from_raw_unchecked(raw_env, raw_exports);
- init(exports);
+ let env = Env::from_raw(raw_env);
+ let exports = JsObject::from_raw_unchecked(raw_env, raw_exports);
+ init(exports)
+ }
+
+ napi::bindgen_prelude::register_module_exports(register)
}
#[cfg(target_arch = "wasm32")]
diff --git a/wasm/index.mjs b/wasm/index.mjs
index 65743ae7..7120fd0d 100644
--- a/wasm/index.mjs
+++ b/wasm/index.mjs
@@ -28,6 +28,7 @@ export default async function init(input) {
}
}))
.then(({instance}) => {
+ instance.exports.register_module();
env = new Environment(instance);
bundleAsyncInternal = createBundleAsync(env);
wasm = env.exports;
diff --git a/wasm/wasm-node.mjs b/wasm/wasm-node.mjs
index e361dc51..7e8c69f2 100644
--- a/wasm/wasm-node.mjs
+++ b/wasm/wasm-node.mjs
@@ -15,6 +15,7 @@ let instance = new WebAssembly.Instance(wasmModule, {
},
},
});
+instance.exports.register_module();
let env = new Environment(instance);
let wasm = env.exports;
let bundleAsyncInternal = createBundleAsync(env);
From ecead1470b11189104c69817ea7a44f492e9b188 Mon Sep 17 00:00:00 2001
From: Devon Govett
Date: Tue, 27 Feb 2024 22:44:33 -0500
Subject: [PATCH 004/160] fix version in cargo.toml
---
napi/Cargo.toml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/napi/Cargo.toml b/napi/Cargo.toml
index 028a3450..3360a9cd 100644
--- a/napi/Cargo.toml
+++ b/napi/Cargo.toml
@@ -16,7 +16,7 @@ bundler = ["dep:crossbeam-channel", "dep:rayon"]
serde = { version = "1.0.123", features = ["derive"] }
serde_bytes = "0.11.5"
cssparser = "0.33.0"
-lightningcss = { path = "../", features = ["nodejs", "serde"] }
+lightningcss = { version = "1.0.0-alpha.54", path = "../", features = ["nodejs", "serde"] }
parcel_sourcemap = { version = "2.1.1", features = ["json"] }
serde-detach = "0.0.1"
smallvec = { version = "1.7.0", features = ["union"] }
From 7dd0cb3cd77a40d4ead820ed95d60877b042716c Mon Sep 17 00:00:00 2001
From: Brennan Kinney <5098581+polarathene@users.noreply.github.com>
Date: Mon, 4 Mar 2024 17:45:24 +1300
Subject: [PATCH 005/160] docs: Update reference to Stylo (#691)
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 03554462..f44d7b3c 100644
--- a/README.md
+++ b/README.md
@@ -9,7 +9,7 @@ An extremely fast CSS parser, transformer, and minifier written in Rust. Use it
- **Extremely fast** – Parsing and minifying large files is completed in milliseconds, often with significantly smaller output than other tools. See [benchmarks](#benchmarks) below.
- **Typed property values** – many other CSS parsers treat property values as an untyped series of tokens. This means that each transformer that wants to do something with these values must interpret them itself, leading to duplicate work and inconsistencies. Lightning CSS parses all values using the grammar from the CSS specification, and exposes a specific value type for each property.
-- **Browser-grade parser** – Lightning CSS is built on the [cssparser](https://github.com/servo/rust-cssparser) and [selectors](https://github.com/servo/servo/tree/master/components/selectors) crates created by Mozilla and used by Firefox and Servo. These provide a solid general purpose CSS-parsing foundation on top of which Lightning CSS implements support for all specific CSS rules and properties.
+- **Browser-grade parser** – Lightning CSS is built on the [cssparser](https://github.com/servo/rust-cssparser) and [selectors](https://github.com/servo/stylo/tree/main/selectors) crates created by Mozilla and used by Firefox and Servo. These provide a solid general purpose CSS-parsing foundation on top of which Lightning CSS implements support for all specific CSS rules and properties.
- **Minification** – One of the main purposes of Lightning CSS is to minify CSS to make it smaller. This includes many optimizations including:
- Combining longhand properties into shorthands where possible.
- Merging adjacent rules with the same selectors or declarations when it is safe to do so.
From e37b9ed5e303b3e7372de092345cfe6e86ef9cb8 Mon Sep 17 00:00:00 2001
From: Robin Malfait
Date: Thu, 7 Mar 2024 17:13:32 +0100
Subject: [PATCH 006/160] disable transform optimizations (#694)
---
src/lib.rs | 96 +++++++++++++++-------------
src/properties/transform.rs | 124 ++++++++++++++++++++----------------
2 files changed, 122 insertions(+), 98 deletions(-)
diff --git a/src/lib.rs b/src/lib.rs
index efaf28c9..8c70f8ad 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -11552,14 +11552,16 @@ mod tests {
".foo { transform: matrix3d(1, 0, 0, 0, 0, 1, 6, 0, 0, 0, 1, 0, 50, 100, 0, 1.1)",
".foo{transform:matrix3d(1,0,0,0,0,1,6,0,0,0,1,0,50,100,0,1.1)}",
);
- minify_test(
- ".foo{transform:translate(100px,200px) rotate(45deg) skew(10deg) scale(2)}",
- ".foo{transform:matrix(1.41421,1.41421,-1.16485,1.66358,100,200)}",
- );
- minify_test(
- ".foo{transform:translate(200px,300px) translate(100px,200px) scale(2)}",
- ".foo{transform:matrix(2,0,0,2,300,500)}",
- );
+ // TODO: Re-enable with a better solution
+ // See: https://github.com/parcel-bundler/lightningcss/issues/288
+ // minify_test(
+ // ".foo{transform:translate(100px,200px) rotate(45deg) skew(10deg) scale(2)}",
+ // ".foo{transform:matrix(1.41421,1.41421,-1.16485,1.66358,100,200)}",
+ // );
+ // minify_test(
+ // ".foo{transform:translate(200px,300px) translate(100px,200px) scale(2)}",
+ // ".foo{transform:matrix(2,0,0,2,300,500)}",
+ // );
minify_test(
".foo{transform:translate(100px,200px) rotate(45deg)}",
".foo{transform:translate(100px,200px)rotate(45deg)}",
@@ -11568,34 +11570,36 @@ mod tests {
".foo{transform:rotate3d(1, 1, 1, 45deg) translate3d(100px, 100px, 10px)}",
".foo{transform:rotate3d(1,1,1,45deg)translate3d(100px,100px,10px)}",
);
- minify_test(
- ".foo{transform:translate3d(100px, 100px, 10px) skew(10deg) scale3d(2, 3, 4)}",
- ".foo{transform:matrix3d(2,0,0,0,.528981,3,0,0,0,0,4,0,100,100,10,1)}",
- );
- minify_test(
- ".foo{transform:matrix3d(0.804737854124365, 0.5058793634016805, -0.31061721752604554, 0, -0.31061721752604554, 0.804737854124365, 0.5058793634016805, 0, 0.5058793634016805, -0.31061721752604554, 0.804737854124365, 0, 100, 100, 10, 1)}",
- ".foo{transform:translate3d(100px,100px,10px)rotate3d(1,1,1,45deg)}"
- );
- minify_test(
- ".foo{transform:matrix3d(1, 0, 0, 0, 0, 0.7071067811865476, 0.7071067811865475, 0, 0, -0.7071067811865475, 0.7071067811865476, 0, 100, 100, 10, 1)}",
- ".foo{transform:translate3d(100px,100px,10px)rotateX(45deg)}"
- );
- minify_test(
- ".foo{transform:translate3d(100px, 200px, 10px) translate(100px, 100px)}",
- ".foo{transform:translate3d(200px,300px,10px)}",
- );
- minify_test(
- ".foo{transform:rotate(45deg) rotate(45deg)}",
- ".foo{transform:rotate(90deg)}",
- );
- minify_test(
- ".foo{transform:matrix(0.7071067811865476, 0.7071067811865475, -0.7071067811865475, 0.7071067811865476, 100, 100)}",
- ".foo{transform:translate(100px,100px)rotate(45deg)}"
- );
- minify_test(
- ".foo{transform:translateX(2in) translateX(50px)}",
- ".foo{transform:translate(242px)}",
- );
+ // TODO: Re-enable with a better solution
+ // See: https://github.com/parcel-bundler/lightningcss/issues/288
+ // minify_test(
+ // ".foo{transform:translate3d(100px, 100px, 10px) skew(10deg) scale3d(2, 3, 4)}",
+ // ".foo{transform:matrix3d(2,0,0,0,.528981,3,0,0,0,0,4,0,100,100,10,1)}",
+ // );
+ // minify_test(
+ // ".foo{transform:matrix3d(0.804737854124365, 0.5058793634016805, -0.31061721752604554, 0, -0.31061721752604554, 0.804737854124365, 0.5058793634016805, 0, 0.5058793634016805, -0.31061721752604554, 0.804737854124365, 0, 100, 100, 10, 1)}",
+ // ".foo{transform:translate3d(100px,100px,10px)rotate3d(1,1,1,45deg)}"
+ // );
+ // minify_test(
+ // ".foo{transform:matrix3d(1, 0, 0, 0, 0, 0.7071067811865476, 0.7071067811865475, 0, 0, -0.7071067811865475, 0.7071067811865476, 0, 100, 100, 10, 1)}",
+ // ".foo{transform:translate3d(100px,100px,10px)rotateX(45deg)}"
+ // );
+ // minify_test(
+ // ".foo{transform:translate3d(100px, 200px, 10px) translate(100px, 100px)}",
+ // ".foo{transform:translate3d(200px,300px,10px)}",
+ // );
+ // minify_test(
+ // ".foo{transform:rotate(45deg) rotate(45deg)}",
+ // ".foo{transform:rotate(90deg)}",
+ // );
+ // minify_test(
+ // ".foo{transform:matrix(0.7071067811865476, 0.7071067811865475, -0.7071067811865475, 0.7071067811865476, 100, 100)}",
+ // ".foo{transform:translate(100px,100px)rotate(45deg)}"
+ // );
+ // minify_test(
+ // ".foo{transform:translateX(2in) translateX(50px)}",
+ // ".foo{transform:translate(242px)}",
+ // );
minify_test(
".foo{transform:translateX(calc(2in + 50px))}",
".foo{transform:translate(242px)}",
@@ -11660,7 +11664,9 @@ mod tests {
minify_test(".foo { scale: 1 0 1 }", ".foo{scale:1 0}");
minify_test(".foo { scale: 1 0 0 }", ".foo{scale:1 0 0}");
- minify_test(".foo { transform: scale(3); scale: 0.5 }", ".foo{transform:scale(1.5)}");
+ // TODO: Re-enable with a better solution
+ // See: https://github.com/parcel-bundler/lightningcss/issues/288
+ // minify_test(".foo { transform: scale(3); scale: 0.5 }", ".foo{transform:scale(1.5)}");
minify_test(".foo { scale: 0.5; transform: scale(3); }", ".foo{transform:scale(3)}");
prefix_test(
@@ -26076,13 +26082,15 @@ mod tests {
"@property --property-name{syntax:\"custom|\";inherits:false;initial-value:#ff0}",
);
- minify_test(r#"
- @property --property-name {
- syntax: '';
- inherits: false;
- initial-value: translate(200px,300px) translate(100px,200px) scale(2);
- }
- "#, "@property --property-name{syntax:\"\";inherits:false;initial-value:matrix(2,0,0,2,300,500)}");
+ // TODO: Re-enable with a better solution
+ // See: https://github.com/parcel-bundler/lightningcss/issues/288
+ // minify_test(r#"
+ // @property --property-name {
+ // syntax: '';
+ // inherits: false;
+ // initial-value: translate(200px,300px) translate(100px,200px) scale(2);
+ // }
+ // "#, "@property --property-name{syntax:\"\";inherits:false;initial-value:matrix(2,0,0,2,300,500)}");
minify_test(
r#"
diff --git a/src/properties/transform.rs b/src/properties/transform.rs
index 87699837..75cd909a 100644
--- a/src/properties/transform.rs
+++ b/src/properties/transform.rs
@@ -57,64 +57,80 @@ impl ToCss for TransformList {
return Ok(());
}
+ // TODO: Re-enable with a better solution
+ // See: https://github.com/parcel-bundler/lightningcss/issues/288
if dest.minify {
- // Combine transforms into a single matrix.
- if let Some(matrix) = self.to_matrix() {
- // Generate based on the original transforms.
- let mut base = String::new();
- self.to_css_base(&mut Printer::new(
- &mut base,
- PrinterOptions {
- minify: true,
- ..PrinterOptions::default()
- },
- ))?;
-
- // Decompose the matrix into transform functions if possible.
- // If the resulting length is shorter than the original, use it.
- if let Some(d) = matrix.decompose() {
- let mut decomposed = String::new();
- d.to_css_base(&mut Printer::new(
- &mut decomposed,
- PrinterOptions {
- minify: true,
- ..PrinterOptions::default()
- },
- ))?;
- if decomposed.len() < base.len() {
- base = decomposed;
- }
- }
-
- // Also generate a matrix() or matrix3d() representation and compare that.
- let mut mat = String::new();
- if let Some(matrix) = matrix.to_matrix2d() {
- Transform::Matrix(matrix).to_css(&mut Printer::new(
- &mut mat,
- PrinterOptions {
- minify: true,
- ..PrinterOptions::default()
- },
- ))?
- } else {
- Transform::Matrix3d(matrix).to_css(&mut Printer::new(
- &mut mat,
- PrinterOptions {
- minify: true,
- ..PrinterOptions::default()
- },
- ))?
- }
+ let mut base = String::new();
+ self.to_css_base(&mut Printer::new(
+ &mut base,
+ PrinterOptions {
+ minify: true,
+ ..PrinterOptions::default()
+ },
+ ))?;
- if mat.len() < base.len() {
- dest.write_str(&mat)?;
- } else {
- dest.write_str(&base)?;
- }
+ dest.write_str(&base)?;
- return Ok(());
- }
+ return Ok(());
}
+ // if dest.minify {
+ // // Combine transforms into a single matrix.
+ // if let Some(matrix) = self.to_matrix() {
+ // // Generate based on the original transforms.
+ // let mut base = String::new();
+ // self.to_css_base(&mut Printer::new(
+ // &mut base,
+ // PrinterOptions {
+ // minify: true,
+ // ..PrinterOptions::default()
+ // },
+ // ))?;
+ //
+ // // Decompose the matrix into transform functions if possible.
+ // // If the resulting length is shorter than the original, use it.
+ // if let Some(d) = matrix.decompose() {
+ // let mut decomposed = String::new();
+ // d.to_css_base(&mut Printer::new(
+ // &mut decomposed,
+ // PrinterOptions {
+ // minify: true,
+ // ..PrinterOptions::default()
+ // },
+ // ))?;
+ // if decomposed.len() < base.len() {
+ // base = decomposed;
+ // }
+ // }
+ //
+ // // Also generate a matrix() or matrix3d() representation and compare that.
+ // let mut mat = String::new();
+ // if let Some(matrix) = matrix.to_matrix2d() {
+ // Transform::Matrix(matrix).to_css(&mut Printer::new(
+ // &mut mat,
+ // PrinterOptions {
+ // minify: true,
+ // ..PrinterOptions::default()
+ // },
+ // ))?
+ // } else {
+ // Transform::Matrix3d(matrix).to_css(&mut Printer::new(
+ // &mut mat,
+ // PrinterOptions {
+ // minify: true,
+ // ..PrinterOptions::default()
+ // },
+ // ))?
+ // }
+ //
+ // if mat.len() < base.len() {
+ // dest.write_str(&mat)?;
+ // } else {
+ // dest.write_str(&base)?;
+ // }
+ //
+ // return Ok(());
+ // }
+ // }
self.to_css_base(dest)
}
From 6bd2761badb9d5434783acffcae35ef6c3311e06 Mon Sep 17 00:00:00 2001
From: Devon Govett
Date: Fri, 15 Mar 2024 00:02:34 -0400
Subject: [PATCH 007/160] Merge @supports declarations with the same property
(minus prefix) and value
---
src/lib.rs | 62 +++++++++++++++++++++++++++++++++++++++++++
src/properties/mod.rs | 22 ++++++++++++++-
src/rules/supports.rs | 33 +++++++++++++++++++++--
3 files changed, 114 insertions(+), 3 deletions(-)
diff --git a/src/lib.rs b/src/lib.rs
index 8c70f8ad..f13b7c48 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -13123,6 +13123,68 @@ mod tests {
..Default::default()
},
);
+ prefix_test(
+ r#"
+ @supports ((-webkit-backdrop-filter: blur(10px)) or (backdrop-filter: blur(10px))) {
+ div {
+ backdrop-filter: blur(10px);
+ }
+ }
+ "#,
+ indoc! { r#"
+ @supports ((-webkit-backdrop-filter: blur(10px)) or (backdrop-filter: blur(10px))) {
+ div {
+ -webkit-backdrop-filter: blur(10px);
+ backdrop-filter: blur(10px);
+ }
+ }
+ "#},
+ Browsers {
+ safari: Some(14 << 16),
+ ..Default::default()
+ },
+ );
+ prefix_test(
+ r#"
+ @supports ((-webkit-backdrop-filter: blur(20px)) or (backdrop-filter: blur(10px))) {
+ div {
+ backdrop-filter: blur(10px);
+ }
+ }
+ "#,
+ indoc! { r#"
+ @supports ((-webkit-backdrop-filter: blur(20px))) or ((-webkit-backdrop-filter: blur(10px)) or (backdrop-filter: blur(10px))) {
+ div {
+ -webkit-backdrop-filter: blur(10px);
+ backdrop-filter: blur(10px);
+ }
+ }
+ "#},
+ Browsers {
+ safari: Some(14 << 16),
+ ..Default::default()
+ },
+ );
+ prefix_test(
+ r#"
+ @supports ((-webkit-backdrop-filter: blur(10px)) or (backdrop-filter: blur(10px))) {
+ div {
+ backdrop-filter: blur(10px);
+ }
+ }
+ "#,
+ indoc! { r#"
+ @supports (backdrop-filter: blur(10px)) {
+ div {
+ backdrop-filter: blur(10px);
+ }
+ }
+ "#},
+ Browsers {
+ chrome: Some(80 << 16),
+ ..Default::default()
+ },
+ );
minify_test(
r#"
@supports (width: calc(10px * 2)) {
diff --git a/src/properties/mod.rs b/src/properties/mod.rs
index 6e39fc5d..845140f9 100644
--- a/src/properties/mod.rs
+++ b/src/properties/mod.rs
@@ -322,7 +322,7 @@ macro_rules! define_properties {
}
}
- fn with_prefix(&self, prefix: VendorPrefix) -> PropertyId<'i> {
+ pub(crate) fn with_prefix(&self, prefix: VendorPrefix) -> PropertyId<'i> {
use PropertyId::*;
match self {
$(
@@ -344,6 +344,26 @@ macro_rules! define_properties {
}
}
+ pub(crate) fn add_prefix(&mut self, prefix: VendorPrefix) {
+ use PropertyId::*;
+ match self {
+ $(
+ $(#[$meta])*
+ $property$((vp_name!($vp, p)))? => {
+ macro_rules! get_prefixed {
+ ($v: ty) => {{
+ *p |= prefix;
+ }};
+ () => {{}};
+ }
+
+ get_prefixed!($($vp)?)
+ },
+ )+
+ _ => {}
+ }
+ }
+
pub(crate) fn set_prefixes_for_targets(&mut self, targets: Targets) {
match self {
$(
diff --git a/src/rules/supports.rs b/src/rules/supports.rs
index 68c13e6a..04f1fdd3 100644
--- a/src/rules/supports.rs
+++ b/src/rules/supports.rs
@@ -1,5 +1,7 @@
//! The `@supports` rule.
+use std::collections::HashMap;
+
use super::Location;
use super::{CssRuleList, MinifyContext};
use crate::error::{MinifyError, ParserError, PrinterError};
@@ -159,6 +161,7 @@ impl<'i> Parse<'i> for SupportsCondition<'i> {
let in_parens = Self::parse_in_parens(input)?;
let mut expected_type = None;
let mut conditions = Vec::new();
+ let mut seen_declarations = HashMap::new();
loop {
let condition = input.try_parse(|input| {
@@ -185,14 +188,40 @@ impl<'i> Parse<'i> for SupportsCondition<'i> {
if let Ok(condition) = condition {
if conditions.is_empty() {
- conditions.push(in_parens.clone())
+ conditions.push(in_parens.clone());
+ if let SupportsCondition::Declaration { property_id, value } = &in_parens {
+ seen_declarations.insert((property_id.with_prefix(VendorPrefix::None), value.clone()), 0);
+ }
+ }
+
+ if let SupportsCondition::Declaration { property_id, value } = condition {
+ // Merge multiple declarations with the same property id (minus prefix) and value together.
+ let property_id = property_id.with_prefix(VendorPrefix::None);
+ let key = (property_id.clone(), value.clone());
+ if let Some(index) = seen_declarations.get(&key) {
+ if let SupportsCondition::Declaration {
+ property_id: cur_property,
+ ..
+ } = &mut conditions[*index]
+ {
+ cur_property.add_prefix(property_id.prefix());
+ }
+ } else {
+ seen_declarations.insert(key, conditions.len());
+ conditions.push(SupportsCondition::Declaration { property_id, value });
+ }
+ } else {
+ conditions.push(condition);
}
- conditions.push(condition)
} else {
break;
}
}
+ if conditions.len() == 1 {
+ return Ok(conditions.pop().unwrap());
+ }
+
match expected_type {
Some(1) => Ok(SupportsCondition::And(conditions)),
Some(2) => Ok(SupportsCondition::Or(conditions)),
From baa1a2b7fa52eeb3827f8edcc2e14de33cd69ad0 Mon Sep 17 00:00:00 2001
From: Devon Govett
Date: Fri, 15 Mar 2024 00:05:17 -0400
Subject: [PATCH 008/160] v1.24.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 a4e19974..db6c59ae 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -760,7 +760,7 @@ dependencies = [
[[package]]
name = "lightningcss"
-version = "1.0.0-alpha.54"
+version = "1.0.0-alpha.55"
dependencies = [
"ahash 0.8.7",
"assert_cmd",
diff --git a/Cargo.toml b/Cargo.toml
index 6806ca01..4e27dbf5 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -12,7 +12,7 @@ members = [
[package]
authors = ["Devon Govett "]
name = "lightningcss"
-version = "1.0.0-alpha.54"
+version = "1.0.0-alpha.55"
description = "A CSS parser, transformer, and minifier"
license = "MPL-2.0"
edition = "2021"
diff --git a/package.json b/package.json
index d8fcb832..c9fe3d72 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "lightningcss",
- "version": "1.24.0",
+ "version": "1.24.1",
"license": "MPL-2.0",
"description": "A CSS parser, transformer, and minifier written in Rust",
"main": "node/index.js",
From d7aeff3db67ee9d15e0fefce9251cf41c0b8ec44 Mon Sep 17 00:00:00 2001
From: Devon Govett
Date: Mon, 13 May 2024 19:19:35 -0700
Subject: [PATCH 009/160] 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 010/160] 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 011/160] 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 012/160] 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 013/160] 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 014/160] 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 015/160] 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 016/160] 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 017/160] 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 018/160] 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 019/160] 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 020/160] 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 021/160] 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 022/160] 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