diff --git a/.changeset/fair-cherries-confess.md b/.changeset/fair-cherries-confess.md new file mode 100644 index 00000000..8e277764 --- /dev/null +++ b/.changeset/fair-cherries-confess.md @@ -0,0 +1,5 @@ +--- +"@devup-ui/react": patch +--- + +Add selectors diff --git a/.changeset/nasty-badgers-kiss.md b/.changeset/nasty-badgers-kiss.md new file mode 100644 index 00000000..30db8530 --- /dev/null +++ b/.changeset/nasty-badgers-kiss.md @@ -0,0 +1,5 @@ +--- +"@devup-ui/wasm": patch +--- + +Add default value diff --git a/.changeset/sixty-wombats-sort.md b/.changeset/sixty-wombats-sort.md new file mode 100644 index 00000000..ad5bdd2a --- /dev/null +++ b/.changeset/sixty-wombats-sort.md @@ -0,0 +1,5 @@ +--- +"@devup-ui/wasm": patch +--- + +Fix conditional style issue diff --git a/.gitignore b/.gitignore index 59f94a5b..8eef93ae 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .idea +cobertura.xml build dist node_modules diff --git a/libs/css/src/lib.rs b/libs/css/src/lib.rs index 346fda1c..1ac42ff6 100644 --- a/libs/css/src/lib.rs +++ b/libs/css/src/lib.rs @@ -60,10 +60,14 @@ impl Ord for StyleSelector { impl From<&str> for StyleSelector { fn from(value: &str) -> Self { - if value.contains(":") { - let t: Vec<_> = value.split(":").collect(); + if value.contains("&") { + let t: Vec<_> = value.split("&").collect(); if let Prefix(v) = t[0].into() { - Dual(v, t[1].to_string()) + if t[1].is_empty() { + Prefix(v) + } else { + Dual(v, t[1].to_string()) + } } else { Postfix(t[1].to_string()) } @@ -84,6 +88,8 @@ impl From<&str> for StyleSelector { )) } else if value == "print" { Media("print".to_string()) + } else if value.ends_with(" ") { + Prefix(value.trim().to_string()) } else { Postfix(to_kebab_case(value)) } @@ -132,6 +138,7 @@ pub fn merge_selector(class_name: &str, selector: Option<&StyleSelector>) -> Str pub enum SelectorSeparator { Single, Double, + None, } impl Display for SelectorSeparator { fn fmt(&self, f: &mut Formatter) -> fmt::Result { @@ -141,6 +148,7 @@ impl Display for SelectorSeparator { match self { SelectorSeparator::Single => ":", SelectorSeparator::Double => "::", + SelectorSeparator::None => "", } ) } @@ -166,7 +174,9 @@ static DOUBLE_SEPARATOR: Lazy> = Lazy::new(|| { }); pub fn get_selector_separator(key: &str) -> SelectorSeparator { - if DOUBLE_SEPARATOR.contains(key) { + if key.starts_with(":") || key.is_empty() { + SelectorSeparator::None + } else if DOUBLE_SEPARATOR.contains(key) { SelectorSeparator::Double } else { SelectorSeparator::Single @@ -669,7 +679,7 @@ mod tests { ); assert_eq!( - StyleSelector::from("themeDark:placeholder"), + StyleSelector::from("themeDark&placeholder"), Dual( ":root[data-theme=dark]".to_string(), "placeholder".to_string() @@ -683,6 +693,11 @@ mod tests { StyleSelector::from("themeLight"), Prefix(":root[data-theme=light]".to_string()) ); + + assert_eq!( + StyleSelector::from("*[aria=disabled='true'] &:hover"), + Dual("*[aria=disabled='true']".to_string(), ":hover".to_string()) + ); } #[test] @@ -712,7 +727,7 @@ mod tests { ); assert_eq!( - merge_selector("cls", Some(&"themeDark:hover".into()),), + merge_selector("cls", Some(&"themeDark&hover".into()),), ":root[data-theme=dark] .cls:hover" ); } diff --git a/libs/extractor/src/gen_style.rs b/libs/extractor/src/gen_style.rs index 0499421b..66eecbca 100644 --- a/libs/extractor/src/gen_style.rs +++ b/libs/extractor/src/gen_style.rs @@ -77,10 +77,59 @@ fn gen_style<'a>( (None, None) => { return vec![]; } - (Some(c), None) | (None, Some(c)) => { - gen_style(ast_builder, c) - .into_iter() - .for_each(|p| properties.push(p)); + (None, Some(c)) => { + gen_style(ast_builder, c).into_iter().for_each(|p| { + if let ObjectPropertyKind::ObjectProperty(p) = p { + properties.push(ObjectPropertyKind::ObjectProperty( + ast_builder.alloc_object_property( + SPAN, + PropertyKind::Init, + p.key.clone_in(ast_builder.allocator), + Expression::ConditionalExpression( + ast_builder.alloc_conditional_expression( + SPAN, + condition.clone_in(ast_builder.allocator), + Expression::Identifier( + ast_builder + .alloc_identifier_reference(SPAN, "undefined"), + ), + p.value.clone_in(ast_builder.allocator), + ), + ), + false, + false, + false, + ), + )) + } + }); + } + (Some(c), None) => { + gen_style(ast_builder, c).into_iter().for_each(|p| { + if let ObjectPropertyKind::ObjectProperty(p) = p { + properties.push(ObjectPropertyKind::ObjectProperty( + ast_builder.alloc_object_property( + SPAN, + PropertyKind::Init, + p.key.clone_in(ast_builder.allocator), + Expression::ConditionalExpression( + ast_builder.alloc_conditional_expression( + SPAN, + condition.clone_in(ast_builder.allocator), + p.value.clone_in(ast_builder.allocator), + Expression::Identifier( + ast_builder + .alloc_identifier_reference(SPAN, "undefined"), + ), + ), + ), + false, + false, + false, + ), + )) + } + }); } (Some(c), Some(a)) => { let collect_c = gen_style(ast_builder, c); diff --git a/libs/extractor/src/lib.rs b/libs/extractor/src/lib.rs index 48c874c4..f2400358 100644 --- a/libs/extractor/src/lib.rs +++ b/libs/extractor/src/lib.rs @@ -655,6 +655,32 @@ mod tests { "test.tsx", r#"import { Box } from "@devup-ui/core"; ; +"#, + ExtractOption { + package: "@devup-ui/core".to_string(), + css_file: None + } + ) + .unwrap()); + + reset_class_map(); + assert_debug_snapshot!(extract( + "test.tsx", + r#"import { Box } from "@devup-ui/core"; +; +"#, + ExtractOption { + package: "@devup-ui/core".to_string(), + css_file: None + } + ) + .unwrap()); + + reset_class_map(); + assert_debug_snapshot!(extract( + "test.tsx", + r#"import { Box } from "@devup-ui/core"; +; "#, ExtractOption { package: "@devup-ui/core".to_string(), @@ -2117,4 +2143,59 @@ import {Button} from '@devup/ui' ) .unwrap()); } + + #[test] + #[serial] + fn custom_selector() { + reset_class_map(); + assert_debug_snapshot!(extract( + "test.js", + r#"import {Box} from '@devup-ui/core' + + "#, + ExtractOption { + package: "@devup-ui/core".to_string(), + css_file: None + } + ) + .unwrap()); + + reset_class_map(); + assert_debug_snapshot!(extract( + "test.js", + r#"import {Box} from '@devup-ui/core' + + "#, + ExtractOption { + package: "@devup-ui/core".to_string(), + css_file: None + } + ) + .unwrap()); + + reset_class_map(); + assert_debug_snapshot!(extract( + "test.js", + r#"import {Box} from '@devup-ui/core' + + "#, + ExtractOption { + package: "@devup-ui/core".to_string(), + css_file: None + } + ) + .unwrap()); + } } diff --git a/libs/extractor/src/snapshots/extractor__tests__custom_selector-2.snap b/libs/extractor/src/snapshots/extractor__tests__custom_selector-2.snap new file mode 100644 index 00000000..a33a2e21 --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__custom_selector-2.snap @@ -0,0 +1,23 @@ +--- +source: libs/extractor/src/lib.rs +expression: "extract(\"test.js\",\nr#\"import {Box} from '@devup-ui/core'\n \n \"#,\nExtractOption\n{ package: \"@devup-ui/core\".to_string(), css_file: None }).unwrap()" +--- +ExtractOutput { + styles: [ + Static( + ExtractStaticStyle { + property: "opacity", + value: "0.5", + level: 0, + selector: Some( + Dual( + "*[aria-diabled='true']", + ":hover", + ), + ), + basic: false, + }, + ), + ], + code: "import \"@devup-ui/core/devup-ui.css\";\n
;\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__custom_selector-3.snap b/libs/extractor/src/snapshots/extractor__tests__custom_selector-3.snap new file mode 100644 index 00000000..8078b34d --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__custom_selector-3.snap @@ -0,0 +1,22 @@ +--- +source: libs/extractor/src/lib.rs +expression: "extract(\"test.js\",\nr#\"import {Box} from '@devup-ui/core'\n \n \"#,\nExtractOption\n{ package: \"@devup-ui/core\".to_string(), css_file: None }).unwrap()" +--- +ExtractOutput { + styles: [ + Static( + ExtractStaticStyle { + property: "opacity", + value: "0.5", + level: 0, + selector: Some( + Prefix( + "*[aria-diabled='true']", + ), + ), + basic: false, + }, + ), + ], + code: "import \"@devup-ui/core/devup-ui.css\";\n
;\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__custom_selector.snap b/libs/extractor/src/snapshots/extractor__tests__custom_selector.snap new file mode 100644 index 00000000..a75c39a6 --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__custom_selector.snap @@ -0,0 +1,22 @@ +--- +source: libs/extractor/src/lib.rs +expression: "extract(\"test.js\",\nr#\"import {Box} from '@devup-ui/core'\n \n \"#,\nExtractOption\n{ package: \"@devup-ui/core\".to_string(), css_file: None }).unwrap()" +--- +ExtractOutput { + styles: [ + Static( + ExtractStaticStyle { + property: "opacity", + value: "0.5", + level: 0, + selector: Some( + Postfix( + "[aria-diabled='true']", + ), + ), + basic: false, + }, + ), + ], + code: "import \"@devup-ui/core/devup-ui.css\";\n
;\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__extract_conditional_style_props-7.snap b/libs/extractor/src/snapshots/extractor__tests__extract_conditional_style_props-7.snap new file mode 100644 index 00000000..c86e0ac8 --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__extract_conditional_style_props-7.snap @@ -0,0 +1,17 @@ +--- +source: libs/extractor/src/lib.rs +expression: "extract(\"test.tsx\",\nr#\"import { Box } from \"@devup-ui/core\";\n;\n\"#,\nExtractOption\n{ package: \"@devup-ui/core\".to_string(), css_file: None }).unwrap()" +--- +ExtractOutput { + styles: [ + Dynamic( + ExtractDynamicStyle { + property: "margin", + level: 0, + identifier: "`${a}px`", + selector: None, + }, + ), + ], + code: "import \"@devup-ui/core/devup-ui.css\";\n
;\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__extract_conditional_style_props-8.snap b/libs/extractor/src/snapshots/extractor__tests__extract_conditional_style_props-8.snap new file mode 100644 index 00000000..294713c5 --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__extract_conditional_style_props-8.snap @@ -0,0 +1,17 @@ +--- +source: libs/extractor/src/lib.rs +expression: "extract(\"test.tsx\",\nr#\"import { Box } from \"@devup-ui/core\";\n;\n\"#,\nExtractOption\n{ package: \"@devup-ui/core\".to_string(), css_file: None }).unwrap()" +--- +ExtractOutput { + styles: [ + Dynamic( + ExtractDynamicStyle { + property: "margin", + level: 0, + identifier: "`${b}px`", + selector: None, + }, + ), + ], + code: "import \"@devup-ui/core/devup-ui.css\";\n
;\n", +} diff --git a/libs/extractor/src/style_extractor.rs b/libs/extractor/src/style_extractor.rs index b6b0f9c9..92f8320f 100644 --- a/libs/extractor/src/style_extractor.rs +++ b/libs/extractor/src/style_extractor.rs @@ -74,10 +74,6 @@ pub fn extract_style_from_expression<'a>( level: u8, selector: Option<&str>, ) -> ExtractResult<'a> { - println!( - "extract_style_from_expression: {:?} {:?} {:?}", - selector, name, expression - ); let mut typo = false; if name.is_none() && selector.is_none() { @@ -243,6 +239,28 @@ pub fn extract_style_from_expression<'a>( // _ => ExtractResult::Remove, // }; } + if name == "selectors" { + if let Expression::ObjectExpression(obj) = expression { + let mut props = vec![]; + for p in obj.properties.iter_mut() { + if let ObjectPropertyKind::ObjectProperty(ref mut o) = p { + let name = o.key.name().unwrap().to_string(); + if let ExtractResult::ExtractStyle(mut styles) = + extract_style_from_expression( + ast_builder, + None, + &mut o.value, + level, + Some(name.as_str()), + ) + { + props.append(&mut styles); + } + } + } + return ExtractResult::ExtractStyle(props); + } + } if let Some(new_selector) = name.strip_prefix("_") { return extract_style_from_expression( @@ -252,7 +270,7 @@ pub fn extract_style_from_expression<'a>( level, Some( if let Some(selector) = selector { - format!("{}:{}", selector, new_selector) + format!("{}&{}", selector, new_selector) } else { new_selector.to_string() } @@ -262,19 +280,7 @@ pub fn extract_style_from_expression<'a>( } typo = name == "typography"; } - if let Some(value) = get_number_by_literal_expression(expression) { - name.map(|name| { - ExtractResult::ExtractStyle(vec![ExtractStyleProp::Static(Static( - ExtractStaticStyle::new( - name, - &value.to_string(), - level, - selector.map(|s| s.into()), - ), - ))]) - }) - .unwrap_or(ExtractResult::Maintain) - } else if let Some(value) = get_string_by_literal_expression(expression) { + if let Some(value) = get_string_by_literal_expression(expression) { name.map(|name| { ExtractResult::ExtractStyle(vec![ExtractStyleProp::Static(if typo { Typography(value.as_str().to_string()) diff --git a/libs/sheet/src/lib.rs b/libs/sheet/src/lib.rs index 6e88e912..40bad018 100644 --- a/libs/sheet/src/lib.rs +++ b/libs/sheet/src/lib.rs @@ -418,13 +418,24 @@ mod tests { "bg", 0, "red", - Some(&"themeDark:hover".into()), + Some(&"themeDark&hover".into()), false, ); assert_debug_snapshot!(sheet.create_css()); let mut sheet = StyleSheet::default(); - sheet.add_property("test", "bg", 0, "red", Some(&"wrong:hover".into()), false); + sheet.add_property("test", "bg", 0, "red", Some(&"wrong&hover".into()), false); + assert_debug_snapshot!(sheet.create_css()); + + let mut sheet = StyleSheet::default(); + sheet.add_property( + "test", + "bg", + 0, + "red", + Some(&"*[disabled='true'] &:hover".into()), + false, + ); assert_debug_snapshot!(sheet.create_css()); } diff --git a/libs/sheet/src/snapshots/sheet__tests__create_css-10.snap b/libs/sheet/src/snapshots/sheet__tests__create_css-10.snap new file mode 100644 index 00000000..b75cf017 --- /dev/null +++ b/libs/sheet/src/snapshots/sheet__tests__create_css-10.snap @@ -0,0 +1,5 @@ +--- +source: libs/sheet/src/lib.rs +expression: sheet.create_css() +--- +"*[disabled='true'] .test:hover{background:red}" diff --git a/libs/sheet/src/theme.rs b/libs/sheet/src/theme.rs index 7aeab5f2..b5ebc1a7 100644 --- a/libs/sheet/src/theme.rs +++ b/libs/sheet/src/theme.rs @@ -91,6 +91,7 @@ pub struct Theme { pub colors: BTreeMap, #[serde(default = "default_break_points")] pub break_points: Vec, + #[serde(default)] pub typography: BTreeMap, } diff --git a/package.json b/package.json index 87640424..a9ddb20e 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "type": "module", "scripts": { "lint": "pnpm -F @devup-ui/* lint", - "test": "cargo tarpaulin && vitest test --coverage --run && pnpm -r test", + "test": "cargo tarpaulin --out xml --out stdout && vitest test --coverage --run && pnpm -r test", "build": "pnpm -F @devup-ui/* build", "dev": "pnpm -r dev", "benchmark": "node benchmark.js" diff --git a/packages/react/src/types/props/selector/index.ts b/packages/react/src/types/props/selector/index.ts index 462c8c55..60ddcdf4 100644 --- a/packages/react/src/types/props/selector/index.ts +++ b/packages/react/src/types/props/selector/index.ts @@ -66,4 +66,6 @@ export interface DevupSelectorProps { _viewTransitionImagePair?: DevupCommonProps _viewTransitionNew?: DevupCommonProps _viewTransitionOld?: DevupCommonProps + + selectors?: Record }