diff --git a/.changepacks/changepack_log_Vdiwh8ITkqV6cbmVeUEta.json b/.changepacks/changepack_log_Vdiwh8ITkqV6cbmVeUEta.json new file mode 100644 index 00000000..75c37d1c --- /dev/null +++ b/.changepacks/changepack_log_Vdiwh8ITkqV6cbmVeUEta.json @@ -0,0 +1 @@ +{"changes":{"bindings/devup-ui-wasm/package.json":"Patch","packages/react/package.json":"Patch"},"note":"Support @media, @support","date":"2026-01-12T14:31:53.767704300Z"} \ No newline at end of file diff --git a/bindings/devup-ui-wasm/src/snapshots/devup_ui_wasm__tests__code_extract.snap b/bindings/devup-ui-wasm/src/snapshots/devup_ui_wasm__tests__code_extract.snap index f3b79c30..f147d7ee 100644 --- a/bindings/devup-ui-wasm/src/snapshots/devup_ui_wasm__tests__code_extract.snap +++ b/bindings/devup-ui-wasm/src/snapshots/devup_ui_wasm__tests__code_extract.snap @@ -1,5 +1,5 @@ --- source: bindings/devup-ui-wasm/src/lib.rs -expression: get_css(None).unwrap() +expression: "get_css(None, false).unwrap().split(\"*/\").nth(1).unwrap()" --- "@layer t;@layer t{:root{color-scheme:light;--primary:light-dark(#FFF,#000)}:root[data-theme=dark]{color-scheme:dark}}" diff --git a/libs/css/src/lib.rs b/libs/css/src/lib.rs index ec7d891b..740a3e52 100644 --- a/libs/css/src/lib.rs +++ b/libs/css/src/lib.rs @@ -40,7 +40,7 @@ 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 { selector: s, .. } => { + StyleSelector::At { selector: s, .. } => { if let Some(s) = s { s.replace("&", &format!(".{class_name}")) } else { @@ -85,7 +85,12 @@ pub fn add_selector_params(selector: StyleSelector, params: &str) -> StyleSelect StyleSelector::Global(value, file) => { StyleSelector::Global(format!("{}({})", value, params), file) } - StyleSelector::Media { query, selector } => StyleSelector::Media { + StyleSelector::At { + kind, + query, + selector, + } => StyleSelector::At { + kind, query: query.to_string(), selector: selector.map(|s| format!("{}({})", s, params)), }, @@ -317,6 +322,7 @@ mod tests { use crate::{ class_map::{get_class_map, reset_class_map, set_class_map}, debug::set_debug, + style_selector::AtRuleKind, }; use super::*; @@ -721,7 +727,8 @@ mod tests { assert_eq!( merge_selector( "cls", - Some(&StyleSelector::Media { + Some(&StyleSelector::At { + kind: AtRuleKind::Media, query: "print".to_string(), selector: None }) @@ -732,7 +739,8 @@ mod tests { assert_eq!( merge_selector( "cls", - Some(&StyleSelector::Media { + Some(&StyleSelector::At { + kind: AtRuleKind::Media, query: "print".to_string(), selector: Some("&:hover".to_string()) }) @@ -796,13 +804,15 @@ mod tests { ); assert_eq!( add_selector_params( - StyleSelector::Media { + StyleSelector::At { + kind: AtRuleKind::Media, query: "print".to_string(), selector: Some("&:is".to_string()) }, "test" ), - StyleSelector::Media { + StyleSelector::At { + kind: AtRuleKind::Media, query: "print".to_string(), selector: Some("&:is(test)".to_string()) } diff --git a/libs/css/src/style_selector.rs b/libs/css/src/style_selector.rs index e070a887..0dddd22e 100644 --- a/libs/css/src/style_selector.rs +++ b/libs/css/src/style_selector.rs @@ -10,9 +10,30 @@ use crate::{ utils::to_camel_case, }; +#[derive( + Debug, PartialEq, PartialOrd, Ord, Clone, Copy, Hash, Eq, Serialize, Deserialize, Default, +)] +pub enum AtRuleKind { + #[default] + Media, + Supports, + Container, +} + +impl Display for AtRuleKind { + fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { + match self { + AtRuleKind::Media => write!(f, "media"), + AtRuleKind::Supports => write!(f, "supports"), + AtRuleKind::Container => write!(f, "container"), + } + } +} + #[derive(Debug, PartialEq, Clone, Hash, Eq, Serialize, Deserialize)] pub enum StyleSelector { - Media { + At { + kind: AtRuleKind, query: String, selector: Option, }, @@ -30,7 +51,12 @@ fn optimize_selector_string(selector: &str) -> String { } pub fn optimize_selector(selector: StyleSelector) -> StyleSelector { match selector { - StyleSelector::Media { query, selector } => StyleSelector::Media { + StyleSelector::At { + kind, + query, + selector, + } => StyleSelector::At { + kind, query: query.to_string(), selector: selector .as_ref() @@ -54,15 +80,21 @@ impl Ord for StyleSelector { fn cmp(&self, other: &Self) -> Ordering { match (self, other) { ( - StyleSelector::Media { + StyleSelector::At { + kind: ka, query: a, selector: aa, }, - StyleSelector::Media { + StyleSelector::At { + kind: kb, query: b, selector: bb, }, ) => { + let k = (*ka as u8).cmp(&(*kb as u8)); + if k != Ordering::Equal { + return k; + } let c = a.cmp(b); if c == Ordering::Equal { aa.cmp(bb) } else { c } } @@ -74,20 +106,8 @@ impl Ord for StyleSelector { order_cmp } } - ( - StyleSelector::Media { - selector: _, - query: _, - }, - StyleSelector::Selector(_), - ) => Ordering::Greater, - ( - StyleSelector::Selector(_), - StyleSelector::Media { - selector: _, - query: _, - }, - ) => Ordering::Less, + (StyleSelector::At { .. }, StyleSelector::Selector(_)) => Ordering::Greater, + (StyleSelector::Selector(_), StyleSelector::At { .. }) => Ordering::Less, (StyleSelector::Global(a, _), StyleSelector::Global(b, _)) => { if a == b { return Ordering::Equal; @@ -143,9 +163,10 @@ impl From<&str> for StyleSelector { } else if let Some(s) = value.strip_prefix("theme-") { // first character should lower case StyleSelector::Selector(format!(":root[data-theme={}] &", to_camel_case(s))) - } else if value == "print" { - StyleSelector::Media { - query: "print".to_string(), + } else if matches!(value.as_str(), "print" | "screen" | "speech" | "all") { + StyleSelector::At { + kind: AtRuleKind::Media, + query: value.to_string(), selector: None, } } else { @@ -201,11 +222,16 @@ impl Display for StyleSelector { "{}", match self { StyleSelector::Selector(value) => value.to_string(), - StyleSelector::Media { query, selector } => { + StyleSelector::At { + kind, + query, + selector, + } => { + let space = if query.starts_with('(') { "" } else { " " }; if let Some(selector) = selector { - format!("@{query} {selector}") + format!("@{kind}{space}{query} {selector}") } else { - format!("@{query}") + format!("@{kind}{space}{query}") } } StyleSelector::Global(value, _) => value.to_string(), @@ -255,11 +281,33 @@ mod tests { #[rstest] #[case(StyleSelector::Selector("&:hover".to_string()), "&:hover")] - #[case(StyleSelector::Media { + #[case(StyleSelector::At { + kind: AtRuleKind::Media, query: "screen and (max-width: 600px)".to_string(), selector: None, }, - "@screen and (max-width: 600px)" + "@media screen and (max-width: 600px)" + )] + #[case(StyleSelector::At { + kind: AtRuleKind::Supports, + query: "(display: grid)".to_string(), + selector: None, + }, + "@supports(display: grid)" + )] + #[case(StyleSelector::At { + kind: AtRuleKind::Container, + query: "(min-width: 768px)".to_string(), + selector: None, + }, + "@container(min-width: 768px)" + )] + #[case(StyleSelector::At { + kind: AtRuleKind::Container, + query: "sidebar (min-width: 400px)".to_string(), + selector: None, + }, + "@container sidebar (min-width: 400px)" )] #[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) { @@ -269,7 +317,8 @@ mod tests { #[rstest] #[case( - StyleSelector::Media { + StyleSelector::At { + kind: AtRuleKind::Media, query: "screen".to_string(), selector: None, }, @@ -282,16 +331,31 @@ mod tests { std::cmp::Ordering::Less )] #[case( - StyleSelector::Media { + StyleSelector::At { + kind: AtRuleKind::Media, query: "a".to_string(), selector: None, }, - StyleSelector::Media { + StyleSelector::At { + kind: AtRuleKind::Media, query: "b".to_string(), selector: None, }, std::cmp::Ordering::Less )] + #[case( + StyleSelector::At { + kind: AtRuleKind::Media, + query: "(min-width: 768px)".to_string(), + selector: None, + }, + StyleSelector::At { + kind: AtRuleKind::Supports, + query: "(display: grid)".to_string(), + selector: None, + }, + std::cmp::Ordering::Less + )] #[case( StyleSelector::Global(":root[data-theme=dark]".to_string(), "file1.rs".to_string()), StyleSelector::Global(":root[data-theme=light]".to_string(), "file2.rs".to_string()), @@ -304,7 +368,8 @@ mod tests { )] #[case( StyleSelector::Selector("&:hover".to_string()), - StyleSelector::Media { + StyleSelector::At { + kind: AtRuleKind::Media, query: "screen".to_string(), selector: None, }, diff --git a/libs/extractor/src/css_utils.rs b/libs/extractor/src/css_utils.rs index b1f87f15..a73008b0 100644 --- a/libs/extractor/src/css_utils.rs +++ b/libs/extractor/src/css_utils.rs @@ -4,7 +4,7 @@ use crate::utils::{get_string_by_literal_expression, wrap_direct_call}; use css::{ optimize_multi_css_value::{check_multi_css_optimize, optimize_mutli_css_value}, rm_css_comment::rm_css_comment, - style_selector::StyleSelector, + style_selector::{AtRuleKind, StyleSelector}, }; use oxc_allocator::Allocator; use oxc_span::SPAN; @@ -246,23 +246,26 @@ pub fn css_to_style( 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() { - None - } else { - Some(format!("@media{s}")) + // Split by at-rules (@media, @supports, @container) to handle multiple at-rules in a single input + for at_rule in ["@media", "@supports", "@container"] { + if input.contains(at_rule) { + let at_inputs = input + .split(at_rule) + .flat_map(|s| { + let s = s.trim(); + if s.is_empty() { + None + } else { + Some(format!("{at_rule}{s}")) + } + }) + .collect::>(); + if at_inputs.len() > 1 { + for at_input in at_inputs { + styles.extend(css_to_style(&at_input, level, selector)); } - }) - .collect::>(); - if media_inputs.len() > 1 { - for media_input in media_inputs { - styles.extend(css_to_style(&media_input, level, selector)); + return styles; } - return styles; } } @@ -322,9 +325,10 @@ pub fn css_to_style( end = rest.find('}').unwrap_or(rest.len()); } let block = &rest[..end]; - let sel = &if let Some(StyleSelector::Media { query, .. }) = selector { + let sel = &if let Some(StyleSelector::At { kind, query, .. }) = selector { let local_sel = selector_part.trim().to_string(); - Some(StyleSelector::Media { + Some(StyleSelector::At { + kind: *kind, query: query.clone(), selector: if local_sel == "&" { None @@ -335,11 +339,26 @@ pub fn css_to_style( } else { let sel = selector_part.trim().to_string(); if sel.starts_with("@media") { - Some(StyleSelector::Media { + Some(StyleSelector::At { + kind: AtRuleKind::Media, query: sel.replace(" ", "").replace("and(", "and (")["@media".len()..] .to_string(), selector: None, }) + } else if sel.starts_with("@supports") { + Some(StyleSelector::At { + kind: AtRuleKind::Supports, + query: sel.replace(" ", "").replace("and(", "and (")["@supports".len()..] + .to_string(), + selector: None, + }) + } else if sel.starts_with("@container") { + Some(StyleSelector::At { + kind: AtRuleKind::Container, + query: sel.replace(" ", "").replace("and(", "and (")["@container".len()..] + .to_string(), + selector: None, + }) } else if sel.is_empty() { selector.clone() } else { @@ -543,11 +562,13 @@ mod tests { color: #fff; }`", vec![ - ("border", "1px solid #000", Some(StyleSelector::Media { + ("border", "1px solid #000", Some(StyleSelector::At { + kind: AtRuleKind::Media, query: "(min-width:768px)".to_string(), selector: None, })), - ("color", "#FFF", Some(StyleSelector::Media { + ("color", "#FFF", Some(StyleSelector::At { + kind: AtRuleKind::Media, query: "(min-width:768px)".to_string(), selector: None, })), @@ -564,19 +585,23 @@ mod tests { color: #fff; }`", vec![ - ("border", "1px solid #000", Some(StyleSelector::Media { + ("border", "1px solid #000", Some(StyleSelector::At { + kind: AtRuleKind::Media, query: "(min-width:768px)and (max-width:1024px)".to_string(), selector: None, })), - ("color", "#FFF", Some(StyleSelector::Media { + ("color", "#FFF", Some(StyleSelector::At { + kind: AtRuleKind::Media, query: "(min-width:768px)and (max-width:1024px)".to_string(), selector: None, })), - ("border", "1px solid #000", Some(StyleSelector::Media { + ("border", "1px solid #000", Some(StyleSelector::At { + kind: AtRuleKind::Media, query: "(min-width:768px)".to_string(), selector: None, })), - ("color", "#FFF", Some(StyleSelector::Media { + ("color", "#FFF", Some(StyleSelector::At { + kind: AtRuleKind::Media, query: "(min-width:768px)".to_string(), selector: None, })), @@ -594,19 +619,23 @@ mod tests { } }`", vec![ - ("border", "1px solid #FFF", Some(StyleSelector::Media { + ("border", "1px solid #FFF", Some(StyleSelector::At { + kind: AtRuleKind::Media, query: "(min-width:768px)".to_string(), selector: None, })), - ("color", "#FFF", Some(StyleSelector::Media { + ("color", "#FFF", Some(StyleSelector::At { + kind: AtRuleKind::Media, query: "(min-width:768px)".to_string(), selector: None, })), - ("border", "1px solid #000", Some(StyleSelector::Media { + ("border", "1px solid #000", Some(StyleSelector::At { + kind: AtRuleKind::Media, query: "(min-width:768px)".to_string(), selector: Some("&:hover,&:active,&:nth-child(2)".to_string()), })), - ("color", "#000", Some(StyleSelector::Media { + ("color", "#000", Some(StyleSelector::At { + kind: AtRuleKind::Media, query: "(min-width:768px)".to_string(), selector: Some("&:hover,&:active,&:nth-child(2)".to_string()), })), @@ -624,19 +653,23 @@ mod tests { } }`", vec![ - ("border", "1px solid #FFF", Some(StyleSelector::Media { + ("border", "1px solid #FFF", Some(StyleSelector::At { + kind: AtRuleKind::Media, query: "(min-width:768px)".to_string(), selector: None, })), - ("color", "#FFF", Some(StyleSelector::Media { + ("color", "#FFF", Some(StyleSelector::At { + kind: AtRuleKind::Media, query: "(min-width:768px)".to_string(), selector: None, })), - ("border", "1px solid #000", Some(StyleSelector::Media { + ("border", "1px solid #000", Some(StyleSelector::At { + kind: AtRuleKind::Media, query: "(min-width:768px)".to_string(), selector: Some("&:hover".to_string()), })), - ("color", "#000", Some(StyleSelector::Media { + ("color", "#000", Some(StyleSelector::At { + kind: AtRuleKind::Media, query: "(min-width:768px)".to_string(), selector: Some("&:hover".to_string()), })), @@ -664,35 +697,43 @@ mod tests { } }`", vec![ - ("border", "1px solid #FFF", Some(StyleSelector::Media { + ("border", "1px solid #FFF", Some(StyleSelector::At { + kind: AtRuleKind::Media, query: "(max-width:768px)and (min-width:480px)".to_string(), selector: None, })), - ("color", "#FFF", Some(StyleSelector::Media { + ("color", "#FFF", Some(StyleSelector::At { + kind: AtRuleKind::Media, query: "(max-width:768px)and (min-width:480px)".to_string(), selector: None, })), - ("border", "1px solid #000", Some(StyleSelector::Media { + ("border", "1px solid #000", Some(StyleSelector::At { + kind: AtRuleKind::Media, query: "(max-width:768px)and (min-width:480px)".to_string(), selector: Some("&:hover".to_string()), })), - ("color", "#000", Some(StyleSelector::Media { + ("color", "#000", Some(StyleSelector::At { + kind: AtRuleKind::Media, query: "(max-width:768px)and (min-width:480px)".to_string(), selector: Some("&:hover".to_string()), })), - ("border", "1px solid #FFF", Some(StyleSelector::Media { + ("border", "1px solid #FFF", Some(StyleSelector::At { + kind: AtRuleKind::Media, query: "(min-width:768px)".to_string(), selector: None, })), - ("color", "#FFF", Some(StyleSelector::Media { + ("color", "#FFF", Some(StyleSelector::At { + kind: AtRuleKind::Media, query: "(min-width:768px)".to_string(), selector: None, })), - ("border", "1px solid #000", Some(StyleSelector::Media { + ("border", "1px solid #000", Some(StyleSelector::At { + kind: AtRuleKind::Media, query: "(min-width:768px)".to_string(), selector: Some("&:hover".to_string()), })), - ("color", "#000", Some(StyleSelector::Media { + ("color", "#000", Some(StyleSelector::At { + kind: AtRuleKind::Media, query: "(min-width:768px)".to_string(), selector: Some("&:hover".to_string()), })), @@ -710,19 +751,23 @@ mod tests { color: #000; }`", vec![ - ("border", "1px solid #FFF", Some(StyleSelector::Media { + ("border", "1px solid #FFF", Some(StyleSelector::At { + kind: AtRuleKind::Media, query: "(min-width:768px)".to_string(), selector: None, })), - ("color", "#FFF", Some(StyleSelector::Media { + ("color", "#FFF", Some(StyleSelector::At { + kind: AtRuleKind::Media, query: "(min-width:768px)".to_string(), selector: None, })), - ("border", "1px solid #000", Some(StyleSelector::Media { + ("border", "1px solid #000", Some(StyleSelector::At { + kind: AtRuleKind::Media, query: "(max-width:768px)and (min-width:480px)".to_string(), selector: None, })), - ("color", "#000", Some(StyleSelector::Media { + ("color", "#000", Some(StyleSelector::At { + kind: AtRuleKind::Media, query: "(max-width:768px)and (min-width:480px)".to_string(), selector: None, })), @@ -737,6 +782,76 @@ mod tests { }`", vec![] )] + // @supports test cases + #[case( + "`@supports (display: grid) { + display: grid; + grid-template-columns: 1fr 1fr; + }`", + vec![ + ("display", "grid", Some(StyleSelector::At { + kind: AtRuleKind::Supports, + query: "(display:grid)".to_string(), + selector: None, + })), + ("grid-template-columns", "1fr 1fr", Some(StyleSelector::At { + kind: AtRuleKind::Supports, + query: "(display:grid)".to_string(), + selector: None, + })), + ] + )] + #[case( + "`@supports (display: flex) { + &:hover { + display: flex; + } + }`", + vec![ + ("display", "flex", Some(StyleSelector::At { + kind: AtRuleKind::Supports, + query: "(display:flex)".to_string(), + selector: Some("&:hover".to_string()), + })), + ] + )] + #[case( + "`@supports not (display: grid) { + display: block; + }`", + vec![ + ("display", "block", Some(StyleSelector::At { + kind: AtRuleKind::Supports, + query: "not(display:grid)".to_string(), + selector: None, + })), + ] + )] + // @container test cases + #[case( + "`@container (min-width: 768px) { + padding: 10px; + }`", + vec![ + ("padding", "10px", Some(StyleSelector::At { + kind: AtRuleKind::Container, + query: "(min-width:768px)".to_string(), + selector: None, + })), + ] + )] + #[case( + "`@container sidebar (min-width: 400px) { + display: flex; + }`", + vec![ + ("display", "flex", Some(StyleSelector::At { + kind: AtRuleKind::Container, + query: "sidebar(min-width:400px)".to_string(), + selector: None, + })), + ] + )] #[case( "`ul { font-family: 'Roboto Hello', sans-serif; }`", vec![ @@ -939,11 +1054,13 @@ mod tests { color: #fff; }", vec![ - ("border", "1px solid #000", Some(StyleSelector::Media { + ("border", "1px solid #000", Some(StyleSelector::At { + kind: AtRuleKind::Media, query: "(min-width:768px)".to_string(), selector: None, })), - ("color", "#FFF", Some(StyleSelector::Media { + ("color", "#FFF", Some(StyleSelector::At { + kind: AtRuleKind::Media, query: "(min-width:768px)".to_string(), selector: None, })), @@ -960,19 +1077,23 @@ mod tests { color: #fff; }", vec![ - ("border", "1px solid #000", Some(StyleSelector::Media { + ("border", "1px solid #000", Some(StyleSelector::At { + kind: AtRuleKind::Media, query: "(min-width:768px)and (max-width:1024px)".to_string(), selector: None, })), - ("color", "#FFF", Some(StyleSelector::Media { + ("color", "#FFF", Some(StyleSelector::At { + kind: AtRuleKind::Media, query: "(min-width:768px)and (max-width:1024px)".to_string(), selector: None, })), - ("border", "1px solid #000", Some(StyleSelector::Media { + ("border", "1px solid #000", Some(StyleSelector::At { + kind: AtRuleKind::Media, query: "(min-width:768px)".to_string(), selector: None, })), - ("color", "#FFF", Some(StyleSelector::Media { + ("color", "#FFF", Some(StyleSelector::At { + kind: AtRuleKind::Media, query: "(min-width:768px)".to_string(), selector: None, })), @@ -990,19 +1111,23 @@ mod tests { } }", vec![ - ("border", "1px solid #FFF", Some(StyleSelector::Media { + ("border", "1px solid #FFF", Some(StyleSelector::At { + kind: AtRuleKind::Media, query: "(min-width:768px)".to_string(), selector: None, })), - ("color", "#FFF", Some(StyleSelector::Media { + ("color", "#FFF", Some(StyleSelector::At { + kind: AtRuleKind::Media, query: "(min-width:768px)".to_string(), selector: None, })), - ("border", "1px solid #000", Some(StyleSelector::Media { + ("border", "1px solid #000", Some(StyleSelector::At { + kind: AtRuleKind::Media, query: "(min-width:768px)".to_string(), selector: Some("&:hover,&:active,&:nth-child(2)".to_string()), })), - ("color", "#000", Some(StyleSelector::Media { + ("color", "#000", Some(StyleSelector::At { + kind: AtRuleKind::Media, query: "(min-width:768px)".to_string(), selector: Some("&:hover,&:active,&:nth-child(2)".to_string()), })), @@ -1020,19 +1145,23 @@ mod tests { } }", vec![ - ("border", "1px solid #FFF", Some(StyleSelector::Media { + ("border", "1px solid #FFF", Some(StyleSelector::At { + kind: AtRuleKind::Media, query: "(min-width:768px)".to_string(), selector: None, })), - ("color", "#FFF", Some(StyleSelector::Media { + ("color", "#FFF", Some(StyleSelector::At { + kind: AtRuleKind::Media, query: "(min-width:768px)".to_string(), selector: None, })), - ("border", "1px solid #000", Some(StyleSelector::Media { + ("border", "1px solid #000", Some(StyleSelector::At { + kind: AtRuleKind::Media, query: "(min-width:768px)".to_string(), selector: Some("&:hover".to_string()), })), - ("color", "#000", Some(StyleSelector::Media { + ("color", "#000", Some(StyleSelector::At { + kind: AtRuleKind::Media, query: "(min-width:768px)".to_string(), selector: Some("&:hover".to_string()), })), @@ -1060,35 +1189,43 @@ mod tests { } }", vec![ - ("border", "1px solid #FFF", Some(StyleSelector::Media { + ("border", "1px solid #FFF", Some(StyleSelector::At { + kind: AtRuleKind::Media, query: "(max-width:768px)and (min-width:480px)".to_string(), selector: None, })), - ("color", "#FFF", Some(StyleSelector::Media { + ("color", "#FFF", Some(StyleSelector::At { + kind: AtRuleKind::Media, query: "(max-width:768px)and (min-width:480px)".to_string(), selector: None, })), - ("border", "1px solid #000", Some(StyleSelector::Media { + ("border", "1px solid #000", Some(StyleSelector::At { + kind: AtRuleKind::Media, query: "(max-width:768px)and (min-width:480px)".to_string(), selector: Some("&:hover".to_string()), })), - ("color", "#000", Some(StyleSelector::Media { + ("color", "#000", Some(StyleSelector::At { + kind: AtRuleKind::Media, query: "(max-width:768px)and (min-width:480px)".to_string(), selector: Some("&:hover".to_string()), })), - ("border", "1px solid #FFF", Some(StyleSelector::Media { + ("border", "1px solid #FFF", Some(StyleSelector::At { + kind: AtRuleKind::Media, query: "(min-width:768px)".to_string(), selector: None, })), - ("color", "#FFF", Some(StyleSelector::Media { + ("color", "#FFF", Some(StyleSelector::At { + kind: AtRuleKind::Media, query: "(min-width:768px)".to_string(), selector: None, })), - ("border", "1px solid #000", Some(StyleSelector::Media { + ("border", "1px solid #000", Some(StyleSelector::At { + kind: AtRuleKind::Media, query: "(min-width:768px)".to_string(), selector: Some("&:hover".to_string()), })), - ("color", "#000", Some(StyleSelector::Media { + ("color", "#000", Some(StyleSelector::At { + kind: AtRuleKind::Media, query: "(min-width:768px)".to_string(), selector: Some("&:hover".to_string()), })), @@ -1106,19 +1243,23 @@ mod tests { color: #000; }", vec![ - ("border", "1px solid #FFF", Some(StyleSelector::Media { + ("border", "1px solid #FFF", Some(StyleSelector::At { + kind: AtRuleKind::Media, query: "(min-width:768px)".to_string(), selector: None, })), - ("color", "#FFF", Some(StyleSelector::Media { + ("color", "#FFF", Some(StyleSelector::At { + kind: AtRuleKind::Media, query: "(min-width:768px)".to_string(), selector: None, })), - ("border", "1px solid #000", Some(StyleSelector::Media { + ("border", "1px solid #000", Some(StyleSelector::At { + kind: AtRuleKind::Media, query: "(max-width:768px)and (min-width:480px)".to_string(), selector: None, })), - ("color", "#000", Some(StyleSelector::Media { + ("color", "#000", Some(StyleSelector::At { + kind: AtRuleKind::Media, query: "(max-width:768px)and (min-width:480px)".to_string(), selector: None, })), diff --git a/libs/extractor/src/extractor/extract_style_from_expression.rs b/libs/extractor/src/extractor/extract_style_from_expression.rs index 3c878909..888b2b93 100644 --- a/libs/extractor/src/extractor/extract_style_from_expression.rs +++ b/libs/extractor/src/extractor/extract_style_from_expression.rs @@ -239,6 +239,48 @@ pub fn extract_style_from_expression<'a>( }; } + // Handle at-rules: @media, @supports, @container (or _media, _supports, _container) + let at_rule_name = name + .strip_prefix("@") + .or_else(|| name.strip_prefix("_")) + .filter(|n| matches!(*n, "media" | "supports" | "container")); + + if let Some(at_rule) = at_rule_name + && let Expression::ObjectExpression(obj) = expression + { + let mut props = vec![]; + for p in obj.properties.iter_mut() { + if let ObjectPropertyKind::ObjectProperty(o) = p + && let Some(query) = get_string_by_property_key(&o.key) + { + let at_selector = StyleSelector::At { + kind: match at_rule { + "media" => css::style_selector::AtRuleKind::Media, + "supports" => css::style_selector::AtRuleKind::Supports, + "container" => css::style_selector::AtRuleKind::Container, + _ => unreachable!(), + }, + query: query.to_string(), + selector: selector.as_ref().map(|s| s.to_string()), + }; + props.extend( + extract_style_from_expression( + ast_builder, + None, + &mut o.value, + level, + &Some(at_selector), + ) + .styles, + ); + } + } + return ExtractResult { + styles: props, + ..ExtractResult::default() + }; + } + if let Some(new_selector) = name.strip_prefix("_") { return extract_style_from_expression( ast_builder, diff --git a/libs/extractor/src/lib.rs b/libs/extractor/src/lib.rs index 78f2b7c1..d24faa7f 100644 --- a/libs/extractor/src/lib.rs +++ b/libs/extractor/src/lib.rs @@ -9020,6 +9020,345 @@ const margin = 5; )); } + #[test] + #[serial] + fn test_media_query_selectors() { + // Test _print media query selector + reset_class_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import {Box} from '@devup-ui/core' + +"#, + ExtractOption { + package: "@devup-ui/core".to_string(), + css_dir: "@devup-ui/core".to_string(), + single_css: false, + import_main_css: false + } + ) + .unwrap() + )); + + // Test _screen media query selector + reset_class_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import {Box} from '@devup-ui/core' + +"#, + ExtractOption { + package: "@devup-ui/core".to_string(), + css_dir: "@devup-ui/core".to_string(), + single_css: false, + import_main_css: false + } + ) + .unwrap() + )); + + // Test _speech media query selector + reset_class_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import {Box} from '@devup-ui/core' + +"#, + ExtractOption { + package: "@devup-ui/core".to_string(), + css_dir: "@devup-ui/core".to_string(), + single_css: false, + import_main_css: false + } + ) + .unwrap() + )); + + // Test _all media query selector + reset_class_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import {Box} from '@devup-ui/core' + +"#, + ExtractOption { + package: "@devup-ui/core".to_string(), + css_dir: "@devup-ui/core".to_string(), + single_css: false, + import_main_css: false + } + ) + .unwrap() + )); + + // Test multiple media query selectors combined + reset_class_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import {Box} from '@devup-ui/core' + +"#, + ExtractOption { + package: "@devup-ui/core".to_string(), + css_dir: "@devup-ui/core".to_string(), + single_css: false, + import_main_css: false + } + ) + .unwrap() + )); + } + + #[test] + #[serial] + fn test_at_rules_underscore_prefix() { + // Test _container at-rule + reset_class_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import {Box} from '@devup-ui/core' + +"#, + ExtractOption { + package: "@devup-ui/core".to_string(), + css_dir: "@devup-ui/core".to_string(), + single_css: false, + import_main_css: false + } + ) + .unwrap() + )); + + // Test _media at-rule + reset_class_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import {Box} from '@devup-ui/core' + +"#, + ExtractOption { + package: "@devup-ui/core".to_string(), + css_dir: "@devup-ui/core".to_string(), + single_css: false, + import_main_css: false + } + ) + .unwrap() + )); + + // Test _supports at-rule + reset_class_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import {Box} from '@devup-ui/core' + +"#, + ExtractOption { + package: "@devup-ui/core".to_string(), + css_dir: "@devup-ui/core".to_string(), + single_css: false, + import_main_css: false + } + ) + .unwrap() + )); + + // Test _container with named container + reset_class_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import {Box} from '@devup-ui/core' + +"#, + ExtractOption { + package: "@devup-ui/core".to_string(), + css_dir: "@devup-ui/core".to_string(), + single_css: false, + import_main_css: false + } + ) + .unwrap() + )); + } + + #[test] + #[serial] + fn test_at_rules_at_prefix() { + // Test @container at-rule + reset_class_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import {Box} from '@devup-ui/core' + +"#, + ExtractOption { + package: "@devup-ui/core".to_string(), + css_dir: "@devup-ui/core".to_string(), + single_css: false, + import_main_css: false + } + ) + .unwrap() + )); + + // Test @media at-rule + reset_class_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import {Box} from '@devup-ui/core' + +"#, + ExtractOption { + package: "@devup-ui/core".to_string(), + css_dir: "@devup-ui/core".to_string(), + single_css: false, + import_main_css: false + } + ) + .unwrap() + )); + + // Test @supports at-rule + reset_class_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import {Box} from '@devup-ui/core' + +"#, + ExtractOption { + package: "@devup-ui/core".to_string(), + css_dir: "@devup-ui/core".to_string(), + single_css: false, + import_main_css: false + } + ) + .unwrap() + )); + } + + #[test] + #[serial] + fn test_global_css_at_rules() { + // Test globalCss with @media nested inside selector + reset_class_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import {globalCss} from '@devup-ui/core' +globalCss({ + body: { + "@media": { + "(min-width: 768px)": { bg: "white" } + } + } +}) +"#, + ExtractOption { + package: "@devup-ui/core".to_string(), + css_dir: "@devup-ui/core".to_string(), + single_css: false, + import_main_css: false + } + ) + .unwrap() + )); + + // Test globalCss with @supports nested inside selector + reset_class_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import {globalCss} from '@devup-ui/core' +globalCss({ + ".grid-container": { + "@supports": { + "(display: grid)": { display: "grid" } + } + } +}) +"#, + ExtractOption { + package: "@devup-ui/core".to_string(), + css_dir: "@devup-ui/core".to_string(), + single_css: false, + import_main_css: false + } + ) + .unwrap() + )); + + // Test globalCss with @container nested inside selector + reset_class_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import {globalCss} from '@devup-ui/core' +globalCss({ + ".card": { + "@container": { + "(min-width: 400px)": { p: 4 } + } + } +}) +"#, + ExtractOption { + package: "@devup-ui/core".to_string(), + css_dir: "@devup-ui/core".to_string(), + single_css: false, + import_main_css: false + } + ) + .unwrap() + )); + + // Test globalCss with multiple at-rules nested inside selectors + reset_class_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import {globalCss} from '@devup-ui/core' +globalCss({ + body: { + bg: "gray", + "@media": { + "(min-width: 768px)": { bg: "white" }, + "(prefers-color-scheme: dark)": { bg: "black" } + } + }, + ".container": { + "@supports": { + "(display: grid)": { display: "grid" } + } + } +}) +"#, + ExtractOption { + package: "@devup-ui/core".to_string(), + css_dir: "@devup-ui/core".to_string(), + single_css: false, + import_main_css: false + } + ) + .unwrap() + )); + } + #[rstest] #[case("test.tsx", "const x = 1;", "@devup-ui/react", false)] // no package string #[case( diff --git a/libs/extractor/src/snapshots/extractor__tests__apply_typography-2.snap b/libs/extractor/src/snapshots/extractor__tests__apply_typography-2.snap index 96205e24..13aa2085 100644 --- a/libs/extractor/src/snapshots/extractor__tests__apply_typography-2.snap +++ b/libs/extractor/src/snapshots/extractor__tests__apply_typography-2.snap @@ -1,6 +1,6 @@ --- source: libs/extractor/src/lib.rs -expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import {Text} from '@devup-ui/core'\n \n \"#,\nExtractOption\n{ package: \"@devup-ui/core\".to_string(), css_file: None }).unwrap())" +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import {Text} from '@devup-ui/core'\n \n \"#,\nExtractOption\n{\n package: \"@devup-ui/core\".to_string(), css_dir:\n \"@devup-ui/core\".to_string(), single_css: true, import_main_css: false\n}).unwrap())" --- ToBTreeSet { styles: { diff --git a/libs/extractor/src/snapshots/extractor__tests__apply_typography-3.snap b/libs/extractor/src/snapshots/extractor__tests__apply_typography-3.snap index d91897e2..db27b0e6 100644 --- a/libs/extractor/src/snapshots/extractor__tests__apply_typography-3.snap +++ b/libs/extractor/src/snapshots/extractor__tests__apply_typography-3.snap @@ -1,6 +1,6 @@ --- source: libs/extractor/src/lib.rs -expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import {Text} from '@devup-ui/core'\n \n \"#,\nExtractOption\n{ package: \"@devup-ui/core\".to_string(), css_file: None }).unwrap())" +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import {Text} from '@devup-ui/core'\n \n \"#,\nExtractOption\n{\n package: \"@devup-ui/core\".to_string(), css_dir:\n \"@devup-ui/core\".to_string(), single_css: true, import_main_css: false\n}).unwrap())" --- ToBTreeSet { styles: { diff --git a/libs/extractor/src/snapshots/extractor__tests__apply_typography.snap b/libs/extractor/src/snapshots/extractor__tests__apply_typography.snap index 70a43587..54037105 100644 --- a/libs/extractor/src/snapshots/extractor__tests__apply_typography.snap +++ b/libs/extractor/src/snapshots/extractor__tests__apply_typography.snap @@ -1,6 +1,6 @@ --- source: libs/extractor/src/lib.rs -expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import {Text} from '@devup-ui/core'\n \n \"#,\nExtractOption\n{ package: \"@devup-ui/core\".to_string(), css_file: None }).unwrap())" +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import {Text} from '@devup-ui/core'\n \n \"#,\nExtractOption\n{\n package: \"@devup-ui/core\".to_string(), css_dir:\n \"@devup-ui/core\".to_string(), single_css: true, import_main_css: false\n}).unwrap())" --- ToBTreeSet { styles: { diff --git a/libs/extractor/src/snapshots/extractor__tests__apply_var_typography-2.snap b/libs/extractor/src/snapshots/extractor__tests__apply_var_typography-2.snap index fd54ef0d..312595a0 100644 --- a/libs/extractor/src/snapshots/extractor__tests__apply_var_typography-2.snap +++ b/libs/extractor/src/snapshots/extractor__tests__apply_var_typography-2.snap @@ -1,6 +1,6 @@ --- source: libs/extractor/src/lib.rs -expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import {Text} from '@devup-ui/core'\n \n \"#,\nExtractOption\n{ package: \"@devup-ui/core\".to_string(), css_file: None }).unwrap())" +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import {Text} from '@devup-ui/core'\n \n \"#,\nExtractOption\n{\n package: \"@devup-ui/core\".to_string(), css_dir:\n \"@devup-ui/core\".to_string(), single_css: true, import_main_css: false\n}).unwrap())" --- ToBTreeSet { styles: {}, diff --git a/libs/extractor/src/snapshots/extractor__tests__apply_var_typography-3.snap b/libs/extractor/src/snapshots/extractor__tests__apply_var_typography-3.snap index 2edef42f..b2502eba 100644 --- a/libs/extractor/src/snapshots/extractor__tests__apply_var_typography-3.snap +++ b/libs/extractor/src/snapshots/extractor__tests__apply_var_typography-3.snap @@ -1,6 +1,6 @@ --- source: libs/extractor/src/lib.rs -expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import {Text} from '@devup-ui/core'\n \n \"#,\nExtractOption\n{ package: \"@devup-ui/core\".to_string(), css_file: None }).unwrap())" +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import {Text} from '@devup-ui/core'\n \n \"#,\nExtractOption\n{\n package: \"@devup-ui/core\".to_string(), css_dir:\n \"@devup-ui/core\".to_string(), single_css: true, import_main_css: false\n}).unwrap())" --- ToBTreeSet { styles: {}, diff --git a/libs/extractor/src/snapshots/extractor__tests__apply_var_typography-4.snap b/libs/extractor/src/snapshots/extractor__tests__apply_var_typography-4.snap index 7b55cf25..ae0e5ff9 100644 --- a/libs/extractor/src/snapshots/extractor__tests__apply_var_typography-4.snap +++ b/libs/extractor/src/snapshots/extractor__tests__apply_var_typography-4.snap @@ -1,6 +1,6 @@ --- source: libs/extractor/src/lib.rs -expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import {Box as DevupButton} from '@devup-ui/core'\n \n \n \"#,\nExtractOption\n{ package: \"@devup-ui/core\".to_string(), css_file: None }).unwrap())" +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import {Box as DevupButton} from '@devup-ui/core'\n \n \n \"#,\nExtractOption\n{\n package: \"@devup-ui/core\".to_string(), css_dir:\n \"@devup-ui/core\".to_string(), single_css: true, import_main_css: false\n}).unwrap())" --- ToBTreeSet { styles: { diff --git a/libs/extractor/src/snapshots/extractor__tests__apply_var_typography.snap b/libs/extractor/src/snapshots/extractor__tests__apply_var_typography.snap index 6126d98a..1f65a842 100644 --- a/libs/extractor/src/snapshots/extractor__tests__apply_var_typography.snap +++ b/libs/extractor/src/snapshots/extractor__tests__apply_var_typography.snap @@ -1,6 +1,6 @@ --- source: libs/extractor/src/lib.rs -expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import {Text} from '@devup-ui/core'\n \n \"#,\nExtractOption\n{ package: \"@devup-ui/core\".to_string(), css_file: None }).unwrap())" +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import {Text} from '@devup-ui/core'\n \n \"#,\nExtractOption\n{\n package: \"@devup-ui/core\".to_string(), css_dir:\n \"@devup-ui/core\".to_string(), single_css: true, import_main_css: false\n}).unwrap())" --- ToBTreeSet { styles: {}, diff --git a/libs/extractor/src/snapshots/extractor__tests__at_rules_at_prefix-2.snap b/libs/extractor/src/snapshots/extractor__tests__at_rules_at_prefix-2.snap new file mode 100644 index 00000000..5db8c825 --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__at_rules_at_prefix-2.snap @@ -0,0 +1,24 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import {Box} from '@devup-ui/core'\n\n\"#,\nExtractOption\n{\n package: \"@devup-ui/core\".to_string(), css_dir:\n \"@devup-ui/core\".to_string(), single_css: false, import_main_css: false\n}).unwrap())" +--- +ToBTreeSet { + styles: { + Static( + ExtractStaticStyle { + property: "background", + value: "blue", + level: 0, + selector: Some( + At { + kind: Media, + query: "(min-width: 768px)", + selector: None, + }, + ), + style_order: None, + }, + ), + }, + code: "import \"@devup-ui/core/devup-ui-0.css\";\n
;\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__at_rules_at_prefix-3.snap b/libs/extractor/src/snapshots/extractor__tests__at_rules_at_prefix-3.snap new file mode 100644 index 00000000..bf57c692 --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__at_rules_at_prefix-3.snap @@ -0,0 +1,24 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import {Box} from '@devup-ui/core'\n\n\"#,\nExtractOption\n{\n package: \"@devup-ui/core\".to_string(), css_dir:\n \"@devup-ui/core\".to_string(), single_css: false, import_main_css: false\n}).unwrap())" +--- +ToBTreeSet { + styles: { + Static( + ExtractStaticStyle { + property: "display", + value: "flex", + level: 0, + selector: Some( + At { + kind: Supports, + query: "(display: flex)", + selector: None, + }, + ), + style_order: None, + }, + ), + }, + code: "import \"@devup-ui/core/devup-ui-0.css\";\n
;\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__at_rules_at_prefix.snap b/libs/extractor/src/snapshots/extractor__tests__at_rules_at_prefix.snap new file mode 100644 index 00000000..e370b3c7 --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__at_rules_at_prefix.snap @@ -0,0 +1,24 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import {Box} from '@devup-ui/core'\n\n\"#,\nExtractOption\n{\n package: \"@devup-ui/core\".to_string(), css_dir:\n \"@devup-ui/core\".to_string(), single_css: false, import_main_css: false\n}).unwrap())" +--- +ToBTreeSet { + styles: { + Static( + ExtractStaticStyle { + property: "background", + value: "red", + level: 0, + selector: Some( + At { + kind: Container, + query: "(min-width: 400px)", + selector: None, + }, + ), + style_order: None, + }, + ), + }, + code: "import \"@devup-ui/core/devup-ui-0.css\";\n
;\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__at_rules_underscore_prefix-2.snap b/libs/extractor/src/snapshots/extractor__tests__at_rules_underscore_prefix-2.snap new file mode 100644 index 00000000..d441a6a8 --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__at_rules_underscore_prefix-2.snap @@ -0,0 +1,24 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import {Box} from '@devup-ui/core'\n\n\"#,\nExtractOption\n{\n package: \"@devup-ui/core\".to_string(), css_dir:\n \"@devup-ui/core\".to_string(), single_css: false, import_main_css: false\n}).unwrap())" +--- +ToBTreeSet { + styles: { + Static( + ExtractStaticStyle { + property: "background", + value: "blue", + level: 0, + selector: Some( + At { + kind: Media, + query: "(min-width: 768px)", + selector: None, + }, + ), + style_order: None, + }, + ), + }, + code: "import \"@devup-ui/core/devup-ui-0.css\";\n
;\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__at_rules_underscore_prefix-3.snap b/libs/extractor/src/snapshots/extractor__tests__at_rules_underscore_prefix-3.snap new file mode 100644 index 00000000..c635b562 --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__at_rules_underscore_prefix-3.snap @@ -0,0 +1,24 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import {Box} from '@devup-ui/core'\n\n\"#,\nExtractOption\n{\n package: \"@devup-ui/core\".to_string(), css_dir:\n \"@devup-ui/core\".to_string(), single_css: false, import_main_css: false\n}).unwrap())" +--- +ToBTreeSet { + styles: { + Static( + ExtractStaticStyle { + property: "display", + value: "grid", + level: 0, + selector: Some( + At { + kind: Supports, + query: "(display: grid)", + selector: None, + }, + ), + style_order: None, + }, + ), + }, + code: "import \"@devup-ui/core/devup-ui-0.css\";\n
;\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__at_rules_underscore_prefix-4.snap b/libs/extractor/src/snapshots/extractor__tests__at_rules_underscore_prefix-4.snap new file mode 100644 index 00000000..f5008b91 --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__at_rules_underscore_prefix-4.snap @@ -0,0 +1,24 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import {Box} from '@devup-ui/core'\n\n\"#,\nExtractOption\n{\n package: \"@devup-ui/core\".to_string(), css_dir:\n \"@devup-ui/core\".to_string(), single_css: false, import_main_css: false\n}).unwrap())" +--- +ToBTreeSet { + styles: { + Static( + ExtractStaticStyle { + property: "padding", + value: "16px", + level: 0, + selector: Some( + At { + kind: Container, + query: "sidebar (min-width: 400px)", + selector: None, + }, + ), + style_order: None, + }, + ), + }, + code: "import \"@devup-ui/core/devup-ui-0.css\";\n
;\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__at_rules_underscore_prefix.snap b/libs/extractor/src/snapshots/extractor__tests__at_rules_underscore_prefix.snap new file mode 100644 index 00000000..9d3bb36d --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__at_rules_underscore_prefix.snap @@ -0,0 +1,24 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import {Box} from '@devup-ui/core'\n\n\"#,\nExtractOption\n{\n package: \"@devup-ui/core\".to_string(), css_dir:\n \"@devup-ui/core\".to_string(), single_css: false, import_main_css: false\n}).unwrap())" +--- +ToBTreeSet { + styles: { + Static( + ExtractStaticStyle { + property: "background", + value: "red", + level: 0, + selector: Some( + At { + kind: Container, + query: "(min-width: 400px)", + selector: None, + }, + ), + style_order: None, + }, + ), + }, + code: "import \"@devup-ui/core/devup-ui-0.css\";\n
;\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__avoid_same_name_component.snap b/libs/extractor/src/snapshots/extractor__tests__avoid_same_name_component.snap index 0d152461..1cdbadae 100644 --- a/libs/extractor/src/snapshots/extractor__tests__avoid_same_name_component.snap +++ b/libs/extractor/src/snapshots/extractor__tests__avoid_same_name_component.snap @@ -1,6 +1,6 @@ --- source: libs/extractor/src/lib.rs -expression: "ToBTreeSet::from(extract(\"test.jsx\",\nr#\"import {Box} from '@devup-ui/core'\nimport {Button} from '@devup/ui'\n ;\n ;