diff --git a/.changeset/puny-jeans-mix.md b/.changeset/puny-jeans-mix.md new file mode 100644 index 00000000..535310af --- /dev/null +++ b/.changeset/puny-jeans-mix.md @@ -0,0 +1,7 @@ +--- +"@devup-ui/wasm": patch +--- + +css util support media query +support devup json interface with wrong char +optimize zero value diff --git a/Cargo.lock b/Cargo.lock index 6c0d31b6..102b7273 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -649,9 +649,9 @@ dependencies = [ [[package]] name = "oxc_allocator" -version = "0.77.0" +version = "0.77.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdbeef2a832aa69e8ca20a8ef9e37da18d1a827cf34d682685a09fe2b6caaef1" +checksum = "993101d424ea517a299b312786e6feeaeaa64a0f130cf7cc45e1c6a6e8847ac5" dependencies = [ "allocator-api2", "bumpalo", @@ -662,9 +662,9 @@ dependencies = [ [[package]] name = "oxc_ast" -version = "0.77.0" +version = "0.77.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8135343afd05522ca069c33c663b71f4836f5aa8f951384327126febead4c30" +checksum = "a651de56b9e2a0273b7b2291fcb35ad52142b59582ac7a44dff3899a9b73bdbc" dependencies = [ "bitflags", "oxc_allocator", @@ -678,9 +678,9 @@ dependencies = [ [[package]] name = "oxc_ast_macros" -version = "0.77.0" +version = "0.77.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c33f44b3c4e5e473fcac4e0319f51b44a800b99d8a421d0a9e96e123b2cc1a6" +checksum = "215515d242db3a88c93928446b62265a63f5a54d14d691f9d763f5fb61df3c25" dependencies = [ "phf", "proc-macro2", @@ -690,9 +690,9 @@ dependencies = [ [[package]] name = "oxc_ast_visit" -version = "0.77.0" +version = "0.77.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9266313d0d04b48898f7243fb05a03c10aab1c854d5f2efb41b201bed1e8ef1a" +checksum = "0de629999113e65cbf28c65f0b1a85edb041264544a01ebd8b90fb572ed4104a" dependencies = [ "oxc_allocator", "oxc_ast", @@ -702,9 +702,9 @@ dependencies = [ [[package]] name = "oxc_cfg" -version = "0.77.0" +version = "0.77.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "297c81043bd8adb4712e25ddeb6559703a8f6667ffa9b1968c089f53d41dbbaf" +checksum = "2e4b77125811315c710a833f99e6f57623842f2fd2f3607439770c329682c235" dependencies = [ "bitflags", "itertools 0.14.0", @@ -717,9 +717,9 @@ dependencies = [ [[package]] name = "oxc_codegen" -version = "0.77.0" +version = "0.77.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28369f773f38efa0e0377a789a1aaed9eae4f9c90abeb2f91aa6de343eda29e8" +checksum = "e01f5313f94aa1af6230e255b6b22a11b38dc047807d115937c8d90acc4291c3" dependencies = [ "bitflags", "cow-utils", @@ -738,18 +738,18 @@ dependencies = [ [[package]] name = "oxc_data_structures" -version = "0.77.0" +version = "0.77.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5514bed9595363bfa66bfcf6dcecedb0ed5c40f162c429a70893edb825dba9db" +checksum = "ccdbefc90ed80b5bebddefde04c29a1e97c0ef7965dc8333aee577862dd0d099" dependencies = [ "rustversion", ] [[package]] name = "oxc_diagnostics" -version = "0.77.0" +version = "0.77.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29e47cf799dd9d84be89dd3af9a110232fa6f9b305bf32f65df364c3a85dec76" +checksum = "dbd1cf40a3b5a93083fb038c8eb52e10ab003a7ad5cef8026dd640b1bea5e6b0" dependencies = [ "cow-utils", "oxc-miette", @@ -758,9 +758,9 @@ dependencies = [ [[package]] name = "oxc_ecmascript" -version = "0.77.0" +version = "0.77.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8da2043abf37c986c29fa44470b7f41f94d147c626d2db8e9edc69b867a07b8b" +checksum = "a96f5cb2cafa3c3bc0b14799d8dfe242be2be48b92ba77293db094d72b4a9c10" dependencies = [ "num-bigint", "num-traits", @@ -771,9 +771,9 @@ dependencies = [ [[package]] name = "oxc_estree" -version = "0.77.0" +version = "0.77.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11c7b19dfa467408ae3e1f8a52bdf0b79e421f0811bef9d57df44369ff5f14a8" +checksum = "7de1227584df59c254a51465254f9e75a407e2329c59686fd2ff5b1659816568" [[package]] name = "oxc_index" @@ -783,9 +783,9 @@ checksum = "2fa07b0cfa997730afed43705766ef27792873fdf5215b1391949fec678d2392" [[package]] name = "oxc_parser" -version = "0.77.0" +version = "0.77.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "760adcacb436921e78a256ed467fa3cc4cd27147542ac0f94bcaf4a900a4002a" +checksum = "81476a01b11f87ebfb63c3a462333000a2a3fa40a29ec8699e8215f5ef7439ec" dependencies = [ "bitflags", "cow-utils", @@ -806,9 +806,9 @@ dependencies = [ [[package]] name = "oxc_regular_expression" -version = "0.77.0" +version = "0.77.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6af8abf0a50098480239f588f6f997148c48e0f062b78b32aedf9a7a380442cb" +checksum = "30b692c86bb96fae9d12869403b670c2a76c3aaae220f428281370e7e66dc96e" dependencies = [ "bitflags", "oxc_allocator", @@ -822,9 +822,9 @@ dependencies = [ [[package]] name = "oxc_semantic" -version = "0.77.0" +version = "0.77.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f67db9a6930a4dfcc849ee5cc95825882e50783f4e4e2550db4cd67f2f486121" +checksum = "0872f70f43ce192a554549140d7be117ef527a2b0b651e5db9386db71cb458d3" dependencies = [ "itertools 0.14.0", "oxc_allocator", @@ -858,9 +858,9 @@ dependencies = [ [[package]] name = "oxc_span" -version = "0.77.0" +version = "0.77.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9be736965e94fed8a096679f493ef794718821ffc6a3e509cf6dc6c10f5d38a" +checksum = "d379518d49fb763cb07f38d86eaad471d6ca71547c48c0c4fcae28d733e99b6e" dependencies = [ "compact_str", "oxc-miette", @@ -871,9 +871,9 @@ dependencies = [ [[package]] name = "oxc_syntax" -version = "0.77.0" +version = "0.77.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d69df31e7fc11197b27a3edfb316d0f9bf51778f824b1dafbca68c8fac59898" +checksum = "1b96724d9883c251e889825640fa409518d8a614b1832764861b964e58f8eac5" dependencies = [ "bitflags", "cow-utils", diff --git a/apps/landing/package.json b/apps/landing/package.json index 5255704d..0add1acd 100644 --- a/apps/landing/package.json +++ b/apps/landing/package.json @@ -16,11 +16,11 @@ "@devup-ui/reset-css": "workspace:*", "@mdx-js/loader": "^3.1.0", "@mdx-js/react": "^3.1.0", - "@next/mdx": "^15.3.5", + "@next/mdx": "^15.4.1", "@types/mdx": "^2.0.13", "body-scroll-lock": "3.1.5", "clsx": "^2.1.1", - "next": "^15.3.5", + "next": "^15.4.1", "react": "^19.1.0", "react-dom": "^19.1.0", "react-markdown": "^10.1.0", diff --git a/apps/next/package.json b/apps/next/package.json index e0ed1b52..6eebc07d 100644 --- a/apps/next/package.json +++ b/apps/next/package.json @@ -12,7 +12,7 @@ "dependencies": { "react": "^19.1.0", "react-dom": "^19.1.0", - "next": "^15.3.5", + "next": "^15.4.1", "@devup-ui/react": "workspace:*" }, "devDependencies": { diff --git a/apps/rsbuild/package.json b/apps/rsbuild/package.json index a3eab20d..67b7a708 100644 --- a/apps/rsbuild/package.json +++ b/apps/rsbuild/package.json @@ -15,7 +15,7 @@ "@devup-ui/react": "workspace:*" }, "devDependencies": { - "@rsbuild/core": "^1.4.5", + "@rsbuild/core": "^1.4.7", "@rsbuild/plugin-react": "^1.3.4", "@devup-ui/rsbuild-plugin": "workspace:*" } diff --git a/apps/vite-lib/package.json b/apps/vite-lib/package.json index ec195282..dd155bbd 100644 --- a/apps/vite-lib/package.json +++ b/apps/vite-lib/package.json @@ -14,7 +14,7 @@ "dependencies": { "react": "^19.1.0", "@devup-ui/react": "workspace:*", - "vite": "^7.0.3" + "vite": "^7.0.5" }, "devDependencies": { "vite-plugin-dts": "^4.5.4", diff --git a/apps/vite/package.json b/apps/vite/package.json index e8fac001..0633207f 100644 --- a/apps/vite/package.json +++ b/apps/vite/package.json @@ -17,7 +17,7 @@ }, "devDependencies": { "@devup-ui/vite-plugin": "workspace:*", - "vite": "^7.0.3", + "vite": "^7.0.5", "@vitejs/plugin-react": "^4.6.0", "typescript": "^5", "@types/node": "^24", diff --git a/benchmark/next-chakra-ui/package.json b/benchmark/next-chakra-ui/package.json index 18bbb960..42c24c2d 100644 --- a/benchmark/next-chakra-ui/package.json +++ b/benchmark/next-chakra-ui/package.json @@ -12,7 +12,7 @@ "dependencies": { "@chakra-ui/react": "^3.22.0", "@emotion/react": "^11.14.0", - "next": "^15.3.5", + "next": "^15.4.1", "next-themes": "^0.4.6", "react": "^19.1.0", "react-dom": "^19.1.0", diff --git a/benchmark/next-devup-ui/package.json b/benchmark/next-devup-ui/package.json index aa8e8bff..b28b9c87 100644 --- a/benchmark/next-devup-ui/package.json +++ b/benchmark/next-devup-ui/package.json @@ -12,7 +12,7 @@ "dependencies": { "react": "^19.1.0", "react-dom": "^19.1.0", - "next": "^15.3.5", + "next": "^15.4.1", "@devup-ui/react": "workspace:*" }, "devDependencies": { diff --git a/benchmark/next-kuma-ui/package.json b/benchmark/next-kuma-ui/package.json index 13d8d638..1dde41b3 100644 --- a/benchmark/next-kuma-ui/package.json +++ b/benchmark/next-kuma-ui/package.json @@ -12,7 +12,7 @@ "dependencies": { "react": "^19.1.0", "react-dom": "^19.1.0", - "next": "^15.3.5", + "next": "^15.4.1", "@kuma-ui/core": "^1.5.9" }, "devDependencies": { diff --git a/bindings/devup-ui-wasm/src/lib.rs b/bindings/devup-ui-wasm/src/lib.rs index 2c68c892..8bfde35a 100644 --- a/bindings/devup-ui-wasm/src/lib.rs +++ b/bindings/devup-ui-wasm/src/lib.rs @@ -231,50 +231,12 @@ pub fn get_theme_interface( theme_interface_name: &str, ) -> String { let sheet = GLOBAL_STYLE_SHEET.lock().unwrap(); - let mut color_keys = HashSet::new(); - let mut typography_keys = HashSet::new(); - let mut theme_keys = HashSet::new(); - for color_theme in sheet.theme.colors.values() { - color_theme.0.keys().for_each(|key| { - color_keys.insert(key.clone()); - }); - } - sheet.theme.typography.keys().for_each(|key| { - typography_keys.insert(key.clone()); - }); - - sheet.theme.colors.keys().for_each(|key| { - theme_keys.insert(key.clone()); - }); - - if color_keys.is_empty() && typography_keys.is_empty() { - String::new() - } else { - format!( - "import \"{}\";declare module \"{}\"{{interface {}{{{}}}interface {}{{{}}}interface {}{{{}}}}}", - package_name, - package_name, - color_interface_name, - color_keys - .into_iter() - .map(|key| format!("${key}:null;")) - .collect::>() - .join(""), - typography_interface_name, - typography_keys - .into_iter() - .map(|key| format!("{key}:null;")) - .collect::>() - .join(""), - theme_interface_name, - theme_keys - .into_iter() - // key to pascal - .map(|key| format!("{key}:null;")) - .collect::>() - .join("") - ) - } + sheet.create_interface( + package_name, + color_interface_name, + typography_interface_name, + theme_interface_name, + ) } #[cfg(test)] mod tests { @@ -311,42 +273,6 @@ mod tests { ); } - #[test] - #[serial] - fn test_get_theme_interface() { - { - let mut sheet = GLOBAL_STYLE_SHEET.lock().unwrap(); - *sheet = StyleSheet::default(); - } - assert_eq!( - get_theme_interface( - "package", - "ColorInterface", - "TypographyInterface", - "ThemeInterface" - ), - "" - ); - - { - let mut sheet = GLOBAL_STYLE_SHEET.lock().unwrap(); - let mut theme = Theme::default(); - let mut color_theme = ColorTheme::default(); - color_theme.add_color("primary", "#000"); - theme.add_color_theme("dark", color_theme); - sheet.set_theme(theme); - } - assert_eq!( - get_theme_interface( - "package", - "ColorInterface", - "TypographyInterface", - "ThemeInterface" - ), - "import \"package\";declare module \"package\"{interface ColorInterface{$primary:null;}interface TypographyInterface{}interface ThemeInterface{dark:null;}}" - ); - } - #[test] fn deserialize_theme() { { diff --git a/libs/css/src/constant.rs b/libs/css/src/constant.rs index 4781765f..7a0de2ae 100644 --- a/libs/css/src/constant.rs +++ b/libs/css/src/constant.rs @@ -77,6 +77,10 @@ pub(super) static DOUBLE_SEPARATOR: phf::Set<&str> = phf_set! { }; pub(super) static F_SPACE_RE: Lazy = Lazy::new(|| Regex::new(r"\s*,\s*").unwrap()); +pub(super) static F_DOT_RE: Lazy = Lazy::new(|| Regex::new(r"(\b|,)0\.(\d+)").unwrap()); +pub(super) static DOT_ZERO_RE: Lazy = + Lazy::new(|| Regex::new(r"(\b|,)-?0\.0+([^\d])").unwrap()); + pub(super) static COLOR_HASH: Lazy = Lazy::new(|| Regex::new(r"#([0-9a-zA-Z]+)").unwrap()); pub(super) static ZERO_RE: Lazy = Lazy::new(|| Regex::new(r"(^|\s|\(|,)-?0(px|em|rem|vh|vw|%|dvh|dvw)").unwrap()); diff --git a/libs/css/src/lib.rs b/libs/css/src/lib.rs index c9df267b..61966673 100644 --- a/libs/css/src/lib.rs +++ b/libs/css/src/lib.rs @@ -20,7 +20,16 @@ pub fn merge_selector(class_name: &str, selector: Option<&StyleSelector>) -> Str if let Some(selector) = selector { match selector { StyleSelector::Selector(value) => value.replace("&", &format!(".{class_name}")), - StyleSelector::Media(_) => format!(".{class_name}"), + StyleSelector::Media { + selector: s, + query: _, + } => { + if let Some(s) = s { + s.replace("&", &format!(".{class_name}")) + } else { + format!(".{class_name}") + } + } StyleSelector::Global(v, _) => v.to_string(), } } else { @@ -211,9 +220,14 @@ mod tests { sheet_to_classname("background", 0, Some("rgba(255,0,0,0.5)"), None, None), ); + assert_eq!( + sheet_to_classname("background", 0, Some("rgba(255, 0, 0, 0.5)"), None, None), + sheet_to_classname("background", 0, Some("rgba(255,0,0,.5)"), None, None), + ); + { let map = GLOBAL_CLASS_MAP.lock().unwrap(); - assert_eq!(map.get("background-0-rgba(255,0,0,0.5)--255"), Some(&2)); + assert_eq!(map.get("background-0-rgba(255,0,0,.5)--255"), Some(&2)); } assert_eq!( sheet_to_classname("background", 0, Some("#fff"), None, None), @@ -365,6 +379,16 @@ mod tests { sheet_to_classname("test", 0, Some("0 0vw"), None, None), "d2" ); + + reset_class_map(); + assert_eq!( + sheet_to_classname("transition", 0, Some("all 0.3s ease-in-out"), None, None), + "d0" + ); + assert_eq!( + sheet_to_classname("transition", 0, Some("all .3s ease-in-out"), None, None), + "d0" + ); } #[test] diff --git a/libs/css/src/optimize_value.rs b/libs/css/src/optimize_value.rs index 57358b78..0d863160 100644 --- a/libs/css/src/optimize_value.rs +++ b/libs/css/src/optimize_value.rs @@ -1,4 +1,7 @@ -use crate::{COLOR_HASH, F_SPACE_RE, ZERO_RE}; +use crate::{ + COLOR_HASH, F_SPACE_RE, ZERO_RE, + constant::{DOT_ZERO_RE, F_DOT_RE}, +}; pub fn optimize_value(value: &str) -> String { let mut ret = value.trim().to_string(); @@ -11,6 +14,8 @@ pub fn optimize_value(value: &str) -> String { .to_string(); } if ret.contains("0") { + ret = DOT_ZERO_RE.replace_all(&ret, "${1}0${2}").to_string(); + ret = F_DOT_RE.replace_all(&ret, "${1}.${2}").to_string(); ret = ZERO_RE.replace_all(&ret, "${1}0").to_string(); } // remove ; from dynamic value @@ -57,6 +62,17 @@ mod tests { #[rstest] #[case("0px", "0")] + #[case("0.0px", "0")] + #[case("0.0em", "0")] + #[case("0.0rem", "0")] + #[case("0.0vh", "0")] + #[case("0.0vw", "0")] + #[case("0.0%", "0")] + #[case("0.0dvh", "0")] + #[case("0.0dvw", "0")] + #[case("1.3s", "1.3s")] + #[case("0.3s", ".3s")] + #[case("0.3s ease-in-out", ".3s ease-in-out")] #[case("0em", "0")] #[case("0rem", "0")] #[case("0vh", "0")] @@ -70,9 +86,13 @@ mod tests { #[case("0vh 0vh", "0 0")] #[case("0vw 0vw", "0 0")] #[case("-0vw -0vw", "0 0")] + #[case("-0.2em", "-.2em")] + #[case("-0.02em", "-.02em")] #[case("scale(0px)", "scale(0)")] #[case("scale(-0px)", "scale(0)")] #[case("scale(-0px);", "scale(0)")] + #[case("rgba(255, 0, 0, 0.5)", "rgba(255,0,0,.5)")] + #[case("rgba(0.0,0.0,0.0,0.5)", "rgba(0,0,0,.5)")] #[case("red;", "red")] #[case("translate(0px)", "translate(0)")] #[case("translate(-0px,0px)", "translate(0,0)")] diff --git a/libs/css/src/style_selector.rs b/libs/css/src/style_selector.rs index 77190cc9..596aa097 100644 --- a/libs/css/src/style_selector.rs +++ b/libs/css/src/style_selector.rs @@ -9,7 +9,10 @@ use crate::{constant::SELECTOR_ORDER_MAP, selector_separator::SelectorSeparator, #[derive(Debug, PartialEq, Clone, Hash, Eq, Serialize, Deserialize)] pub enum StyleSelector { - Media(String), + Media { + query: String, + selector: Option, + }, Selector(String), // selector, file Global(String, String), @@ -23,12 +26,36 @@ impl PartialOrd for StyleSelector { impl Ord for StyleSelector { fn cmp(&self, other: &Self) -> Ordering { match (self, other) { - (StyleSelector::Media(a), StyleSelector::Media(b)) => a.cmp(b), + ( + StyleSelector::Media { + query: a, + selector: aa, + }, + StyleSelector::Media { + query: b, + selector: bb, + }, + ) => { + let c = a.cmp(b); + if c == Ordering::Equal { aa.cmp(bb) } else { c } + } (StyleSelector::Selector(a), StyleSelector::Selector(b)) => { get_selector_order(a).cmp(&get_selector_order(b)) } - (StyleSelector::Media(_), StyleSelector::Selector(_)) => Ordering::Greater, - (StyleSelector::Selector(_), StyleSelector::Media(_)) => Ordering::Less, + ( + StyleSelector::Media { + selector: _, + query: _, + }, + StyleSelector::Selector(_), + ) => Ordering::Greater, + ( + StyleSelector::Selector(_), + StyleSelector::Media { + selector: _, + query: _, + }, + ) => Ordering::Less, (StyleSelector::Global(a, _), StyleSelector::Global(b, _)) => { if a == b { return Ordering::Equal; @@ -84,7 +111,10 @@ impl From<&str> for StyleSelector { &s[1..] )) } else if value == "print" { - StyleSelector::Media("print".to_string()) + StyleSelector::Media { + query: "print".to_string(), + selector: None, + } } else { let post = to_kebab_case(value); @@ -134,7 +164,13 @@ impl Display for StyleSelector { "{}", match self { StyleSelector::Selector(value) => value.to_string(), - StyleSelector::Media(value) => format!("@{value}"), + StyleSelector::Media { query, selector } => { + if let Some(selector) = selector { + format!("@{query} {selector}") + } else { + format!("@{query}") + } + } StyleSelector::Global(value, _) => format!("{value}"), } ) @@ -182,7 +218,12 @@ mod tests { #[rstest] #[case(StyleSelector::Selector("&:hover".to_string()), "&:hover")] - #[case(StyleSelector::Media("screen and (max-width: 600px)".to_string()), "@screen and (max-width: 600px)")] + #[case(StyleSelector::Media { + query: "screen and (max-width: 600px)".to_string(), + selector: None, + }, + "@screen and (max-width: 600px)" + )] #[case(StyleSelector::Global(":root[data-theme=dark]".to_string(), "file.rs".to_string()), ":root[data-theme=dark]")] fn test_style_selector_display(#[case] selector: StyleSelector, #[case] expected: &str) { let output = format!("{selector}"); @@ -191,7 +232,10 @@ mod tests { #[rstest] #[case( - StyleSelector::Media("screen".to_string()), + StyleSelector::Media { + query: "screen".to_string(), + selector: None, + }, StyleSelector::Selector("&:hover".to_string()), std::cmp::Ordering::Greater )] @@ -201,8 +245,14 @@ mod tests { std::cmp::Ordering::Less )] #[case( - StyleSelector::Media("a".to_string()), - StyleSelector::Media("b".to_string()), + StyleSelector::Media { + query: "a".to_string(), + selector: None, + }, + StyleSelector::Media { + query: "b".to_string(), + selector: None, + }, std::cmp::Ordering::Less )] #[case( @@ -217,7 +267,10 @@ mod tests { )] #[case( StyleSelector::Selector("&:hover".to_string()), - StyleSelector::Media("screen".to_string()), + StyleSelector::Media { + query: "screen".to_string(), + selector: None, + }, std::cmp::Ordering::Less )] #[case( diff --git a/libs/extractor/Cargo.toml b/libs/extractor/Cargo.toml index a0b6be7b..6719adbc 100644 --- a/libs/extractor/Cargo.toml +++ b/libs/extractor/Cargo.toml @@ -4,13 +4,13 @@ version = "0.1.0" edition = "2024" [dependencies] -oxc_parser = "0.77.0" -oxc_syntax = "0.77.0" -oxc_span = "0.77.0" -oxc_allocator = "0.77.0" -oxc_ast = "0.77.0" -oxc_ast_visit = "0.77.0" -oxc_codegen = "0.77.0" +oxc_parser = "0.77.1" +oxc_syntax = "0.77.1" +oxc_span = "0.77.1" +oxc_allocator = "0.77.1" +oxc_ast = "0.77.1" +oxc_ast_visit = "0.77.1" +oxc_codegen = "0.77.1" css = { path = "../css" } phf = "0.12" strum = "0.27.1" diff --git a/libs/extractor/src/css_utils.rs b/libs/extractor/src/css_utils.rs index bdcca762..24ae4643 100644 --- a/libs/extractor/src/css_utils.rs +++ b/libs/extractor/src/css_utils.rs @@ -12,7 +12,89 @@ use crate::{ pub fn css_to_style<'a>( css: &str, level: u8, - selector: &Option<&StyleSelector>, + selector: &Option, +) -> Vec> { + let mut styles = vec![]; + let mut input = css; + + if input.contains("@media") { + let media_inputs = input + .split("@media") + .flat_map(|s| { + let s = s.trim(); + if s.is_empty() { + return None; + } + Some(format!("@media{}", s)) + }) + .collect::>(); + if media_inputs.len() > 1 { + for media_input in media_inputs { + styles.extend(css_to_style(&media_input, level, selector)); + } + return styles; + } + } + + if input.contains('{') { + while let Some(start) = input.find('{') { + let rest = &input[start + 1..]; + + let end = if selector.is_none() { + rest.rfind('}').unwrap() + } else { + rest.find('}').unwrap() + }; + let block = &rest[..end]; + let sel = &if let Some(StyleSelector::Media { query, .. }) = selector { + let local_sel = input[..start].trim().to_string(); + Some(StyleSelector::Media { + query: query.clone(), + selector: if local_sel == "&" { + None + } else { + Some(local_sel) + }, + }) + } else { + let sel = input[..start].trim().to_string(); + if sel.starts_with("@media") { + Some(StyleSelector::Media { + query: sel.replace(" ", "")["@media".len()..].to_string(), + selector: None, + }) + } else { + Some(StyleSelector::Selector(sel)) + } + }; + let block = if block.contains('{') { + css_to_style(block, level, &sel) + } else { + css_to_style_block(block, level, &sel) + }; + let input_end = input.rfind('}').unwrap() + 1; + + input = &input[start + end + 2..input_end]; + styles.extend(block); + } + } else { + styles.extend(css_to_style_block(input, level, selector)); + } + + styles.sort_by_key(|a| { + if let crate::ExtractStyleProp::Static(crate::ExtractStyleValue::Static(a)) = a { + a.property().to_string() + } else { + "".to_string() + } + }); + styles +} + +fn css_to_style_block<'a>( + css: &str, + level: u8, + selector: &Option, ) -> Vec> { css.split(";") .map(|s| { @@ -24,7 +106,7 @@ pub fn css_to_style<'a>( let property = to_camel_case(iter.next().unwrap()); let value = iter.next().unwrap(); Some(ExtractStyleProp::Static(ExtractStyleValue::Static( - ExtractStaticStyle::new(&property, value, level, selector.cloned()), + ExtractStaticStyle::new(&property, value, level, selector.clone()), ))) } }) @@ -130,42 +212,215 @@ mod tests { #[case( "color: red; background: blue;", vec![ - ("color", "red"), - ("background", "blue"), + ("color", "red", None), + ("background", "blue", None), ] )] #[case( "margin:0;padding:0;", vec![ - ("margin", "0"), - ("padding", "0"), + ("margin", "0", None), + ("padding", "0", None), ] )] #[case( "font-size: 16px;", vec![ - ("fontSize", "16px"), + ("fontSize", "16px", None), ] )] #[case( "border: 1px solid #000; color: #fff;", vec![ - ("border", "1px solid #000"), - ("color", "#FFF"), + ("border", "1px solid #000", None), + ("color", "#FFF", None), ] )] #[case( "", vec![] )] - fn test_css_to_style(#[case] input: &str, #[case] expected: Vec<(&str, &str)>) { + #[case( + "@media (min-width: 768px) { + border: 1px solid #000; + color: #fff; + }", + vec![ + ("border", "1px solid #000", Some(StyleSelector::Media { + query: "(min-width:768px)".to_string(), + selector: None, + })), + ("color", "#FFF", Some(StyleSelector::Media { + query: "(min-width:768px)".to_string(), + selector: None, + })), + ] + )] + #[case( + "@media (min-width: 768px) and (max-width: 1024px) { + border: 1px solid #000; + color: #fff; + } + + @media (min-width: 768px) { + border: 1px solid #000; + color: #fff; + }", + vec![ + ("border", "1px solid #000", Some(StyleSelector::Media { + query: "(min-width:768px)and(max-width:1024px)".to_string(), + selector: None, + })), + ("color", "#FFF", Some(StyleSelector::Media { + query: "(min-width:768px)and(max-width:1024px)".to_string(), + selector: None, + })), + ("border", "1px solid #000", Some(StyleSelector::Media { + query: "(min-width:768px)".to_string(), + selector: None, + })), + ("color", "#FFF", Some(StyleSelector::Media { + query: "(min-width:768px)".to_string(), + selector: None, + })), + ] + )] + #[case( + "@media (min-width: 768px) { + & { + border: 1px solid #fff; + color: #fff; + } + &:hover { + border: 1px solid #000; + color: #000; + } + }", + vec![ + ("border", "1px solid #FFF", Some(StyleSelector::Media { + query: "(min-width:768px)".to_string(), + selector: None, + })), + ("color", "#FFF", Some(StyleSelector::Media { + query: "(min-width:768px)".to_string(), + selector: None, + })), + ("border", "1px solid #000", Some(StyleSelector::Media { + query: "(min-width:768px)".to_string(), + selector: Some("&:hover".to_string()), + })), + ("color", "#000", Some(StyleSelector::Media { + query: "(min-width:768px)".to_string(), + selector: Some("&:hover".to_string()), + })), + ] + )] + #[case( + "@media (min-width: 768px) { + & { + border: 1px solid #fff; + color: #fff; + } + &:hover { + border: 1px solid #000; + color: #000; + } + } + @media (max-width: 768px) and (min-width: 480px) { + & { + border: 1px solid #fff; + color: #fff; + } + &:hover { + border: 1px solid #000; + color: #000; + } + }", + vec![ + ("border", "1px solid #FFF", Some(StyleSelector::Media { + query: "(max-width:768px)and(min-width:480px)".to_string(), + selector: None, + })), + ("color", "#FFF", Some(StyleSelector::Media { + query: "(max-width:768px)and(min-width:480px)".to_string(), + selector: None, + })), + ("border", "1px solid #000", Some(StyleSelector::Media { + query: "(max-width:768px)and(min-width:480px)".to_string(), + selector: Some("&:hover".to_string()), + })), + ("color", "#000", Some(StyleSelector::Media { + query: "(max-width:768px)and(min-width:480px)".to_string(), + selector: Some("&:hover".to_string()), + })), + ("border", "1px solid #FFF", Some(StyleSelector::Media { + query: "(min-width:768px)".to_string(), + selector: None, + })), + ("color", "#FFF", Some(StyleSelector::Media { + query: "(min-width:768px)".to_string(), + selector: None, + })), + ("border", "1px solid #000", Some(StyleSelector::Media { + query: "(min-width:768px)".to_string(), + selector: Some("&:hover".to_string()), + })), + ("color", "#000", Some(StyleSelector::Media { + query: "(min-width:768px)".to_string(), + selector: Some("&:hover".to_string()), + })), + ] + )] + #[case( + "@media (min-width: 768px) { + & { + border: 1px solid #fff; + color: #fff; + } + } + @media (max-width: 768px) and (min-width: 480px) { + border: 1px solid #000; + color: #000; + }", + vec![ + ("border", "1px solid #FFF", Some(StyleSelector::Media { + query: "(min-width:768px)".to_string(), + selector: None, + })), + ("color", "#FFF", Some(StyleSelector::Media { + query: "(min-width:768px)".to_string(), + selector: None, + })), + ("border", "1px solid #000", Some(StyleSelector::Media { + query: "(max-width:768px)and(min-width:480px)".to_string(), + selector: None, + })), + ("color", "#000", Some(StyleSelector::Media { + query: "(max-width:768px)and(min-width:480px)".to_string(), + selector: None, + })), + ] + )] + #[case( + "@media (min-width: 768px) { + & { + } + } + @media (max-width: 768px) and (min-width: 480px) { + }", + vec![] + )] + fn test_css_to_style( + #[case] input: &str, + #[case] expected: Vec<(&str, &str, Option)>, + ) { let styles = css_to_style(input, 0, &None); - let mut result: Vec<(&str, &str)> = styles + let mut result: Vec<(&str, &str, Option)> = styles .iter() .filter_map(|prop| { if let crate::ExtractStyleProp::Static(crate::ExtractStyleValue::Static(st)) = prop { - Some((st.property(), st.value())) + Some((st.property(), st.value(), st.selector().cloned())) } else { None } diff --git a/libs/extractor/src/extractor/extract_global_style_from_expression.rs b/libs/extractor/src/extractor/extract_global_style_from_expression.rs index 73dad72e..31e924f2 100644 --- a/libs/extractor/src/extractor/extract_global_style_from_expression.rs +++ b/libs/extractor/src/extractor/extract_global_style_from_expression.rs @@ -66,7 +66,7 @@ pub fn extract_global_style_from_expression<'a>( None, &mut o.value, 0, - Some(&StyleSelector::Global(name.clone(), file.to_string())), + &Some(StyleSelector::Global(name.clone(), file.to_string())), ) .styles, ); diff --git a/libs/extractor/src/extractor/extract_keyframes_from_expression.rs b/libs/extractor/src/extractor/extract_keyframes_from_expression.rs index 6bc44157..5d20ac82 100644 --- a/libs/extractor/src/extractor/extract_keyframes_from_expression.rs +++ b/libs/extractor/src/extractor/extract_keyframes_from_expression.rs @@ -39,7 +39,7 @@ pub fn extract_keyframes_from_expression<'a>( name = format!("{num}%"); } let mut styles = - extract_style_from_expression(ast_builder, None, &mut o.value, 0, None) + extract_style_from_expression(ast_builder, None, &mut o.value, 0, &None) .styles .into_iter() .filter_map(|s| match s { diff --git a/libs/extractor/src/extractor/extract_style_from_expression.rs b/libs/extractor/src/extractor/extract_style_from_expression.rs index ae358630..cc3faa13 100644 --- a/libs/extractor/src/extractor/extract_style_from_expression.rs +++ b/libs/extractor/src/extractor/extract_style_from_expression.rs @@ -31,7 +31,7 @@ pub fn extract_style_from_expression<'a>( name: Option<&str>, expression: &mut Expression<'a>, level: u8, - selector: Option<&StyleSelector>, + selector: &Option, ) -> ExtractResult<'a> { let mut typo = false; @@ -67,7 +67,7 @@ pub fn extract_style_from_expression<'a>( Some(name), &mut prop.value, 0, - None, + &None, ); props_styles.extend(styles); tag = _tag.or(tag); @@ -84,7 +84,7 @@ pub fn extract_style_from_expression<'a>( None, &mut prop.argument, 0, - None, + &None, ); props_styles.extend(styles); tag = _tag.or(tag); @@ -110,7 +110,7 @@ pub fn extract_style_from_expression<'a>( None, &mut conditional.consequent, level, - None, + &None, ) .styles, ))), @@ -134,7 +134,7 @@ pub fn extract_style_from_expression<'a>( None, &mut parenthesized.expression, level, - None, + &None, ), Expression::TemplateLiteral(tmp) => ExtractResult { styles: css_to_style( @@ -176,8 +176,8 @@ pub fn extract_style_from_expression<'a>( None, &mut o.value, level, - Some( - &if let Some(selector) = selector { + &Some( + if let Some(selector) = selector { name.replace("&", &selector.to_string()) } else { name @@ -202,7 +202,7 @@ pub fn extract_style_from_expression<'a>( None, expression, level, - Some(&if let Some(selector) = selector { + &Some(if let Some(selector) = selector { (selector, new_selector).into() } else { new_selector.into() @@ -221,7 +221,7 @@ pub fn extract_style_from_expression<'a>( name, &value, level, - selector.cloned(), + selector.clone(), )) })], ..ExtractResult::default() @@ -243,7 +243,7 @@ pub fn extract_style_from_expression<'a>( name.unwrap(), level, &expression_to_code(expression), - selector.cloned(), + selector.clone(), ), ))], ..ExtractResult::default() @@ -269,7 +269,7 @@ pub fn extract_style_from_expression<'a>( name, &tmp.quasis[0].value.raw, level, - selector.cloned(), + selector.clone(), )) })], ..ExtractResult::default() @@ -312,7 +312,7 @@ pub fn extract_style_from_expression<'a>( name, level, &expression_to_code(expression), - selector.cloned(), + selector.clone(), ), ))], ..ExtractResult::default() @@ -370,7 +370,7 @@ pub fn extract_style_from_expression<'a>( name, level, &identifier.name, - selector.cloned(), + selector.clone(), ), ))], ..ExtractResult::default() diff --git a/libs/extractor/src/extractor/extract_style_from_jsx.rs b/libs/extractor/src/extractor/extract_style_from_jsx.rs index 6e385fbc..83e09ccc 100644 --- a/libs/extractor/src/extractor/extract_style_from_jsx.rs +++ b/libs/extractor/src/extractor/extract_style_from_jsx.rs @@ -20,7 +20,7 @@ pub fn extract_style_from_jsx<'a>( Some(name), expression.expression.to_expression_mut(), 0, - None, + &None, ) } else { ExtractResult::default() @@ -31,7 +31,7 @@ pub fn extract_style_from_jsx<'a>( Some(name), &mut Expression::StringLiteral(literal.clone_in(ast_builder.allocator)), 0, - None, + &None, ), _ => ExtractResult::default(), } diff --git a/libs/extractor/src/extractor/extract_style_from_member_expression.rs b/libs/extractor/src/extractor/extract_style_from_member_expression.rs index 5e872c58..30039c03 100644 --- a/libs/extractor/src/extractor/extract_style_from_member_expression.rs +++ b/libs/extractor/src/extractor/extract_style_from_member_expression.rs @@ -25,7 +25,7 @@ pub(super) fn extract_style_from_member_expression<'a>( name: Option<&str>, mem: &mut ComputedMemberExpression<'a>, level: u8, - selector: Option<&StyleSelector>, + selector: &Option, ) -> ExtractResult<'a> { let mem_expression = &mem.expression.clone_in(ast_builder.allocator); let mut ret: Vec = vec![]; @@ -71,7 +71,7 @@ pub(super) fn extract_style_from_member_expression<'a>( false, ), )), - selector.cloned(), + selector.clone(), ), ))] }) @@ -99,7 +99,7 @@ pub(super) fn extract_style_from_member_expression<'a>( false, ), )), - selector.cloned(), + selector.clone(), ), ))), ); @@ -171,7 +171,7 @@ pub(super) fn extract_style_from_member_expression<'a>( false, ), )), - selector.cloned(), + selector.clone(), ), ))), } @@ -217,7 +217,7 @@ pub(super) fn extract_style_from_member_expression<'a>( false, ), )), - selector.cloned(), + selector.clone(), ), ))) } diff --git a/libs/extractor/src/lib.rs b/libs/extractor/src/lib.rs index 0a1e1fab..46a02df4 100644 --- a/libs/extractor/src/lib.rs +++ b/libs/extractor/src/lib.rs @@ -2114,6 +2114,120 @@ import clsx from 'clsx' color: blue; `] })}/>; +"#, + ExtractOption { + package: "@devup-ui/core".to_string(), + css_file: None + } + ) + .unwrap() + )); + + reset_class_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import { css } from "@devup-ui/core"; +; +"#, + ExtractOption { + package: "@devup-ui/core".to_string(), + css_file: None + } + ) + .unwrap() + )); + + reset_class_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import { css } from "@devup-ui/core"; +; +"#, + ExtractOption { + package: "@devup-ui/core".to_string(), + css_file: None + } + ) + .unwrap() + )); + + reset_class_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import { css } from "@devup-ui/core"; +; +"#, + ExtractOption { + package: "@devup-ui/core".to_string(), + css_file: None + } + ) + .unwrap() + )); + } + + #[test] + #[serial] + fn extract_static_css_with_media_query() { + reset_class_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import { css } from "@devup-ui/core"; +; +"#, + ExtractOption { + package: "@devup-ui/core".to_string(), + css_file: None + } + ) + .unwrap() + )); + + reset_class_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import { css } from "@devup-ui/core"; +; +"#, + ExtractOption { + package: "@devup-ui/core".to_string(), + css_file: None + } + ) + .unwrap() + )); + + reset_class_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import { css } from "@devup-ui/core"; +; "#, ExtractOption { package: "@devup-ui/core".to_string(), @@ -4401,6 +4515,58 @@ globalCss({ .unwrap() )); + reset_class_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import { globalCss } from "@devup-ui/core"; + globalCss({ + "div": `` + }) + "#, + ExtractOption { + package: "@devup-ui/core".to_string(), + css_file: None + } + ) + .unwrap() + )); + + reset_class_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import { globalCss } from "@devup-ui/core"; + globalCss({ + "div": ` ` + }) + "#, + ExtractOption { + package: "@devup-ui/core".to_string(), + css_file: None + } + ) + .unwrap() + )); + + reset_class_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import { globalCss } from "@devup-ui/core"; + globalCss({ + "div": ` + ` + }) + "#, + ExtractOption { + package: "@devup-ui/core".to_string(), + css_file: None + } + ) + .unwrap() + )); + reset_class_map(); assert_debug_snapshot!(ToBTreeSet::from( extract( @@ -4412,6 +4578,52 @@ globalCss({ color: blue; } ` + "#, + ExtractOption { + package: "@devup-ui/core".to_string(), + css_file: None + } + ) + .unwrap() + )); + + reset_class_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import { globalCss } from "@devup-ui/core"; + globalCss`` + "#, + ExtractOption { + package: "@devup-ui/core".to_string(), + css_file: None + } + ) + .unwrap() + )); + + reset_class_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import { globalCss } from "@devup-ui/core"; + globalCss` ` + "#, + ExtractOption { + package: "@devup-ui/core".to_string(), + css_file: None + } + ) + .unwrap() + )); + + reset_class_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import { globalCss } from "@devup-ui/core"; + globalCss` + ` "#, ExtractOption { package: "@devup-ui/core".to_string(), diff --git a/libs/extractor/src/snapshots/extractor__tests__custom_selector-2.snap b/libs/extractor/src/snapshots/extractor__tests__custom_selector-2.snap index eb196c12..babc7d03 100644 --- a/libs/extractor/src/snapshots/extractor__tests__custom_selector-2.snap +++ b/libs/extractor/src/snapshots/extractor__tests__custom_selector-2.snap @@ -7,7 +7,7 @@ ToBTreeSet { Static( ExtractStaticStyle { property: "opacity", - value: "0.5", + value: ".5", level: 0, selector: Some( Selector( diff --git a/libs/extractor/src/snapshots/extractor__tests__custom_selector-3.snap b/libs/extractor/src/snapshots/extractor__tests__custom_selector-3.snap index 71a0dadc..55609e63 100644 --- a/libs/extractor/src/snapshots/extractor__tests__custom_selector-3.snap +++ b/libs/extractor/src/snapshots/extractor__tests__custom_selector-3.snap @@ -7,7 +7,7 @@ ToBTreeSet { Static( ExtractStaticStyle { property: "opacity", - value: "0.5", + value: ".5", level: 0, selector: Some( Selector( diff --git a/libs/extractor/src/snapshots/extractor__tests__custom_selector.snap b/libs/extractor/src/snapshots/extractor__tests__custom_selector.snap index 866b0352..4c3211f1 100644 --- a/libs/extractor/src/snapshots/extractor__tests__custom_selector.snap +++ b/libs/extractor/src/snapshots/extractor__tests__custom_selector.snap @@ -7,7 +7,7 @@ ToBTreeSet { Static( ExtractStaticStyle { property: "opacity", - value: "0.5", + value: ".5", level: 0, selector: Some( Selector( diff --git a/libs/extractor/src/snapshots/extractor__tests__extract_conditional_style_props_with_class_name-2.snap b/libs/extractor/src/snapshots/extractor__tests__extract_conditional_style_props_with_class_name-2.snap index 4cdab5ba..caf3c438 100644 --- a/libs/extractor/src/snapshots/extractor__tests__extract_conditional_style_props_with_class_name-2.snap +++ b/libs/extractor/src/snapshots/extractor__tests__extract_conditional_style_props_with_class_name-2.snap @@ -18,7 +18,7 @@ ToBTreeSet { Static( ExtractStaticStyle { property: "opacity", - value: "0.5", + value: ".5", level: 0, selector: None, style_order: None, diff --git a/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_template_literal-2.snap b/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_template_literal-2.snap index a05c5c3d..bfc6bdeb 100644 --- a/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_template_literal-2.snap +++ b/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_template_literal-2.snap @@ -1,15 +1,8 @@ --- source: libs/extractor/src/lib.rs -expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import { globalCss } from \"@devup-ui/core\";\n globalCss`\n div {\n background-color: red;\n color: blue;\n }\n `\n \"#,\nExtractOption\n{ package: \"@devup-ui/core\".to_string(), css_file: None }).unwrap())" +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import { globalCss } from \"@devup-ui/core\";\n globalCss({\n \"div\": ``\n })\n \"#,\nExtractOption\n{ package: \"@devup-ui/core\".to_string(), css_file: None }).unwrap())" --- ToBTreeSet { - styles: { - Css( - ExtractCss { - css: "div{background-color:red;color:blue}", - file: "test.tsx", - }, - ), - }, - code: "import \"@devup-ui/core/devup-ui.css\";\n;\n", + styles: {}, + code: ";\n", } diff --git a/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_template_literal-3.snap b/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_template_literal-3.snap new file mode 100644 index 00000000..6ab3a887 --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_template_literal-3.snap @@ -0,0 +1,8 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import { globalCss } from \"@devup-ui/core\";\n globalCss({\n \"div\": ` `\n })\n \"#,\nExtractOption\n{ package: \"@devup-ui/core\".to_string(), css_file: None }).unwrap())" +--- +ToBTreeSet { + styles: {}, + code: ";\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_template_literal-4.snap b/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_template_literal-4.snap new file mode 100644 index 00000000..6324acba --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_template_literal-4.snap @@ -0,0 +1,8 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import { globalCss } from \"@devup-ui/core\";\n globalCss({\n \"div\": ` \n `\n })\n \"#,\nExtractOption\n{ package: \"@devup-ui/core\".to_string(), css_file: None }).unwrap())" +--- +ToBTreeSet { + styles: {}, + code: ";\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_template_literal-5.snap b/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_template_literal-5.snap new file mode 100644 index 00000000..a05c5c3d --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_template_literal-5.snap @@ -0,0 +1,15 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import { globalCss } from \"@devup-ui/core\";\n globalCss`\n div {\n background-color: red;\n color: blue;\n }\n `\n \"#,\nExtractOption\n{ package: \"@devup-ui/core\".to_string(), css_file: None }).unwrap())" +--- +ToBTreeSet { + styles: { + Css( + ExtractCss { + css: "div{background-color:red;color:blue}", + file: "test.tsx", + }, + ), + }, + code: "import \"@devup-ui/core/devup-ui.css\";\n;\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_template_literal-6.snap b/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_template_literal-6.snap new file mode 100644 index 00000000..246f18bd --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_template_literal-6.snap @@ -0,0 +1,8 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import { globalCss } from \"@devup-ui/core\";\n globalCss``\n \"#,\nExtractOption\n{ package: \"@devup-ui/core\".to_string(), css_file: None }).unwrap())" +--- +ToBTreeSet { + styles: {}, + code: ";\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_template_literal-7.snap b/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_template_literal-7.snap new file mode 100644 index 00000000..0e3568c0 --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_template_literal-7.snap @@ -0,0 +1,8 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import { globalCss } from \"@devup-ui/core\";\n globalCss` `\n \"#,\nExtractOption\n{ package: \"@devup-ui/core\".to_string(), css_file: None }).unwrap())" +--- +ToBTreeSet { + styles: {}, + code: ";\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_template_literal-8.snap b/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_template_literal-8.snap new file mode 100644 index 00000000..d8eaafeb --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_template_literal-8.snap @@ -0,0 +1,8 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import { globalCss } from \"@devup-ui/core\";\n globalCss` \n `\n \"#,\nExtractOption\n{ package: \"@devup-ui/core\".to_string(), css_file: None }).unwrap())" +--- +ToBTreeSet { + styles: {}, + code: ";\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__extract_keyframs-2.snap b/libs/extractor/src/snapshots/extractor__tests__extract_keyframs-2.snap index e90ec901..bb7c4755 100644 --- a/libs/extractor/src/snapshots/extractor__tests__extract_keyframs-2.snap +++ b/libs/extractor/src/snapshots/extractor__tests__extract_keyframs-2.snap @@ -28,7 +28,7 @@ ToBTreeSet { "50%": [ ExtractStaticStyle { property: "opacity", - value: "0.5", + value: ".5", level: 0, selector: None, style_order: None, diff --git a/libs/extractor/src/snapshots/extractor__tests__extract_keyframs-3.snap b/libs/extractor/src/snapshots/extractor__tests__extract_keyframs-3.snap index cb339efc..2c2536ce 100644 --- a/libs/extractor/src/snapshots/extractor__tests__extract_keyframs-3.snap +++ b/libs/extractor/src/snapshots/extractor__tests__extract_keyframs-3.snap @@ -28,7 +28,7 @@ ToBTreeSet { "50%": [ ExtractStaticStyle { property: "opacity", - value: "0.5", + value: ".5", level: 0, selector: None, style_order: None, diff --git a/libs/extractor/src/snapshots/extractor__tests__extract_keyframs-4.snap b/libs/extractor/src/snapshots/extractor__tests__extract_keyframs-4.snap index 3fc937c6..8e92ff23 100644 --- a/libs/extractor/src/snapshots/extractor__tests__extract_keyframs-4.snap +++ b/libs/extractor/src/snapshots/extractor__tests__extract_keyframs-4.snap @@ -28,7 +28,7 @@ ToBTreeSet { "50%": [ ExtractStaticStyle { property: "opacity", - value: "0.5", + value: ".5", level: 0, selector: None, style_order: None, diff --git a/libs/extractor/src/snapshots/extractor__tests__extract_keyframs-5.snap b/libs/extractor/src/snapshots/extractor__tests__extract_keyframs-5.snap index 84e3a4ab..59758203 100644 --- a/libs/extractor/src/snapshots/extractor__tests__extract_keyframs-5.snap +++ b/libs/extractor/src/snapshots/extractor__tests__extract_keyframs-5.snap @@ -28,7 +28,7 @@ ToBTreeSet { "50%": [ ExtractStaticStyle { property: "opacity", - value: "0.5", + value: ".5", level: 0, selector: None, style_order: None, diff --git a/libs/extractor/src/snapshots/extractor__tests__extract_keyframs-6.snap b/libs/extractor/src/snapshots/extractor__tests__extract_keyframs-6.snap index 950f909c..94129fb1 100644 --- a/libs/extractor/src/snapshots/extractor__tests__extract_keyframs-6.snap +++ b/libs/extractor/src/snapshots/extractor__tests__extract_keyframs-6.snap @@ -28,7 +28,7 @@ ToBTreeSet { "50%": [ ExtractStaticStyle { property: "opacity", - value: "0.5", + value: ".5", level: 0, selector: None, style_order: None, diff --git a/libs/extractor/src/snapshots/extractor__tests__extract_keyframs-7.snap b/libs/extractor/src/snapshots/extractor__tests__extract_keyframs-7.snap index 182bdc6b..ef8c4a9a 100644 --- a/libs/extractor/src/snapshots/extractor__tests__extract_keyframs-7.snap +++ b/libs/extractor/src/snapshots/extractor__tests__extract_keyframs-7.snap @@ -28,7 +28,7 @@ ToBTreeSet { "50%": [ ExtractStaticStyle { property: "opacity", - value: "0.5", + value: ".5", level: 0, selector: None, style_order: None, diff --git a/libs/extractor/src/snapshots/extractor__tests__extract_keyframs-8.snap b/libs/extractor/src/snapshots/extractor__tests__extract_keyframs-8.snap index bfc4d10c..06c4f541 100644 --- a/libs/extractor/src/snapshots/extractor__tests__extract_keyframs-8.snap +++ b/libs/extractor/src/snapshots/extractor__tests__extract_keyframs-8.snap @@ -28,7 +28,7 @@ ToBTreeSet { "50%": [ ExtractStaticStyle { property: "opacity", - value: "0.5", + value: ".5", level: 0, selector: None, style_order: None, diff --git a/libs/extractor/src/snapshots/extractor__tests__extract_keyframs-9.snap b/libs/extractor/src/snapshots/extractor__tests__extract_keyframs-9.snap index 1af56979..661aaa02 100644 --- a/libs/extractor/src/snapshots/extractor__tests__extract_keyframs-9.snap +++ b/libs/extractor/src/snapshots/extractor__tests__extract_keyframs-9.snap @@ -28,7 +28,7 @@ ToBTreeSet { "50%": [ ExtractStaticStyle { property: "opacity", - value: "0.5", + value: ".5", level: 0, selector: None, style_order: None, @@ -61,7 +61,7 @@ ToBTreeSet { "50%": [ ExtractStaticStyle { property: "opacity", - value: "0.5", + value: ".5", level: 0, selector: None, style_order: None, @@ -94,7 +94,7 @@ ToBTreeSet { "50%": [ ExtractStaticStyle { property: "opacity", - value: "0.5", + value: ".5", level: 0, selector: None, style_order: None, diff --git a/libs/extractor/src/snapshots/extractor__tests__extract_selector-2.snap b/libs/extractor/src/snapshots/extractor__tests__extract_selector-2.snap index 46f45342..5bdd4cd1 100644 --- a/libs/extractor/src/snapshots/extractor__tests__extract_selector-2.snap +++ b/libs/extractor/src/snapshots/extractor__tests__extract_selector-2.snap @@ -18,7 +18,7 @@ ToBTreeSet { Static( ExtractStaticStyle { property: "boxShadow", - value: "0 0 15px 0 rgba(0,0,0,0.25)", + value: "0 0 15px 0 rgba(0,0,0,.25)", level: 2, selector: Some( Selector( @@ -31,7 +31,7 @@ ToBTreeSet { Static( ExtractStaticStyle { property: "boxShadow", - value: "0 1px 3px 0 rgba(0,0,0,0.25)", + value: "0 1px 3px 0 rgba(0,0,0,.25)", level: 0, selector: Some( Selector( @@ -79,7 +79,7 @@ ToBTreeSet { Static( ExtractStaticStyle { property: "transform", - value: "scale(0.95)", + value: "scale(.95)", level: 0, selector: Some( Selector( diff --git a/libs/extractor/src/snapshots/extractor__tests__extract_static_css_class_name_props-11.snap b/libs/extractor/src/snapshots/extractor__tests__extract_static_css_class_name_props-11.snap new file mode 100644 index 00000000..cc677315 --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__extract_static_css_class_name_props-11.snap @@ -0,0 +1,8 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import { css } from \"@devup-ui/core\";\n;\n\"#,\nExtractOption\n{ package: \"@devup-ui/core\".to_string(), css_file: None }).unwrap())" +--- +ToBTreeSet { + styles: {}, + code: ";\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__extract_static_css_class_name_props-12.snap b/libs/extractor/src/snapshots/extractor__tests__extract_static_css_class_name_props-12.snap new file mode 100644 index 00000000..be2680e2 --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__extract_static_css_class_name_props-12.snap @@ -0,0 +1,8 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import { css } from \"@devup-ui/core\";\n;\n\"#,\nExtractOption\n{ package: \"@devup-ui/core\".to_string(), css_file: None }).unwrap())" +--- +ToBTreeSet { + styles: {}, + code: ";\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__extract_static_css_class_name_props-13.snap b/libs/extractor/src/snapshots/extractor__tests__extract_static_css_class_name_props-13.snap new file mode 100644 index 00000000..a54a01e7 --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__extract_static_css_class_name_props-13.snap @@ -0,0 +1,8 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import { css } from \"@devup-ui/core\";\n;\n\"#,\nExtractOption\n{ package: \"@devup-ui/core\".to_string(), css_file: None }).unwrap())" +--- +ToBTreeSet { + styles: {}, + code: ";\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__extract_static_css_with_media_query-2.snap b/libs/extractor/src/snapshots/extractor__tests__extract_static_css_with_media_query-2.snap new file mode 100644 index 00000000..fb7a0fdd --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__extract_static_css_with_media_query-2.snap @@ -0,0 +1,41 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import { css } from \"@devup-ui/core\";\n;\n\"#,\nExtractOption\n{ package: \"@devup-ui/core\".to_string(), css_file: None }).unwrap())" +--- +ToBTreeSet { + styles: { + Static( + ExtractStaticStyle { + property: "backgroundColor", + value: "blue", + level: 0, + selector: Some( + Media { + query: "(min-width:768px)", + selector: Some( + "&:active", + ), + }, + ), + style_order: None, + }, + ), + Static( + ExtractStaticStyle { + property: "backgroundColor", + value: "red", + level: 0, + selector: Some( + Media { + query: "(min-width:768px)", + selector: Some( + "&:hover", + ), + }, + ), + style_order: None, + }, + ), + }, + code: "import \"@devup-ui/core/devup-ui.css\";\n;\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__extract_static_css_with_media_query-3.snap b/libs/extractor/src/snapshots/extractor__tests__extract_static_css_with_media_query-3.snap new file mode 100644 index 00000000..9c8b013e --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__extract_static_css_with_media_query-3.snap @@ -0,0 +1,23 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import { css } from \"@devup-ui/core\";\n;\n\"#,\nExtractOption\n{ package: \"@devup-ui/core\".to_string(), css_file: None }).unwrap())" +--- +ToBTreeSet { + styles: { + Static( + ExtractStaticStyle { + property: "backgroundColor", + value: "red", + level: 0, + selector: Some( + Media { + query: "(min-width:768px)", + selector: None, + }, + ), + style_order: None, + }, + ), + }, + code: "import \"@devup-ui/core/devup-ui.css\";\n;\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__extract_static_css_with_media_query.snap b/libs/extractor/src/snapshots/extractor__tests__extract_static_css_with_media_query.snap new file mode 100644 index 00000000..82a55616 --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__extract_static_css_with_media_query.snap @@ -0,0 +1,23 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import { css } from \"@devup-ui/core\";\n;\n\"#,\nExtractOption\n{ package: \"@devup-ui/core\".to_string(), css_file: None }).unwrap())" +--- +ToBTreeSet { + styles: { + Static( + ExtractStaticStyle { + property: "backgroundColor", + value: "red", + level: 0, + selector: Some( + Media { + query: "(min-width:768px)", + selector: None, + }, + ), + style_order: None, + }, + ), + }, + code: "import \"@devup-ui/core/devup-ui.css\";\n;\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__props_direct_array_select-2.snap b/libs/extractor/src/snapshots/extractor__tests__props_direct_array_select-2.snap index 7db32365..6d0abce0 100644 --- a/libs/extractor/src/snapshots/extractor__tests__props_direct_array_select-2.snap +++ b/libs/extractor/src/snapshots/extractor__tests__props_direct_array_select-2.snap @@ -18,7 +18,7 @@ ToBTreeSet { Static( ExtractStaticStyle { property: "opacity", - value: "0.5", + value: ".5", level: 0, selector: None, style_order: None, diff --git a/libs/extractor/src/snapshots/extractor__tests__props_direct_array_select-7.snap b/libs/extractor/src/snapshots/extractor__tests__props_direct_array_select-7.snap index 15353be8..88a0df77 100644 --- a/libs/extractor/src/snapshots/extractor__tests__props_direct_array_select-7.snap +++ b/libs/extractor/src/snapshots/extractor__tests__props_direct_array_select-7.snap @@ -18,7 +18,7 @@ ToBTreeSet { Static( ExtractStaticStyle { property: "opacity", - value: "0.5", + value: ".5", level: 0, selector: None, style_order: None, diff --git a/libs/extractor/src/snapshots/extractor__tests__props_direct_object_select-2.snap b/libs/extractor/src/snapshots/extractor__tests__props_direct_object_select-2.snap index 260a1b2e..900229d9 100644 --- a/libs/extractor/src/snapshots/extractor__tests__props_direct_object_select-2.snap +++ b/libs/extractor/src/snapshots/extractor__tests__props_direct_object_select-2.snap @@ -18,7 +18,7 @@ ToBTreeSet { Static( ExtractStaticStyle { property: "opacity", - value: "0.5", + value: ".5", level: 0, selector: None, style_order: None, diff --git a/libs/extractor/src/snapshots/extractor__tests__props_direct_object_select-3.snap b/libs/extractor/src/snapshots/extractor__tests__props_direct_object_select-3.snap index de58979c..daab7a59 100644 --- a/libs/extractor/src/snapshots/extractor__tests__props_direct_object_select-3.snap +++ b/libs/extractor/src/snapshots/extractor__tests__props_direct_object_select-3.snap @@ -18,7 +18,7 @@ ToBTreeSet { Static( ExtractStaticStyle { property: "opacity", - value: "0.5", + value: ".5", level: 0, selector: None, style_order: None, diff --git a/libs/extractor/src/snapshots/extractor__tests__props_direct_variable_object_select.snap b/libs/extractor/src/snapshots/extractor__tests__props_direct_variable_object_select.snap index 6006f9fb..d6050df6 100644 --- a/libs/extractor/src/snapshots/extractor__tests__props_direct_variable_object_select.snap +++ b/libs/extractor/src/snapshots/extractor__tests__props_direct_variable_object_select.snap @@ -18,7 +18,7 @@ ToBTreeSet { Static( ExtractStaticStyle { property: "opacity", - value: "0.5", + value: ".5", level: 0, selector: None, style_order: None, diff --git a/libs/extractor/src/snapshots/extractor__tests__rest_props-2.snap b/libs/extractor/src/snapshots/extractor__tests__rest_props-2.snap index 46512836..2adec258 100644 --- a/libs/extractor/src/snapshots/extractor__tests__rest_props-2.snap +++ b/libs/extractor/src/snapshots/extractor__tests__rest_props-2.snap @@ -42,7 +42,7 @@ ToBTreeSet { Static( ExtractStaticStyle { property: "transform", - value: "scale(0.95)", + value: "scale(.95)", level: 0, selector: Some( Selector( diff --git a/libs/extractor/src/snapshots/extractor__tests__rest_props.snap b/libs/extractor/src/snapshots/extractor__tests__rest_props.snap index 14f871bf..b9b0c74b 100644 --- a/libs/extractor/src/snapshots/extractor__tests__rest_props.snap +++ b/libs/extractor/src/snapshots/extractor__tests__rest_props.snap @@ -18,7 +18,7 @@ ToBTreeSet { Static( ExtractStaticStyle { property: "opacity", - value: "0.5", + value: ".5", level: 0, selector: None, style_order: None, diff --git a/libs/extractor/src/snapshots/extractor__tests__style_order-6.snap b/libs/extractor/src/snapshots/extractor__tests__style_order-6.snap index cdc5b156..eba42b50 100644 --- a/libs/extractor/src/snapshots/extractor__tests__style_order-6.snap +++ b/libs/extractor/src/snapshots/extractor__tests__style_order-6.snap @@ -48,7 +48,7 @@ ToBTreeSet { Static( ExtractStaticStyle { property: "opacity", - value: "0.5", + value: ".5", level: 0, selector: Some( Selector( diff --git a/libs/extractor/src/snapshots/extractor__tests__style_order.snap b/libs/extractor/src/snapshots/extractor__tests__style_order.snap index 1e4ac7e5..89210a7e 100644 --- a/libs/extractor/src/snapshots/extractor__tests__style_order.snap +++ b/libs/extractor/src/snapshots/extractor__tests__style_order.snap @@ -37,7 +37,7 @@ ToBTreeSet { Static( ExtractStaticStyle { property: "opacity", - value: "0.5", + value: ".5", level: 0, selector: Some( Selector( diff --git a/libs/extractor/src/snapshots/extractor__tests__style_order2-2.snap b/libs/extractor/src/snapshots/extractor__tests__style_order2-2.snap index 9ad954bc..122e0630 100644 --- a/libs/extractor/src/snapshots/extractor__tests__style_order2-2.snap +++ b/libs/extractor/src/snapshots/extractor__tests__style_order2-2.snap @@ -48,7 +48,7 @@ ToBTreeSet { Static( ExtractStaticStyle { property: "opacity", - value: "0.5", + value: ".5", level: 0, selector: Some( Selector( diff --git a/libs/extractor/src/snapshots/extractor__tests__style_order2-3.snap b/libs/extractor/src/snapshots/extractor__tests__style_order2-3.snap index 986fcf89..388836d9 100644 --- a/libs/extractor/src/snapshots/extractor__tests__style_order2-3.snap +++ b/libs/extractor/src/snapshots/extractor__tests__style_order2-3.snap @@ -48,7 +48,7 @@ ToBTreeSet { Static( ExtractStaticStyle { property: "opacity", - value: "0.5", + value: ".5", level: 0, selector: Some( Selector( diff --git a/libs/extractor/src/snapshots/extractor__tests__style_order2.snap b/libs/extractor/src/snapshots/extractor__tests__style_order2.snap index 832dfdd0..efbc1c33 100644 --- a/libs/extractor/src/snapshots/extractor__tests__style_order2.snap +++ b/libs/extractor/src/snapshots/extractor__tests__style_order2.snap @@ -48,7 +48,7 @@ ToBTreeSet { Static( ExtractStaticStyle { property: "opacity", - value: "0.5", + value: ".5", level: 0, selector: Some( Selector( diff --git a/libs/extractor/src/visit.rs b/libs/extractor/src/visit.rs index 265be584..8d5a22df 100644 --- a/libs/extractor/src/visit.rs +++ b/libs/extractor/src/visit.rs @@ -155,7 +155,7 @@ impl<'a> VisitMut<'a> for DevupVisitor<'a> { None, call.arguments[0].to_expression_mut(), 0, - None, + &None, ); if styles.is_empty() { @@ -239,6 +239,10 @@ impl<'a> VisitMut<'a> for DevupVisitor<'a> { if let Some(cls) = class_name { *it = cls; + } else { + *it = self + .ast + .expression_string_literal(SPAN, self.ast.atom(""), None); } self.styles.extend( styles @@ -278,13 +282,15 @@ impl<'a> VisitMut<'a> for DevupVisitor<'a> { .expression_string_literal(SPAN, self.ast.atom(&name), None); } UtilType::GlobalCss => { - let css = ExtractStyleValue::Css(ExtractCss { - css: optimize_css_block(&css_str), - file: self.filename.clone(), - }); - + let optimized_css = optimize_css_block(&css_str); + if !optimized_css.is_empty() { + let css = ExtractStyleValue::Css(ExtractCss { + css: optimized_css, + file: self.filename.clone(), + }); + self.styles.insert(css); + } *it = self.ast.expression_identifier(SPAN, self.ast.atom("")); - self.styles.insert(css); } } } @@ -336,7 +342,7 @@ impl<'a> VisitMut<'a> for DevupVisitor<'a> { None, it.arguments[1].to_expression_mut(), 0, - None, + &None, ); props_styles.extend(styles); diff --git a/libs/sheet/src/lib.rs b/libs/sheet/src/lib.rs index ad35874f..e39825ce 100644 --- a/libs/sheet/src/lib.rs +++ b/libs/sheet/src/lib.rs @@ -80,6 +80,16 @@ impl ExtractStyle for StyleSheetProperty { } static VAR_RE: Lazy = Lazy::new(|| Regex::new(r"\$\w+").unwrap()); +static INTERFACE_KEY_RE: Lazy = + Lazy::new(|| Regex::new(r"^[a-zA-Z_$][a-zA-Z0-9_$]*$").unwrap()); + +fn convert_interface_key(key: &str) -> String { + if INTERFACE_KEY_RE.is_match(key) { + key.to_string() + } else { + format!("[`{}`]", key.replace("`", "\\`")) + } +} fn convert_theme_variable_value(value: &str) -> String { if value.contains("$") { @@ -222,6 +232,59 @@ impl StyleSheet { self.theme = theme; } + pub fn create_interface( + &self, + package_name: &str, + color_interface_name: &str, + typography_interface_name: &str, + theme_interface_name: &str, + ) -> String { + let mut color_keys = HashSet::new(); + let mut typography_keys = HashSet::new(); + let mut theme_keys = HashSet::new(); + for color_theme in self.theme.colors.values() { + color_theme.0.keys().for_each(|key| { + color_keys.insert(key.clone()); + }); + } + self.theme.typography.keys().for_each(|key| { + typography_keys.insert(key.clone()); + }); + + self.theme.colors.keys().for_each(|key| { + theme_keys.insert(key.clone()); + }); + + if color_keys.is_empty() && typography_keys.is_empty() { + String::new() + } else { + format!( + "import \"{}\";declare module \"{}\"{{interface {}{{{}}}interface {}{{{}}}interface {}{{{}}}}}", + package_name, + package_name, + color_interface_name, + color_keys + .into_iter() + .map(|key| format!("{}:null;", convert_interface_key(&format!("${}", key)))) + .collect::>() + .join(""), + typography_interface_name, + typography_keys + .into_iter() + .map(|key| format!("{}:null;", convert_interface_key(&key))) + .collect::>() + .join(""), + theme_interface_name, + theme_keys + .into_iter() + // key to pascal + .map(|key| format!("{}:null;", convert_interface_key(&key))) + .collect::>() + .join("") + ) + } + } + pub fn create_css(&self) -> String { let mut css = self .imports @@ -261,16 +324,23 @@ impl StyleSheet { .iter() .partition(|prop| matches!(prop.selector, Some(StyleSelector::Global(_, _)))); global_props.sort(); - let (mut medias, mut sorted_props): (Vec<&StyleSheetProperty>, Vec<_>) = rest - .iter() - .partition(|prop| matches!(prop.selector, Some(StyleSelector::Media(_)))); + let (mut medias, mut sorted_props): (Vec<&StyleSheetProperty>, Vec<_>) = + rest.iter().partition(|prop| { + matches!( + prop.selector, + Some(StyleSelector::Media { + query: _, + selector: _ + }) + ) + }); sorted_props.sort(); medias.sort(); let medias = { let mut map = BTreeMap::new(); for prop in medias { - if let Some(StyleSelector::Media(media)) = &prop.selector { - map.entry(media).or_insert_with(Vec::new).push(prop); + if let Some(StyleSelector::Media { query, .. }) = &prop.selector { + map.entry(query).or_insert_with(Vec::new).push(prop); } } map @@ -346,6 +416,8 @@ impl StyleSheet { #[cfg(test)] mod tests { + use crate::theme::{ColorTheme, Typography}; + use super::*; use insta::assert_debug_snapshot; @@ -831,4 +903,61 @@ mod tests { sheet.add_import("test4.tsx", "@devup-ui/core/css/global4.css"); assert_debug_snapshot!(sheet.create_css()); } + + #[test] + fn test_get_theme_interface() { + let sheet = StyleSheet::default(); + assert_eq!( + sheet.create_interface( + "package", + "ColorInterface", + "TypographyInterface", + "ThemeInterface" + ), + "" + ); + + let mut sheet = StyleSheet::default(); + let mut theme = Theme::default(); + let mut color_theme = ColorTheme::default(); + color_theme.add_color("primary", "#000"); + theme.add_color_theme("dark", color_theme); + sheet.set_theme(theme); + assert_eq!( + sheet.create_interface( + "package", + "ColorInterface", + "TypographyInterface", + "ThemeInterface" + ), + "import \"package\";declare module \"package\"{interface ColorInterface{$primary:null;}interface TypographyInterface{}interface ThemeInterface{dark:null;}}" + ); + + // test wrong case + let mut sheet = StyleSheet::default(); + let mut theme = Theme::default(); + let mut color_theme = ColorTheme::default(); + color_theme.add_color("(primary)", "#000"); + theme.add_color_theme("dark", color_theme); + theme.add_typography( + "prim``ary", + vec![Some(Typography::new( + Some("Arial".to_string()), + Some("16px".to_string()), + Some("400".to_string()), + Some("1.5".to_string()), + Some("0.5".to_string()), + ))], + ); + sheet.set_theme(theme); + assert_eq!( + sheet.create_interface( + "package", + "ColorInterface", + "TypographyInterface", + "ThemeInterface" + ), + "import \"package\";declare module \"package\"{interface ColorInterface{[`$(primary)`]:null;}interface TypographyInterface{[`prim\\`\\`ary`]:null;}interface ThemeInterface{dark:null;}}" + ); + } } diff --git a/package.json b/package.json index d128056e..ad084419 100644 --- a/package.json +++ b/package.json @@ -14,18 +14,18 @@ }, "devDependencies": { "eslint-plugin-devup": "^2.0.5", - "eslint": "^9.30.1", + "eslint": "^9.31.0", "vitest": "^3.2.4", "@vitest/coverage-v8": "^3.2.4", "@changesets/cli": "^2.29.5", - "@types/node": "^24.0.12", + "@types/node": "^24.0.14", "happy-dom": "^18.0.1", "@testing-library/react": "^16.3.0", "@testing-library/jest-dom": "^6.6.3", "@devup-ui/vite-plugin": "workspace:*" }, "author": "devfive", - "packageManager": "pnpm@10.12.4", + "packageManager": "pnpm@10.13.1", "resolutions": { "vite": "^6" } diff --git a/packages/components/package.json b/packages/components/package.json index bf6306bd..ad0e54b4 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -48,15 +48,15 @@ }, "devDependencies": { "@devup-ui/vite-plugin": "workspace:*", - "@storybook/addon-docs": "^9.0.16", - "@storybook/addon-onboarding": "^9.0.16", - "@storybook/react-vite": "^9.0.16", + "@storybook/addon-docs": "^9.0.17", + "@storybook/addon-onboarding": "^9.0.17", + "@storybook/react-vite": "^9.0.17", "@types/react": "^19.1.8", - "eslint-plugin-storybook": "^9.0.16", + "eslint-plugin-storybook": "^9.0.17", "rollup-plugin-preserve-directives": "^0.4.0", - "storybook": "^9.0.16", + "storybook": "^9.0.17", "typescript": "^5.8.3", - "vite": "^7.0.3", + "vite": "^7.0.5", "vite-plugin-dts": "^4.5.4", "vitest": "^3.2.4" }, diff --git a/packages/components/src/components/Button/__tests__/__snapshots__/index.browser.test.tsx.snap b/packages/components/src/components/Button/__tests__/__snapshots__/index.browser.test.tsx.snap index 1c1951c3..6c911aee 100644 --- a/packages/components/src/components/Button/__tests__/__snapshots__/index.browser.test.tsx.snap +++ b/packages/components/src/components/Button/__tests__/__snapshots__/index.browser.test.tsx.snap @@ -4,7 +4,7 @@ exports[`Button > color should be white 1`] = `