1use crate::classes::ClassBuilder;
7use serde::{Deserialize, Serialize};
8use std::fmt;
9
10#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
12pub enum NestingSelector {
13 DirectChild,
15 Descendant,
17 AdjacentSibling,
19 GeneralSibling,
21 Custom(String),
23}
24
25impl fmt::Display for NestingSelector {
26 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
27 match self {
28 NestingSelector::DirectChild => write!(f, ">"),
29 NestingSelector::Descendant => write!(f, " "),
30 NestingSelector::AdjacentSibling => write!(f, "+"),
31 NestingSelector::GeneralSibling => write!(f, "~"),
32 NestingSelector::Custom(selector) => write!(f, "{}", selector),
33 }
34 }
35}
36
37impl NestingSelector {
38 pub fn to_class_name(&self) -> String {
40 match self {
41 NestingSelector::DirectChild => "nest-child".to_string(),
42 NestingSelector::Descendant => "nest-descendant".to_string(),
43 NestingSelector::AdjacentSibling => "nest-adjacent".to_string(),
44 NestingSelector::GeneralSibling => "nest-sibling".to_string(),
45 NestingSelector::Custom(selector) => format!(
46 "nest-{}",
47 selector
48 .replace(" ", "-")
49 .replace(">", "child")
50 .replace("+", "adjacent")
51 .replace("~", "sibling")
52 ),
53 }
54 }
55
56 pub fn to_css_value(&self) -> String {
58 self.to_string()
59 }
60}
61
62#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
64pub enum NestingPseudoClass {
65 Hover,
67 Focus,
69 Active,
71 Visited,
73 Link,
75 FirstChild,
77 LastChild,
79 NthChild(String),
81 Custom(String),
83}
84
85impl fmt::Display for NestingPseudoClass {
86 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
87 match self {
88 NestingPseudoClass::Hover => write!(f, ":hover"),
89 NestingPseudoClass::Focus => write!(f, ":focus"),
90 NestingPseudoClass::Active => write!(f, ":active"),
91 NestingPseudoClass::Visited => write!(f, ":visited"),
92 NestingPseudoClass::Link => write!(f, ":link"),
93 NestingPseudoClass::FirstChild => write!(f, ":first-child"),
94 NestingPseudoClass::LastChild => write!(f, ":last-child"),
95 NestingPseudoClass::NthChild(n) => write!(f, ":nth-child({})", n),
96 NestingPseudoClass::Custom(pseudo) => write!(f, ":{}", pseudo),
97 }
98 }
99}
100
101impl NestingPseudoClass {
102 pub fn to_class_name(&self) -> String {
104 match self {
105 NestingPseudoClass::Hover => "nest-hover".to_string(),
106 NestingPseudoClass::Focus => "nest-focus".to_string(),
107 NestingPseudoClass::Active => "nest-active".to_string(),
108 NestingPseudoClass::Visited => "nest-visited".to_string(),
109 NestingPseudoClass::Link => "nest-link".to_string(),
110 NestingPseudoClass::FirstChild => "nest-first-child".to_string(),
111 NestingPseudoClass::LastChild => "nest-last-child".to_string(),
112 NestingPseudoClass::NthChild(n) => {
113 format!("nest-nth-child-{}", n.replace(" ", "-"))
114 }
115 NestingPseudoClass::Custom(pseudo) => format!("nest-{}", pseudo),
116 }
117 }
118
119 pub fn to_css_value(&self) -> String {
121 self.to_string()
122 }
123}
124
125#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
127pub enum NestingMediaQuery {
128 Small,
130 Medium,
132 Large,
134 ExtraLarge,
136 Dark,
138 Light,
140 Print,
142 Screen,
144 Custom(String),
146}
147
148impl fmt::Display for NestingMediaQuery {
149 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
150 match self {
151 NestingMediaQuery::Small => write!(f, "(min-width: 640px)"),
152 NestingMediaQuery::Medium => write!(f, "(min-width: 768px)"),
153 NestingMediaQuery::Large => write!(f, "(min-width: 1024px)"),
154 NestingMediaQuery::ExtraLarge => write!(f, "(min-width: 1280px)"),
155 NestingMediaQuery::Dark => write!(f, "(prefers-color-scheme: dark)"),
156 NestingMediaQuery::Light => write!(f, "(prefers-color-scheme: light)"),
157 NestingMediaQuery::Print => write!(f, "print"),
158 NestingMediaQuery::Screen => write!(f, "screen"),
159 NestingMediaQuery::Custom(query) => write!(f, "{}", query),
160 }
161 }
162}
163
164impl NestingMediaQuery {
165 pub fn to_class_name(&self) -> String {
167 match self {
168 NestingMediaQuery::Small => "nest-sm".to_string(),
169 NestingMediaQuery::Medium => "nest-md".to_string(),
170 NestingMediaQuery::Large => "nest-lg".to_string(),
171 NestingMediaQuery::ExtraLarge => "nest-xl".to_string(),
172 NestingMediaQuery::Dark => "nest-dark".to_string(),
173 NestingMediaQuery::Light => "nest-light".to_string(),
174 NestingMediaQuery::Print => "nest-print".to_string(),
175 NestingMediaQuery::Screen => "nest-screen".to_string(),
176 NestingMediaQuery::Custom(query) => format!(
177 "nest-{}",
178 query
179 .replace("(", "")
180 .replace(")", "")
181 .replace(" ", "-")
182 .replace(":", "-")
183 .replace("--", "-")
184 ),
185 }
186 }
187
188 pub fn to_css_value(&self) -> String {
190 self.to_string()
191 }
192}
193
194pub trait CssNestingUtilities {
196 fn nesting_selector(self, selector: NestingSelector) -> Self;
198 fn nesting_pseudo_class(self, pseudo_class: NestingPseudoClass) -> Self;
200 fn nesting_media_query(self, media_query: NestingMediaQuery) -> Self;
202 fn nested_class(self, selector: NestingSelector, class: &str) -> Self;
204 fn nested_pseudo_class(self, pseudo_class: NestingPseudoClass, class: &str) -> Self;
206 fn nested_media_query(self, media_query: NestingMediaQuery, class: &str) -> Self;
208}
209
210impl CssNestingUtilities for ClassBuilder {
211 fn nesting_selector(self, selector: NestingSelector) -> Self {
212 self.class(selector.to_class_name())
213 }
214
215 fn nesting_pseudo_class(self, pseudo_class: NestingPseudoClass) -> Self {
216 self.class(pseudo_class.to_class_name())
217 }
218
219 fn nesting_media_query(self, media_query: NestingMediaQuery) -> Self {
220 self.class(media_query.to_class_name())
221 }
222
223 fn nested_class(self, selector: NestingSelector, class: &str) -> Self {
224 let nested_class = format!("{}-{}", selector.to_class_name(), class);
225 self.class(&nested_class)
226 }
227
228 fn nested_pseudo_class(self, pseudo_class: NestingPseudoClass, class: &str) -> Self {
229 let nested_class = format!("{}-{}", pseudo_class.to_class_name(), class);
230 self.class(&nested_class)
231 }
232
233 fn nested_media_query(self, media_query: NestingMediaQuery, class: &str) -> Self {
234 let nested_class = format!("{}-{}", media_query.to_class_name(), class);
235 self.class(&nested_class)
236 }
237}
238
239pub trait CssNestingConvenience {
241 fn nested_hover(self, class: &str) -> Self;
243 fn nested_focus(self, class: &str) -> Self;
245 fn nested_active(self, class: &str) -> Self;
247 fn nested_first_child(self, class: &str) -> Self;
249 fn nested_last_child(self, class: &str) -> Self;
251 fn nested_sm(self, class: &str) -> Self;
253 fn nested_md(self, class: &str) -> Self;
255 fn nested_lg(self, class: &str) -> Self;
257 fn nested_dark(self, class: &str) -> Self;
259 fn nested_light(self, class: &str) -> Self;
261}
262
263impl CssNestingConvenience for ClassBuilder {
264 fn nested_hover(self, class: &str) -> Self {
265 self.nested_pseudo_class(NestingPseudoClass::Hover, class)
266 }
267
268 fn nested_focus(self, class: &str) -> Self {
269 self.nested_pseudo_class(NestingPseudoClass::Focus, class)
270 }
271
272 fn nested_active(self, class: &str) -> Self {
273 self.nested_pseudo_class(NestingPseudoClass::Active, class)
274 }
275
276 fn nested_first_child(self, class: &str) -> Self {
277 self.nested_pseudo_class(NestingPseudoClass::FirstChild, class)
278 }
279
280 fn nested_last_child(self, class: &str) -> Self {
281 self.nested_pseudo_class(NestingPseudoClass::LastChild, class)
282 }
283
284 fn nested_sm(self, class: &str) -> Self {
285 self.nested_media_query(NestingMediaQuery::Small, class)
286 }
287
288 fn nested_md(self, class: &str) -> Self {
289 self.nested_media_query(NestingMediaQuery::Medium, class)
290 }
291
292 fn nested_lg(self, class: &str) -> Self {
293 self.nested_media_query(NestingMediaQuery::Large, class)
294 }
295
296 fn nested_dark(self, class: &str) -> Self {
297 self.nested_media_query(NestingMediaQuery::Dark, class)
298 }
299
300 fn nested_light(self, class: &str) -> Self {
301 self.nested_media_query(NestingMediaQuery::Light, class)
302 }
303}
304
305#[cfg(test)]
306mod tests {
307 use super::*;
308 use crate::classes::ClassBuilder;
309
310 #[test]
311 fn test_nesting_selector_enum_values() {
312 assert_eq!(NestingSelector::DirectChild.to_string(), ">");
313 assert_eq!(NestingSelector::Descendant.to_string(), " ");
314 assert_eq!(NestingSelector::AdjacentSibling.to_string(), "+");
315 assert_eq!(NestingSelector::GeneralSibling.to_string(), "~");
316 assert_eq!(
317 NestingSelector::Custom("div".to_string()).to_string(),
318 "div"
319 );
320 }
321
322 #[test]
323 fn test_nesting_selector_class_names() {
324 assert_eq!(NestingSelector::DirectChild.to_class_name(), "nest-child");
325 assert_eq!(
326 NestingSelector::Descendant.to_class_name(),
327 "nest-descendant"
328 );
329 assert_eq!(
330 NestingSelector::AdjacentSibling.to_class_name(),
331 "nest-adjacent"
332 );
333 assert_eq!(
334 NestingSelector::GeneralSibling.to_class_name(),
335 "nest-sibling"
336 );
337 assert_eq!(
338 NestingSelector::Custom("div".to_string()).to_class_name(),
339 "nest-div"
340 );
341 }
342
343 #[test]
344 fn test_nesting_pseudo_class_enum_values() {
345 assert_eq!(NestingPseudoClass::Hover.to_string(), ":hover");
346 assert_eq!(NestingPseudoClass::Focus.to_string(), ":focus");
347 assert_eq!(NestingPseudoClass::Active.to_string(), ":active");
348 assert_eq!(NestingPseudoClass::FirstChild.to_string(), ":first-child");
349 assert_eq!(
350 NestingPseudoClass::NthChild("2n".to_string()).to_string(),
351 ":nth-child(2n)"
352 );
353 assert_eq!(
354 NestingPseudoClass::Custom("custom".to_string()).to_string(),
355 ":custom"
356 );
357 }
358
359 #[test]
360 fn test_nesting_pseudo_class_class_names() {
361 assert_eq!(NestingPseudoClass::Hover.to_class_name(), "nest-hover");
362 assert_eq!(NestingPseudoClass::Focus.to_class_name(), "nest-focus");
363 assert_eq!(NestingPseudoClass::Active.to_class_name(), "nest-active");
364 assert_eq!(
365 NestingPseudoClass::FirstChild.to_class_name(),
366 "nest-first-child"
367 );
368 assert_eq!(
369 NestingPseudoClass::NthChild("2n".to_string()).to_class_name(),
370 "nest-nth-child-2n"
371 );
372 assert_eq!(
373 NestingPseudoClass::Custom("custom".to_string()).to_class_name(),
374 "nest-custom"
375 );
376 }
377
378 #[test]
379 fn test_nesting_media_query_enum_values() {
380 assert_eq!(NestingMediaQuery::Small.to_string(), "(min-width: 640px)");
381 assert_eq!(NestingMediaQuery::Medium.to_string(), "(min-width: 768px)");
382 assert_eq!(NestingMediaQuery::Large.to_string(), "(min-width: 1024px)");
383 assert_eq!(
384 NestingMediaQuery::Dark.to_string(),
385 "(prefers-color-scheme: dark)"
386 );
387 assert_eq!(NestingMediaQuery::Print.to_string(), "print");
388 assert_eq!(
389 NestingMediaQuery::Custom("(max-width: 600px)".to_string()).to_string(),
390 "(max-width: 600px)"
391 );
392 }
393
394 #[test]
395 fn test_nesting_media_query_class_names() {
396 assert_eq!(NestingMediaQuery::Small.to_class_name(), "nest-sm");
397 assert_eq!(NestingMediaQuery::Medium.to_class_name(), "nest-md");
398 assert_eq!(NestingMediaQuery::Large.to_class_name(), "nest-lg");
399 assert_eq!(NestingMediaQuery::Dark.to_class_name(), "nest-dark");
400 assert_eq!(NestingMediaQuery::Print.to_class_name(), "nest-print");
401 assert_eq!(
402 NestingMediaQuery::Custom("(max-width: 600px)".to_string()).to_class_name(),
403 "nest-max-width-600px"
404 );
405 }
406
407 #[test]
408 fn test_css_nesting_utilities() {
409 let classes = ClassBuilder::new()
410 .nesting_selector(NestingSelector::DirectChild)
411 .nesting_pseudo_class(NestingPseudoClass::Hover)
412 .nesting_media_query(NestingMediaQuery::Small)
413 .nested_class(NestingSelector::Descendant, "text-blue-500")
414 .nested_pseudo_class(NestingPseudoClass::Focus, "text-red-500")
415 .nested_media_query(NestingMediaQuery::Medium, "text-green-500");
416
417 let result = classes.build();
418 assert!(result.classes.contains("nest-child"));
419 assert!(result.classes.contains("nest-hover"));
420 assert!(result.classes.contains("nest-sm"));
421 assert!(result.classes.contains("nest-descendant-text-blue-500"));
422 assert!(result.classes.contains("nest-focus-text-red-500"));
423 assert!(result.classes.contains("nest-md-text-green-500"));
424 }
425
426 #[test]
427 fn test_css_nesting_convenience() {
428 let classes = ClassBuilder::new()
429 .nested_hover("text-blue-500")
430 .nested_focus("text-red-500")
431 .nested_active("text-green-500")
432 .nested_first_child("text-yellow-500")
433 .nested_last_child("text-purple-500")
434 .nested_sm("text-pink-500")
435 .nested_md("text-indigo-500")
436 .nested_lg("text-cyan-500")
437 .nested_dark("text-gray-500")
438 .nested_light("text-white");
439
440 let result = classes.build();
441 assert!(result.classes.contains("nest-hover-text-blue-500"));
442 assert!(result.classes.contains("nest-focus-text-red-500"));
443 assert!(result.classes.contains("nest-active-text-green-500"));
444 assert!(result.classes.contains("nest-first-child-text-yellow-500"));
445 assert!(result.classes.contains("nest-last-child-text-purple-500"));
446 assert!(result.classes.contains("nest-sm-text-pink-500"));
447 assert!(result.classes.contains("nest-md-text-indigo-500"));
448 assert!(result.classes.contains("nest-lg-text-cyan-500"));
449 assert!(result.classes.contains("nest-dark-text-gray-500"));
450 assert!(result.classes.contains("nest-light-text-white"));
451 }
452
453 #[test]
454 fn test_css_nesting_serialization() {
455 let selector = NestingSelector::DirectChild;
456 let serialized = serde_json::to_string(&selector).unwrap();
457 let deserialized: NestingSelector = serde_json::from_str(&serialized).unwrap();
458 assert_eq!(selector, deserialized);
459
460 let pseudo_class = NestingPseudoClass::Hover;
461 let serialized = serde_json::to_string(&pseudo_class).unwrap();
462 let deserialized: NestingPseudoClass = serde_json::from_str(&serialized).unwrap();
463 assert_eq!(pseudo_class, deserialized);
464
465 let media_query = NestingMediaQuery::Small;
466 let serialized = serde_json::to_string(&media_query).unwrap();
467 let deserialized: NestingMediaQuery = serde_json::from_str(&serialized).unwrap();
468 assert_eq!(media_query, deserialized);
469 }
470
471 #[test]
472 fn test_css_nesting_comprehensive_usage() {
473 let classes = ClassBuilder::new()
474 .nesting_selector(NestingSelector::DirectChild)
475 .nesting_pseudo_class(NestingPseudoClass::Hover)
476 .nesting_media_query(NestingMediaQuery::Small)
477 .nested_class(NestingSelector::Descendant, "text-blue-500")
478 .nested_pseudo_class(NestingPseudoClass::Focus, "text-red-500")
479 .nested_media_query(NestingMediaQuery::Medium, "text-green-500")
480 .nested_hover("text-yellow-500")
481 .nested_focus("text-purple-500")
482 .nested_active("text-pink-500")
483 .nested_first_child("text-indigo-500")
484 .nested_last_child("text-cyan-500")
485 .nested_sm("text-gray-500")
486 .nested_md("text-white")
487 .nested_lg("text-black")
488 .nested_dark("text-gray-100")
489 .nested_light("text-gray-900");
490
491 let result = classes.build();
492 assert!(result.classes.contains("nest-child"));
493 assert!(result.classes.contains("nest-hover"));
494 assert!(result.classes.contains("nest-sm"));
495 assert!(result.classes.contains("nest-descendant-text-blue-500"));
496 assert!(result.classes.contains("nest-focus-text-red-500"));
497 assert!(result.classes.contains("nest-md-text-green-500"));
498 assert!(result.classes.contains("nest-hover-text-yellow-500"));
499 assert!(result.classes.contains("nest-focus-text-purple-500"));
500 assert!(result.classes.contains("nest-active-text-pink-500"));
501 assert!(result.classes.contains("nest-first-child-text-indigo-500"));
502 assert!(result.classes.contains("nest-last-child-text-cyan-500"));
503 assert!(result.classes.contains("nest-sm-text-gray-500"));
504 assert!(result.classes.contains("nest-md-text-white"));
505 assert!(result.classes.contains("nest-lg-text-black"));
506 assert!(result.classes.contains("nest-dark-text-gray-100"));
507 assert!(result.classes.contains("nest-light-text-gray-900"));
508 }
509}