diff --git a/.changepacks/changepack_log_stylex_attrs_style.json b/.changepacks/changepack_log_stylex_attrs_style.json new file mode 100644 index 000000000..7ccca57d3 --- /dev/null +++ b/.changepacks/changepack_log_stylex_attrs_style.json @@ -0,0 +1,8 @@ +{ + "changes": { + "packages/react/package.json": "Patch", + "bindings/devup-ui-wasm/package.json": "Patch" + }, + "note": "stylex.attrs() gives the dynamic CSS variables it sets as `style` attribute text (`\"--a:\" + value`), as StyleX does for spreading onto non-React targets, instead of a style object; stylex.props() keeps the object React takes. The attrs() return type declares `style` as a string", + "date": "2026-10-01T00:00:00.000Z" +} diff --git a/libs/extractor/src/lib.rs b/libs/extractor/src/lib.rs index a5853d5b5..8d63e0ffe 100644 --- a/libs/extractor/src/lib.rs +++ b/libs/extractor/src/lib.rs @@ -20814,6 +20814,25 @@ const e =
;" ))); } + // `attrs()` gives the `style` attribute as text, as StyleX does, while + // `props()` keeps the object React takes + #[test] + #[serial] + fn test_stylex_attrs_style_is_text() { + let code = extract_tsx( + r"import * as stylex from '@stylexjs/stylex'; +const s = stylex.create({ box: (w, h) => ({ width: w, height: h }) }); +export const a = stylex.attrs(s.box(w, h)); +export const p = stylex.props(s.box(w, h));", + ) + .code; + assert!( + code.contains("style: \"--a:\" + ((v) => typeof v === \"number\" ? v + \"px\" : v)(w) + \";--b:\" + ((v) => typeof v === \"number\" ? v + \"px\" : v)(h)"), + "{code}" + ); + assert!(code.contains("style: {\n\t\t\"--a\":"), "{code}"); + } + #[test] #[serial] fn test_emotion_global_with_spread_attribute() { diff --git a/libs/extractor/src/visit.rs b/libs/extractor/src/visit.rs index 93846123e..39e65e43c 100644 --- a/libs/extractor/src/visit.rs +++ b/libs/extractor/src/visit.rs @@ -637,6 +637,47 @@ impl<'a> DevupVisitor<'a> { self.stylex_function(callee).as_ref() == Some(function) } + /// `"--a:" + a + ";--b:" + b`: the variables `props` sets, as the text of a + /// `style` attribute + fn style_text(&self, props: Vec>) -> Expression<'a> { + let mut text: Option> = None; + let props = props.into_iter().filter_map(|prop| { + if let ObjectPropertyKind::ObjectProperty(prop) = prop { + Some(prop.unbox()) + } else { + None + } + }); + for (index, prop) in props.enumerate() { + let name = get_string_by_property_key(&prop.key).unwrap_or_default(); + let prefix = format!("{}{name}:", if index == 0 { "" } else { ";" }); + let head = Expression::new_string_literal( + SPAN, + Str::from_in(prefix.as_str(), self.ast.allocator()), + None, + &self.ast, + ); + let head = match text { + Some(text) => Expression::new_binary_expression( + SPAN, + text, + BinaryOperator::Addition, + head, + &self.ast, + ), + None => head, + }; + text = Some(Expression::new_binary_expression( + SPAN, + head, + BinaryOperator::Addition, + Expression::new_parenthesized_expression(SPAN, prop.value, &self.ast), + &self.ast, + )); + } + text.unwrap_or_else(|| Expression::new_string_literal(SPAN, "", None, &self.ast)) + } + /// Resolve `stylex.props()` arguments to className expressions and style properties. /// Returns (`class_exprs`, `style_props`) where `style_props` are CSS variable assignments /// from dynamic namespace calls like `styles.bar(h)`. @@ -1565,11 +1606,16 @@ impl<'a> VisitMut<'a> for DevupVisitor<'a> { // Add style property for dynamic CSS variables if !style_props.is_empty() { - let style_obj = Expression::new_object_expression( - SPAN, - oxc_allocator::Vec::from_iter_in(style_props, &self.ast), - &self.ast, - ); + // `attrs()` gives the `style` attribute as text, `props()` an object + let style_obj = if class_attribute == "class" { + self.style_text(style_props) + } else { + Expression::new_object_expression( + SPAN, + oxc_allocator::Vec::from_iter_in(style_props, &self.ast), + &self.ast, + ) + }; props.push(ObjectPropertyKind::new_object_property( SPAN, PropertyKind::Init, diff --git a/packages/react/src/utils/stylex.ts b/packages/react/src/utils/stylex.ts index e46905f6c..e0f051acd 100644 --- a/packages/react/src/utils/stylex.ts +++ b/packages/react/src/utils/stylex.ts @@ -32,7 +32,7 @@ export function props( export function attrs( ..._styles: ReadonlyArray -): { class?: string; style?: Record } { +): { class?: string; style?: string } { throw new Error('Cannot run on the runtime') }