diff --git a/.changeset/afraid-dolls-smell.md b/.changeset/afraid-dolls-smell.md new file mode 100644 index 00000000..03fb6923 --- /dev/null +++ b/.changeset/afraid-dolls-smell.md @@ -0,0 +1,5 @@ +--- +"@devup-ui/wasm": patch +--- + +Support double separator, Support backtick case diff --git a/.changeset/flat-stingrays-breathe.md b/.changeset/flat-stingrays-breathe.md new file mode 100644 index 00000000..e9162be9 --- /dev/null +++ b/.changeset/flat-stingrays-breathe.md @@ -0,0 +1,5 @@ +--- +"@devup-ui/react": patch +--- + +Add double selector diff --git a/libs/css/src/lib.rs b/libs/css/src/lib.rs index b529347c..992eedfe 100644 --- a/libs/css/src/lib.rs +++ b/libs/css/src/lib.rs @@ -1,7 +1,48 @@ use once_cell::sync::Lazy; -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use std::sync::Mutex; +pub enum SelectorSeparator { + Single, + Double, +} + +impl SelectorSeparator { + pub fn separator(&self) -> &str { + match self { + SelectorSeparator::Single => ":", + SelectorSeparator::Double => "::", + } + } +} + +static DOUBLE_SEPARATOR: Lazy> = Lazy::new(|| { + let mut set = HashSet::new(); + + for key in [ + "placeholder", + "before", + "after", + "highlight", + "view-transition", + "view-transition-group", + "view-transition-image-pair", + "view-transition-new", + "view-transition-old", + ] { + set.insert(key); + } + set +}); + +pub fn get_selector_separator(key: &str) -> SelectorSeparator { + if DOUBLE_SEPARATOR.contains(key) { + SelectorSeparator::Double + } else { + SelectorSeparator::Single + } +} + #[derive(Clone, Debug, PartialEq)] pub enum PropertyType { Single(String), @@ -26,56 +67,54 @@ impl From<[&str; 2]> for PropertyType { } } -static GLOBAL_STYLE_PROPERTY: Lazy>> = Lazy::new(|| { - Mutex::new({ - let mut map = HashMap::new(); +static GLOBAL_STYLE_PROPERTY: Lazy> = Lazy::new(|| { + let mut map = HashMap::new(); - for (key, value) in [ - ("bg", "background"), - ("bgAttachment", "background-attachment"), - ("bgClip", "background-clip"), - ("bgColor", "background-color"), - ("bgImage", "background-image"), - ("bgOrigin", "background-origin"), - ("bgPosition", "background-position"), - ("bgPositionX", "background-position-x"), - ("bgPositionY", "background-position-y"), - ("bgRepeat", "background-repeat"), - ("bgSize", "background-size"), - ("animationDir", "animation-direction"), - ("flexDir", "flex-direction"), - ("pos", "position"), - ("m", "margin"), - ("mt", "margin-top"), - ("mr", "margin-right"), - ("mb", "margin-bottom"), - ("ml", "margin-left"), - ("p", "padding"), - ("pt", "padding-top"), - ("pr", "padding-right"), - ("pb", "padding-bottom"), - ("pl", "padding-left"), - ("w", "width"), - ("h", "height"), - ("minW", "min-width"), - ("minH", "min-height"), - ("maxW", "max-width"), - ("maxH", "max-height"), - ] { - map.insert(key, value.into()); - } + for (key, value) in [ + ("bg", "background"), + ("bgAttachment", "background-attachment"), + ("bgClip", "background-clip"), + ("bgColor", "background-color"), + ("bgImage", "background-image"), + ("bgOrigin", "background-origin"), + ("bgPosition", "background-position"), + ("bgPositionX", "background-position-x"), + ("bgPositionY", "background-position-y"), + ("bgRepeat", "background-repeat"), + ("bgSize", "background-size"), + ("animationDir", "animation-direction"), + ("flexDir", "flex-direction"), + ("pos", "position"), + ("m", "margin"), + ("mt", "margin-top"), + ("mr", "margin-right"), + ("mb", "margin-bottom"), + ("ml", "margin-left"), + ("p", "padding"), + ("pt", "padding-top"), + ("pr", "padding-right"), + ("pb", "padding-bottom"), + ("pl", "padding-left"), + ("w", "width"), + ("h", "height"), + ("minW", "min-width"), + ("minH", "min-height"), + ("maxW", "max-width"), + ("maxH", "max-height"), + ] { + map.insert(key, value.into()); + } - for (key, value) in [ - ("mx", ["margin-left", "margin-right"]), - ("my", ["margin-top", "margin-bottom"]), - ("px", ["padding-left", "padding-right"]), - ("py", ["padding-top", "padding-bottom"]), - ("boxSize", ["width", "height"]), - ] { - map.insert(key, value.into()); - } - map - }) + for (key, value) in [ + ("mx", ["margin-left", "margin-right"]), + ("my", ["margin-top", "margin-bottom"]), + ("px", ["padding-left", "padding-right"]), + ("py", ["padding-top", "padding-bottom"]), + ("boxSize", ["width", "height"]), + ] { + map.insert(key, value.into()); + } + map }); static GLOBAL_CLASS_MAP: Lazy>> = @@ -107,8 +146,6 @@ pub fn to_kebab_case(value: &str) -> String { pub fn convert_property(property: &str) -> PropertyType { GLOBAL_STYLE_PROPERTY - .lock() - .unwrap() .get(property) .cloned() .unwrap_or_else(|| to_kebab_case(property).into()) diff --git a/libs/extractor/src/lib.rs b/libs/extractor/src/lib.rs index 3f27ef3c..6f963db2 100644 --- a/libs/extractor/src/lib.rs +++ b/libs/extractor/src/lib.rs @@ -1115,6 +1115,30 @@ export { } ) .unwrap()); + + reset_class_map(); + assert_debug_snapshot!(extract( + "test.js", + r#"import {Flex} from '@devup-ui/core' + + "#, + ExtractOption { + package: "@devup-ui/core".to_string(), + css_file: None + } + ) + .unwrap()); + assert_debug_snapshot!(extract( + "test.js", + r#"import {Flex} from '@devup-ui/core' + + "#, + ExtractOption { + package: "@devup-ui/core".to_string(), + css_file: None + } + ) + .unwrap()); } #[test] @@ -1138,4 +1162,34 @@ PROCESS_DATA.map(({ id, title, content }, idx) => ( ) .unwrap()); } + + #[test] + #[serial] + fn backtick_prop() { + 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__backtick_prop-2.snap b/libs/extractor/src/snapshots/extractor__tests__backtick_prop-2.snap new file mode 100644 index 00000000..8f7f3c60 --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__backtick_prop-2.snap @@ -0,0 +1,17 @@ +--- +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: [ + Dynamic( + ExtractDynamicStyle { + property: "bg", + level: 0, + identifier: "`${variable}`", + selector: None, + }, + ), + ], + code: "import \"@devup-ui/core/devup-ui.css\";\n
;\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__backtick_prop.snap b/libs/extractor/src/snapshots/extractor__tests__backtick_prop.snap new file mode 100644 index 00000000..ea494c11 --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__backtick_prop.snap @@ -0,0 +1,18 @@ +--- +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: "bg", + value: "black", + level: 0, + selector: None, + basic: false, + }, + ), + ], + code: "import \"@devup-ui/core/devup-ui.css\";\n
;\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__props_direct_select-10.snap b/libs/extractor/src/snapshots/extractor__tests__props_direct_select-10.snap new file mode 100644 index 00000000..afc34e56 --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__props_direct_select-10.snap @@ -0,0 +1,26 @@ +--- +source: libs/extractor/src/lib.rs +expression: "extract(\"test.js\",\nr#\"import {Flex} from '@devup-ui/core'\n \n \"#,\nExtractOption\n{ package: \"@devup-ui/core\".to_string(), css_file: None }).unwrap()" +--- +ExtractOutput { + styles: [ + Static( + ExtractStaticStyle { + property: "display", + value: "flex", + level: 0, + selector: None, + basic: true, + }, + ), + Dynamic( + ExtractDynamicStyle { + property: "bg", + level: 0, + identifier: "[`var(--red)`, `${variable}`][idx]", + selector: None, + }, + ), + ], + code: "import \"@devup-ui/core/devup-ui.css\";\n
;\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__props_direct_select-9.snap b/libs/extractor/src/snapshots/extractor__tests__props_direct_select-9.snap new file mode 100644 index 00000000..47ff1b3f --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__props_direct_select-9.snap @@ -0,0 +1,26 @@ +--- +source: libs/extractor/src/lib.rs +expression: "extract(\"test.js\",\nr#\"import {Flex} from '@devup-ui/core'\n \n \"#,\nExtractOption\n{ package: \"@devup-ui/core\".to_string(), css_file: None }).unwrap()" +--- +ExtractOutput { + styles: [ + Static( + ExtractStaticStyle { + property: "display", + value: "flex", + level: 0, + selector: None, + basic: true, + }, + ), + Dynamic( + ExtractDynamicStyle { + property: "bg", + level: 0, + identifier: "[\"var(--red)\", \"var(--blue)\"][idx]", + 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 072a04af..6078fac1 100644 --- a/libs/extractor/src/style_extractor.rs +++ b/libs/extractor/src/style_extractor.rs @@ -1,14 +1,12 @@ -use crate::utils::is_special_property; +use crate::utils::{expression_to_code, is_special_property}; use crate::ExtractStyleProp; use oxc_allocator::CloneIn; -use oxc_ast::ast::{Expression, JSXAttributeValue, ObjectPropertyKind, PropertyKey, Statement}; +use oxc_ast::ast::{Expression, JSXAttributeValue, ObjectPropertyKind, PropertyKey}; use crate::extract_style::ExtractStyleValue::{Dynamic, Static}; use crate::extract_style::{ExtractDynamicStyle, ExtractStaticStyle, ExtractStyleValue}; use oxc_ast::AstBuilder; -use oxc_codegen::Codegen; -use oxc_parser::Parser; -use oxc_span::{SourceType, SPAN}; +use oxc_span::SPAN; use oxc_syntax::operator::{BinaryOperator, LogicalOperator}; const IGNORED_IDENTIFIERS: [&str; 3] = ["undefined", "NaN", "Infinity"]; @@ -106,56 +104,111 @@ pub fn extract_style_from_expression<'a>( ); } } - println!("expression: {:?}", expression); match expression { - Expression::ComputedMemberExpression(mem) => match &mem.object { - Expression::ArrayExpression(array) => { - match &mem.expression { - Expression::NumericLiteral(v) => { - if array.elements.len() < v.value as usize { - // wrong indexing case - ExtractResult::Remove - } else { - extract_style_from_expression( - ast_builder, - name, - array.elements[v.value as usize] - .clone_in(ast_builder.allocator) - .to_expression_mut(), - level, - selector, - ) + Expression::ComputedMemberExpression(mem) => { + let mem_expression = &mem.expression.clone_in(ast_builder.allocator); + match &mut mem.object { + Expression::ArrayExpression(array) => { + for element in array.elements.iter_mut() { + if let Expression::StringLiteral(str) = element.to_expression_mut() { + if let Some(rest) = str.value.strip_prefix("$") { + str.value = ast_builder.atom(&format!("var(--{})", rest)); + } + } else if let Expression::TemplateLiteral(tmp) = element.to_expression_mut() + { + if tmp.quasis.len() == 1 { + if let Some(rest) = tmp.quasis[0].value.raw.strip_prefix("$") { + tmp.quasis[0].value.raw = + ast_builder.atom(&format!("var(--{})", rest)); + } + } } } - // wrong indexing case - Expression::UnaryExpression(unary) => { - if let Expression::NumericLiteral(_) = &unary.argument { - ExtractResult::Remove - } else { + + match mem_expression { + Expression::NumericLiteral(v) => { + if array.elements.len() < v.value as usize { + // wrong indexing case + ExtractResult::Remove + } else { + extract_style_from_expression( + ast_builder, + name, + array.elements[v.value as usize] + .clone_in(ast_builder.allocator) + .to_expression_mut(), + level, + selector, + ) + } + } + // wrong indexing case + Expression::UnaryExpression(unary) => { + if let Expression::NumericLiteral(_) = &unary.argument { + ExtractResult::Remove + } else { + ExtractResult::Maintain + } + } + Expression::Identifier(_) => { + if let Some(name) = name { + return ExtractResult::ExtractStyle(vec![ + ExtractStyleProp::Static(Dynamic(ExtractDynamicStyle::new( + name, + level, + expression_to_code(expression).as_str(), + selector, + ))), + ]); + } ExtractResult::Maintain } + _ => ExtractResult::Maintain, + } + } + Expression::ObjectExpression(obj) => match mem_expression { + Expression::StringLiteral(str) => { + let key = str.value.as_str(); + for p in obj.properties.iter() { + match p { + ObjectPropertyKind::ObjectProperty(o) => { + if let PropertyKey::StaticIdentifier(ident) = &o.key { + if ident.name == key { + return extract_style_from_expression( + ast_builder, + name, + &mut o.value.clone_in(ast_builder.allocator), + level, + selector, + ); + } + } + } + ObjectPropertyKind::SpreadProperty(_) => { + if let Some(name) = name { + return ExtractResult::ExtractStyle(vec![ + ExtractStyleProp::Static(Dynamic( + ExtractDynamicStyle::new( + name, + level, + expression_to_code(expression).as_str(), + selector, + ), + )), + ]); + } + } + } + } + ExtractResult::Remove } Expression::Identifier(_) => { if let Some(name) = name { - let source = ""; - let mut parsed = - Parser::new(ast_builder.allocator, source, SourceType::d_ts()) - .parse(); - parsed.program.body.insert( - 0, - Statement::ExpressionStatement( - ast_builder.alloc_expression_statement( - SPAN, - expression.clone_in(ast_builder.allocator), - ), - ), - ); - let code = Codegen::new().build(&parsed.program).code; return ExtractResult::ExtractStyle(vec![ExtractStyleProp::Static( Dynamic(ExtractDynamicStyle::new( name, level, - code[0..code.len() - 2].to_string().as_str(), + expression_to_code(expression).as_str(), selector, )), )]); @@ -163,89 +216,10 @@ pub fn extract_style_from_expression<'a>( ExtractResult::Maintain } _ => ExtractResult::Maintain, - } - } - Expression::ObjectExpression(obj) => match &mem.expression { - Expression::StringLiteral(str) => { - let key = str.value.as_str(); - for p in obj.properties.iter() { - match p { - ObjectPropertyKind::ObjectProperty(o) => { - if let PropertyKey::StaticIdentifier(ident) = &o.key { - if ident.name == key { - return extract_style_from_expression( - ast_builder, - name, - &mut o.value.clone_in(ast_builder.allocator), - level, - selector, - ); - } - } - } - ObjectPropertyKind::SpreadProperty(_) => { - if let Some(name) = name { - let source = ""; - let mut parsed = Parser::new( - ast_builder.allocator, - source, - SourceType::d_ts(), - ) - .parse(); - parsed.program.body.insert( - 0, - Statement::ExpressionStatement( - ast_builder.alloc_expression_statement( - SPAN, - expression.clone_in(ast_builder.allocator), - ), - ), - ); - let code = Codegen::new().build(&parsed.program).code; - return ExtractResult::ExtractStyle(vec![ - ExtractStyleProp::Static(Dynamic( - ExtractDynamicStyle::new( - name, - level, - code[0..code.len() - 2].to_string().as_str(), - selector, - ), - )), - ]); - } - } - } - } - ExtractResult::Remove - } - Expression::Identifier(_) => { - if let Some(name) = name { - let source = ""; - let mut parsed = - Parser::new(ast_builder.allocator, source, SourceType::d_ts()).parse(); - parsed.program.body.insert( - 0, - Statement::ExpressionStatement(ast_builder.alloc_expression_statement( - SPAN, - expression.clone_in(ast_builder.allocator), - )), - ); - let code = Codegen::new().build(&parsed.program).code; - return ExtractResult::ExtractStyle(vec![ExtractStyleProp::Static( - Dynamic(ExtractDynamicStyle::new( - name, - level, - code[0..code.len() - 2].to_string().as_str(), - selector, - )), - )]); - } - ExtractResult::Maintain - } + }, _ => ExtractResult::Maintain, - }, - _ => ExtractResult::Maintain, - }, + } + } Expression::NumericLiteral(v) => { if let Some(name) = name { ExtractResult::ExtractStyle(vec![ExtractStyleProp::Static(Static( @@ -255,6 +229,31 @@ pub fn extract_style_from_expression<'a>( ExtractResult::Maintain } } + Expression::TemplateLiteral(tmp) => { + if let Some(name) = name { + if tmp.quasis.len() == 1 { + ExtractResult::ExtractStyle(vec![ExtractStyleProp::Static(Static( + ExtractStaticStyle::new( + name, + tmp.quasis[0].value.raw.as_str(), + level, + selector, + ), + ))]) + } else { + ExtractResult::ExtractStyle(vec![ExtractStyleProp::Static(Dynamic( + ExtractDynamicStyle::new( + name, + level, + expression_to_code(expression).as_str(), + selector, + ), + ))]) + } + } else { + ExtractResult::Maintain + } + } Expression::StringLiteral(v) => { if let Some(name) = name { ExtractResult::ExtractStyle(vec![ExtractStyleProp::Static(Static( diff --git a/libs/extractor/src/utils.rs b/libs/extractor/src/utils.rs index b7b70088..63012b5e 100644 --- a/libs/extractor/src/utils.rs +++ b/libs/extractor/src/utils.rs @@ -1,4 +1,9 @@ use once_cell::sync::Lazy; +use oxc_allocator::{Allocator, CloneIn}; +use oxc_ast::ast::{Expression, Statement}; +use oxc_codegen::Codegen; +use oxc_parser::Parser; +use oxc_span::{SourceType, SPAN}; use std::collections::HashSet; /// Convert a value to a pixel value @@ -11,6 +16,21 @@ pub fn convert_value(value: &str) -> String { value } +pub fn expression_to_code(expression: &Expression) -> String { + let source = ""; + let allocator = Allocator::default(); + let ast_builder = oxc_ast::AstBuilder::new(&allocator); + let mut parsed = Parser::new(&allocator, source, SourceType::d_ts()).parse(); + parsed.program.body.insert( + 0, + Statement::ExpressionStatement( + ast_builder.alloc_expression_statement(SPAN, expression.clone_in(&allocator)), + ), + ); + let code = Codegen::new().build(&parsed.program).code; + code[0..code.len() - 2].to_string() +} + static SPECIAL_PROPERTIES: Lazy> = Lazy::new(|| { let mut set = HashSet::<&str>::new(); for prop in [ diff --git a/libs/sheet/src/lib.rs b/libs/sheet/src/lib.rs index 9d22f368..080d5560 100644 --- a/libs/sheet/src/lib.rs +++ b/libs/sheet/src/lib.rs @@ -1,7 +1,7 @@ pub mod theme; use crate::theme::Theme; -use css::{convert_property, to_kebab_case, PropertyType}; +use css::{convert_property, get_selector_separator, to_kebab_case, PropertyType}; use std::cmp::Ordering::{Greater, Less}; use std::collections::{BTreeMap, HashSet}; @@ -21,7 +21,12 @@ pub struct StyleSheetProperty { impl ExtractStyle for StyleSheetProperty { fn extract(&self) -> String { let selector = if let Some(selector) = &self.selector { - format!(":{}", to_kebab_case(selector)) + let selector = to_kebab_case(selector); + format!( + "{}{}", + get_selector_separator(&selector).separator(), + selector + ) } else { String::new() }; diff --git a/packages/react/src/types/props/selector/index.ts b/packages/react/src/types/props/selector/index.ts index 99ff6091..cc219cc6 100644 --- a/packages/react/src/types/props/selector/index.ts +++ b/packages/react/src/types/props/selector/index.ts @@ -20,5 +20,16 @@ export interface DevupSelectorProps { _link?: DevupCommonProps _onlyChild?: DevupCommonProps _optional?: DevupCommonProps + _readOnly?: DevupCommonProps + + // double separator _placeholder?: DevupCommonProps + _before?: DevupCommonProps + _after?: DevupCommonProps + _highlight?: DevupCommonProps + _viewTransition?: DevupCommonProps + _viewTransitionGroup?: DevupCommonProps + _viewTransitionImagePair?: DevupCommonProps + _viewTransitionNew?: DevupCommonProps + _viewTransitionOld?: DevupCommonProps }