1#[derive(Debug, Clone)]
21pub(crate) struct Scope {
22 pub(crate) condition: Option<String>,
23 pub(crate) stylesets: Vec<ScopeContent>,
24}
25
26impl ToCss for Scope {
27 fn to_css(&self, class_name: String) -> String {
28 let stylesets = self.stylesets.clone();
29 let stylesets_css = stylesets
30 .into_iter()
31 .map(|styleset| match styleset {
32 ScopeContent::Block(block) => block.to_css(class_name.clone()),
33 ScopeContent::Rule(rule) => rule.to_css(class_name.clone()),
34 })
36 .fold(String::new(), |acc, css_part| {
37 format!("{}{}\n", acc, css_part)
38 });
39 match &self.condition {
40 Some(condition) => format!("{} {{\n{}}}", condition, stylesets_css),
41 None => stylesets_css.trim().to_string(),
42 }
43 }
44}
45
46#[derive(Debug, Clone)]
48pub(crate) enum ScopeContent {
49 Block(Block),
50 Rule(Rule),
51 }
54
55#[derive(Debug, Clone)]
65pub(crate) struct Block {
66 pub(crate) condition: Option<String>,
67 pub(crate) style_attributes: Vec<StyleAttribute>,
68}
69
70impl ToCss for Block {
71 fn to_css(&self, class_name: String) -> String {
72 let condition = match &self.condition {
73 Some(condition) => format!(" {}", condition),
74 None => String::new(),
75 };
76 let style_property_css = self
77 .style_attributes
78 .clone()
79 .into_iter()
80 .map(|style_property| style_property.to_css(class_name.clone()))
81 .fold(String::new(), |acc, css_part| {
82 format!("{}\n{}", acc, css_part)
83 });
84 if condition.contains('&') {
85 format!(
86 "{} {{{}\n}}",
87 condition.replace('&', format!(".{}", class_name).as_str()),
88 style_property_css
89 )
90 } else {
91 format!(".{}{} {{{}\n}}", class_name, condition, style_property_css)
92 }
93 }
94}
95
96#[derive(Debug, Clone)]
100pub(crate) struct StyleAttribute {
101 pub(crate) key: String,
102 pub(crate) value: String,
103}
104
105impl ToCss for StyleAttribute {
106 fn to_css(&self, _: String) -> String {
107 format!("{}: {};", self.key, self.value)
108 }
109}
110
111#[derive(Debug, Clone)]
115pub(crate) struct Rule {
116 pub(crate) condition: String,
117 pub(crate) content: Vec<RuleContent>,
118}
119
120impl ToCss for Rule {
121 fn to_css(&self, class_name: String) -> String {
122 format!(
123 "{} {{\n{}\n}}",
124 self.condition,
125 self.content
126 .iter()
127 .map(|rc| rc.to_css(class_name.clone()))
128 .collect::<Vec<String>>()
129 .concat()
130 )
131 }
132}
133
134#[derive(Debug, Clone)]
136pub(crate) enum RuleContent {
137 String(String),
138 CurlyBraces(Vec<RuleContent>),
139}
140
141impl ToCss for RuleContent {
142 fn to_css(&self, class_name: String) -> String {
143 match self {
144 RuleContent::String(s) => s.to_string(),
145 RuleContent::CurlyBraces(content) => format!(
146 "{{{}}}",
147 content
148 .iter()
149 .map(|rc| rc.to_css(class_name.clone()))
150 .collect::<Vec<String>>()
151 .concat()
152 ),
153 }
154 }
155}
156
157pub trait ToCss {
160 fn to_css(&self, class_name: String) -> String;
161}
162
163#[cfg(all(test, target_arch = "wasm32"))]
164mod tests {
165 use super::{Block, Rule, Scope, ScopeContent, StyleAttribute, ToCss};
166 use wasm_bindgen_test::*;
167
168 #[wasm_bindgen_test]
169 fn test_scope_building_without_condition() {
170 let test_block = Scope {
171 condition: None,
172 stylesets: vec![
173 ScopeContent::Block(Block {
174 condition: None,
175 style_attributes: vec![StyleAttribute {
176 key: String::from("width"),
177 value: String::from("100vw"),
178 }],
179 }),
180 ScopeContent::Block(Block {
181 condition: Some(String::from(".inner")),
182 style_attributes: vec![StyleAttribute {
183 key: String::from("background-color"),
184 value: String::from("red"),
185 }],
186 }),
187 ScopeContent::Rule(Rule {
188 condition: String::from("@keyframes move"),
189 content: String::from(
190 r#"from {
191width: 100px;
192}
193to {
194width: 200px;
195}"#,
196 ),
197 }),
198 ],
199 };
200 assert_eq!(
201 test_block.to_css(String::from("test")),
202 r#".test {
203width: 100vw;
204}
205.test .inner {
206background-color: red;
207}
208@keyframes move {
209from {
210width: 100px;
211}
212to {
213width: 200px;
214}
215}"#
216 );
217 }
218
219 #[wasm_bindgen_test]
220 fn test_scope_building_with_condition() {
221 let test_block = Scope {
222 condition: Some(String::from("@media only screen and (min-width: 1000px)")),
223 stylesets: vec![
224 ScopeContent::Block(Block {
225 condition: None,
226 style_attributes: vec![StyleAttribute {
227 key: String::from("width"),
228 value: String::from("100vw"),
229 }],
230 }),
231 ScopeContent::Block(Block {
232 condition: Some(String::from(".inner")),
233 style_attributes: vec![StyleAttribute {
234 key: String::from("background-color"),
235 value: String::from("red"),
236 }],
237 }),
238 ScopeContent::Rule(Rule {
239 condition: String::from("@keyframes move"),
240 content: String::from(
241 r#"from {
242width: 100px;
243}
244to {
245width: 200px;
246}"#,
247 ),
248 }),
249 ],
250 };
251 assert_eq!(
252 test_block.to_css(String::from("test")),
253 r#"@media only screen and (min-width: 1000px) {
254.test {
255width: 100vw;
256}
257.test .inner {
258background-color: red;
259}
260@keyframes move {
261from {
262width: 100px;
263}
264to {
265width: 200px;
266}
267}
268}"#
269 );
270 }
271}