stylex_transform/shared/utils/core/
define_vars_utils.rs1use std::rc::Rc;
2
3use indexmap::IndexMap;
4use stylex_macros::stylex_panic;
5use swc_core::ecma::ast::{Expr, Lit};
6
7use crate::shared::enums::data_structures::flat_compiled_styles_value::FlatCompiledStylesValue;
8use crate::shared::structures::types::{
9 ClassPathsInNamespace, FlatCompiledStyles, InjectableStylesMap,
10};
11use crate::shared::utils::ast::convertors::{convert_key_value_to_str, convert_lit_to_string};
12use crate::shared::utils::common::{
13 create_hash, get_key_values_from_object, round_to_decimal_places,
14};
15use stylex_constants::constants::common::SPLIT_TOKEN;
16use stylex_constants::constants::messages::{EXPECTED_CSS_VAR, VALUES_MUST_BE_OBJECT};
17use stylex_enums::value_with_default::ValueWithDefault;
18use stylex_types::structures::injectable_style::InjectableStyle;
19
20pub(crate) fn construct_css_variables_string(
21 variables: &FlatCompiledStyles,
22 theme_name_hash: &String,
23 typed_variables: &mut FlatCompiledStyles,
24) -> InjectableStylesMap {
25 let mut rules_by_at_rule = IndexMap::new();
26
27 for (key, value) in variables.iter() {
28 collect_vars_by_at_rules(key, value, &mut rules_by_at_rule, &[], typed_variables);
29 }
30
31 let mut result: InjectableStylesMap = IndexMap::new();
32
33 for (at_rule, value) in rules_by_at_rule.iter() {
34 let suffix = if at_rule == "default" {
35 String::default()
36 } else {
37 format!("-{}", create_hash(at_rule))
38 };
39
40 let selector = format!(":root, .{theme_name_hash}");
41
42 let mut ltr = format!("{selector}{{{}}}", value.join(""));
43
44 if at_rule != "default" {
45 ltr = wrap_with_at_rules(ltr.as_str(), at_rule);
46 }
47
48 result.insert(
49 format!("{}{}", theme_name_hash, suffix),
50 InjectableStyle::regular(
51 ltr,
52 Some(round_to_decimal_places(
54 priority_for_at_rule(at_rule) / 10.0,
55 1,
56 )),
57 ),
58 );
59 }
60
61 result
62}
63
64pub(crate) fn collect_vars_by_at_rules(
65 key: &String,
66 value: &FlatCompiledStylesValue,
67 collection: &mut ClassPathsInNamespace,
68 at_rules: &[String],
69 typed_variables: &mut FlatCompiledStyles,
70) {
71 let Some((hash_name, value, css_type)) = value.as_tuple() else {
72 stylex_panic!("{}", VALUES_MUST_BE_OBJECT)
73 };
74
75 if let Some(css_type) = css_type {
76 let values = match css_type.value.as_map() {
77 Some(v) => v,
78 None => stylex_panic!("Value must be a map"),
79 };
80
81 let initial_value = get_nitial_value_of_css_type(values);
82
83 typed_variables.insert(
84 hash_name.clone(),
85 Rc::new(FlatCompiledStylesValue::CSSType(
86 hash_name.clone(),
87 css_type.syntax,
88 initial_value.clone(),
89 )),
90 );
91 }
92
93 match value {
94 Expr::Array(_) => stylex_panic!(
95 "Array values are not supported in defineVars(). Use a string, number, or nested object."
96 ),
97 Expr::Lit(lit) => {
98 if let Lit::Null(_) = lit {
99 return;
100 }
101
102 let val = match convert_lit_to_string(lit) {
103 Some(v) => v,
104 None => stylex_panic!("{}", EXPECTED_CSS_VAR),
105 };
106
107 let key = if at_rules.is_empty() {
108 "default".to_string()
109 } else {
110 let mut keys = at_rules.to_vec();
111 keys.sort();
112 keys.join(SPLIT_TOKEN)
113 };
114
115 collection
116 .entry(key)
117 .or_default()
118 .push(format!("--{}:{};", hash_name, val));
119 },
120 Expr::Object(obj) => {
121 let key_values = get_key_values_from_object(obj);
122
123 if !key_values.iter().any(|key_value| {
124 let key = convert_key_value_to_str(key_value);
125
126 key == "default"
127 }) {
128 stylex_panic!(r#"Default value is not defined for "{}" variable."#, key);
129 }
130
131 for key_value in key_values.iter() {
132 let at_rule = convert_key_value_to_str(key_value);
133
134 let extended_at_rules = if at_rule == "default" {
135 at_rules.to_vec()
136 } else {
137 let mut new_at_rule = at_rules.to_vec();
138 new_at_rule.push(at_rule.clone());
139 new_at_rule
140 };
141
142 let value = key_value.value.clone();
143
144 collect_vars_by_at_rules(
145 &at_rule,
146 &FlatCompiledStylesValue::Tuple(hash_name.clone(), value, None),
147 collection,
148 &extended_at_rules,
149 typed_variables,
150 );
151 }
152 },
153 _ => {},
154 }
155}
156
157fn get_nitial_value_of_css_type(values: &IndexMap<String, ValueWithDefault>) -> String {
158 values
159 .get("default")
160 .map(|value| match value {
161 ValueWithDefault::Number(num) => num.to_string(),
162 ValueWithDefault::String(strng) => strng.clone(),
163 ValueWithDefault::Map(map) => get_nitial_value_of_css_type(map),
164 })
165 .unwrap_or_else(|| stylex_panic!("CSS type requires a default value but none was provided."))
166}
167
168pub(crate) fn wrap_with_at_rules(ltr: &str, at_rule: &str) -> String {
169 at_rule
170 .split(SPLIT_TOKEN)
171 .fold(ltr.to_string(), |acc, at_rule| {
172 format!("{}{{{}}}", at_rule, acc)
173 })
174}
175
176pub(crate) fn priority_for_at_rule(at_rule: &str) -> f64 {
177 if at_rule == "default" {
178 1.0
179 } else {
180 1.0 + at_rule.split(SPLIT_TOKEN).count() as f64
181 }
182}