tailwind_rs_core/
enhanced_variants.rs

1//! Enhanced variant system for Tailwind-RS Core
2//!
3//! This module provides advanced variant parsing and combination capabilities,
4//! supporting complex variant combinations, modern CSS features, and
5//! comprehensive selector generation.
6
7use crate::error::Result;
8use crate::responsive::Breakpoint;
9use serde::{Deserialize, Serialize};
10use std::collections::HashMap;
11
12/// Enhanced variant parser for complex class combinations
13#[derive(Debug, Clone)]
14pub struct EnhancedVariantParser {
15    /// Variant definitions
16    variants: HashMap<String, VariantDefinition>,
17    /// Responsive breakpoints
18    breakpoints: HashMap<String, Breakpoint>,
19    /// Custom variant definitions
20    custom_variants: HashMap<String, CustomVariant>,
21}
22
23/// Variant definition
24#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
25pub struct VariantDefinition {
26    /// Variant name
27    pub name: String,
28    /// Variant type
29    pub variant_type: VariantType,
30    /// CSS selector pattern
31    pub selector_pattern: String,
32    /// Media query (for responsive variants)
33    pub media_query: Option<String>,
34    /// Specificity weight
35    pub specificity: u32,
36    /// Whether this variant can be combined with others
37    pub combinable: bool,
38    /// Dependencies (other variants that must be present)
39    pub dependencies: Vec<String>,
40}
41
42/// Variant type classification
43#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
44pub enum VariantType {
45    /// State variants (hover, focus, etc.)
46    State,
47    /// Responsive variants (sm, md, lg, etc.)
48    Responsive,
49    /// Dark mode variants
50    DarkMode,
51    /// Group variants (group-hover, group-focus, etc.)
52    Group,
53    /// Peer variants (peer-hover, peer-focus, etc.)
54    Peer,
55    /// Pseudo-element variants (before, after, etc.)
56    PseudoElement,
57    /// Container query variants
58    Container,
59    /// Cascade layer variants
60    Layer,
61    /// Custom variants
62    Custom,
63}
64
65/// Custom variant definition
66#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
67pub struct CustomVariant {
68    /// Variant name
69    pub name: String,
70    /// CSS selector pattern
71    pub selector_pattern: String,
72    /// Media query
73    pub media_query: Option<String>,
74    /// Specificity weight
75    pub specificity: u32,
76    /// Whether this variant can be combined with others
77    pub combinable: bool,
78}
79
80/// Parsed variant combination
81#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
82pub struct VariantCombination {
83    /// All variants in the combination
84    pub variants: Vec<ParsedVariant>,
85    /// Base class name
86    pub base_class: String,
87    /// Generated CSS selector
88    pub selector: String,
89    /// Media query (if any)
90    pub media_query: Option<String>,
91    /// Total specificity
92    pub specificity: u32,
93    /// Whether this combination is valid
94    pub is_valid: bool,
95    /// Validation errors
96    pub errors: Vec<String>,
97}
98
99/// Parsed individual variant
100#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
101pub struct ParsedVariant {
102    /// Variant name
103    pub name: String,
104    /// Variant type
105    pub variant_type: VariantType,
106    /// CSS selector fragment
107    pub selector_fragment: String,
108    /// Specificity weight
109    pub specificity: u32,
110    /// Position in the combination
111    pub position: usize,
112}
113
114/// Enhanced variant parsing result
115#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
116pub struct VariantParseResult {
117    /// Parsed variant combination
118    pub combination: VariantCombination,
119    /// Processing metadata
120    pub metadata: VariantMetadata,
121}
122
123/// Variant processing metadata
124#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
125pub struct VariantMetadata {
126    /// Processing time
127    pub processing_time: std::time::Duration,
128    /// Number of variants parsed
129    pub variant_count: usize,
130    /// Number of combinations generated
131    pub combination_count: usize,
132    /// Memory usage
133    pub memory_usage: usize,
134    /// Cache hits
135    pub cache_hits: usize,
136    /// Cache misses
137    pub cache_misses: usize,
138}
139
140impl EnhancedVariantParser {
141    /// Create a new enhanced variant parser
142    pub fn new() -> Self {
143        let mut parser = Self {
144            variants: HashMap::new(),
145            breakpoints: HashMap::new(),
146            custom_variants: HashMap::new(),
147        };
148
149        parser.initialize_default_variants();
150        parser.initialize_breakpoints();
151
152        parser
153    }
154
155    /// Initialize default variant definitions
156    fn initialize_default_variants(&mut self) {
157        // State variants
158        self.add_variant(VariantDefinition {
159            name: "hover".to_string(),
160            variant_type: VariantType::State,
161            selector_pattern: ":hover".to_string(),
162            media_query: None,
163            specificity: 10,
164            combinable: true,
165            dependencies: Vec::new(),
166        });
167
168        self.add_variant(VariantDefinition {
169            name: "focus".to_string(),
170            variant_type: VariantType::State,
171            selector_pattern: ":focus".to_string(),
172            media_query: None,
173            specificity: 10,
174            combinable: true,
175            dependencies: Vec::new(),
176        });
177
178        self.add_variant(VariantDefinition {
179            name: "active".to_string(),
180            variant_type: VariantType::State,
181            selector_pattern: ":active".to_string(),
182            media_query: None,
183            specificity: 10,
184            combinable: true,
185            dependencies: Vec::new(),
186        });
187
188        self.add_variant(VariantDefinition {
189            name: "visited".to_string(),
190            variant_type: VariantType::State,
191            selector_pattern: ":visited".to_string(),
192            media_query: None,
193            specificity: 10,
194            combinable: true,
195            dependencies: Vec::new(),
196        });
197
198        self.add_variant(VariantDefinition {
199            name: "disabled".to_string(),
200            variant_type: VariantType::State,
201            selector_pattern: ":disabled".to_string(),
202            media_query: None,
203            specificity: 10,
204            combinable: true,
205            dependencies: Vec::new(),
206        });
207
208        // Dark mode variants
209        self.add_variant(VariantDefinition {
210            name: "dark".to_string(),
211            variant_type: VariantType::DarkMode,
212            selector_pattern: ".dark".to_string(),
213            media_query: None,
214            specificity: 20,
215            combinable: true,
216            dependencies: Vec::new(),
217        });
218
219        // Group variants
220        self.add_variant(VariantDefinition {
221            name: "group-hover".to_string(),
222            variant_type: VariantType::Group,
223            selector_pattern: ".group:hover".to_string(),
224            media_query: None,
225            specificity: 15,
226            combinable: true,
227            dependencies: Vec::new(),
228        });
229
230        self.add_variant(VariantDefinition {
231            name: "group-focus".to_string(),
232            variant_type: VariantType::Group,
233            selector_pattern: ".group:focus".to_string(),
234            media_query: None,
235            specificity: 15,
236            combinable: true,
237            dependencies: Vec::new(),
238        });
239
240        // Peer variants
241        self.add_variant(VariantDefinition {
242            name: "peer-hover".to_string(),
243            variant_type: VariantType::Peer,
244            selector_pattern: ".peer:hover".to_string(),
245            media_query: None,
246            specificity: 15,
247            combinable: true,
248            dependencies: Vec::new(),
249        });
250
251        self.add_variant(VariantDefinition {
252            name: "peer-focus".to_string(),
253            variant_type: VariantType::Peer,
254            selector_pattern: ".peer:focus".to_string(),
255            media_query: None,
256            specificity: 15,
257            combinable: true,
258            dependencies: Vec::new(),
259        });
260
261        // Pseudo-element variants
262        self.add_variant(VariantDefinition {
263            name: "before".to_string(),
264            variant_type: VariantType::PseudoElement,
265            selector_pattern: "::before".to_string(),
266            media_query: None,
267            specificity: 10,
268            combinable: true,
269            dependencies: Vec::new(),
270        });
271
272        self.add_variant(VariantDefinition {
273            name: "after".to_string(),
274            variant_type: VariantType::PseudoElement,
275            selector_pattern: "::after".to_string(),
276            media_query: None,
277            specificity: 10,
278            combinable: true,
279            dependencies: Vec::new(),
280        });
281
282        // Container query variants
283        self.add_variant(VariantDefinition {
284            name: "container".to_string(),
285            variant_type: VariantType::Container,
286            selector_pattern: "@container".to_string(),
287            media_query: None,
288            specificity: 25,
289            combinable: true,
290            dependencies: Vec::new(),
291        });
292
293        // Cascade layer variants
294        self.add_variant(VariantDefinition {
295            name: "layer".to_string(),
296            variant_type: VariantType::Layer,
297            selector_pattern: "@layer".to_string(),
298            media_query: None,
299            specificity: 30,
300            combinable: true,
301            dependencies: Vec::new(),
302        });
303
304        // Responsive variants
305        self.add_variant(VariantDefinition {
306            name: "sm".to_string(),
307            variant_type: VariantType::Responsive,
308            selector_pattern: "".to_string(),
309            media_query: Some("(min-width: 640px)".to_string()),
310            specificity: 20,
311            combinable: false,
312            dependencies: Vec::new(),
313        });
314
315        self.add_variant(VariantDefinition {
316            name: "md".to_string(),
317            variant_type: VariantType::Responsive,
318            selector_pattern: "".to_string(),
319            media_query: Some("(min-width: 768px)".to_string()),
320            specificity: 20,
321            combinable: false,
322            dependencies: Vec::new(),
323        });
324
325        self.add_variant(VariantDefinition {
326            name: "lg".to_string(),
327            variant_type: VariantType::Responsive,
328            selector_pattern: "".to_string(),
329            media_query: Some("(min-width: 1024px)".to_string()),
330            specificity: 20,
331            combinable: false,
332            dependencies: Vec::new(),
333        });
334
335        self.add_variant(VariantDefinition {
336            name: "xl".to_string(),
337            variant_type: VariantType::Responsive,
338            selector_pattern: "".to_string(),
339            media_query: Some("(min-width: 1280px)".to_string()),
340            specificity: 20,
341            combinable: false,
342            dependencies: Vec::new(),
343        });
344
345        self.add_variant(VariantDefinition {
346            name: "2xl".to_string(),
347            variant_type: VariantType::Responsive,
348            selector_pattern: "".to_string(),
349            media_query: Some("(min-width: 1536px)".to_string()),
350            specificity: 20,
351            combinable: false,
352            dependencies: Vec::new(),
353        });
354    }
355
356    /// Initialize responsive breakpoints
357    fn initialize_breakpoints(&mut self) {
358        self.breakpoints.insert("sm".to_string(), Breakpoint::Sm);
359        self.breakpoints.insert("md".to_string(), Breakpoint::Md);
360        self.breakpoints.insert("lg".to_string(), Breakpoint::Lg);
361        self.breakpoints.insert("xl".to_string(), Breakpoint::Xl);
362        self.breakpoints.insert("2xl".to_string(), Breakpoint::Xl);
363    }
364
365    /// Add a variant definition
366    pub fn add_variant(&mut self, variant: VariantDefinition) {
367        self.variants.insert(variant.name.clone(), variant);
368    }
369
370    /// Add a custom variant
371    pub fn add_custom_variant(&mut self, variant: CustomVariant) {
372        self.custom_variants.insert(variant.name.clone(), variant);
373    }
374
375    /// Parse a class string with enhanced variant support
376    pub fn parse_class(&self, class: &str) -> Result<VariantParseResult> {
377        let start_time = std::time::Instant::now();
378
379        // Parse variants and base class
380        let (variants, base_class) = self.parse_variants_advanced(class);
381
382        // Generate variant combination
383        let variant_count = variants.len();
384        let combination = self.generate_variant_combination(variants, base_class)?;
385
386        let processing_time = start_time.elapsed();
387
388        Ok(VariantParseResult {
389            combination,
390            metadata: VariantMetadata {
391                processing_time,
392                variant_count,
393                combination_count: 1,
394                memory_usage: 0, // Placeholder
395                cache_hits: 0,   // Placeholder
396                cache_misses: 1, // Placeholder
397            },
398        })
399    }
400
401    /// Advanced variant parsing supporting multiple variants
402    fn parse_variants_advanced(&self, class: &str) -> (Vec<String>, String) {
403        let mut variants = Vec::new();
404        let mut remaining = class.to_string();
405
406        // Parse variants in order of specificity (most specific first)
407        let variant_patterns = [
408            // Dark mode variants
409            ("dark:", "dark"),
410            // Group variants
411            ("group-hover:", "group-hover"),
412            ("group-focus:", "group-focus"),
413            ("group-active:", "group-active"),
414            ("group-disabled:", "group-disabled"),
415            // Peer variants
416            ("peer-hover:", "peer-hover"),
417            ("peer-focus:", "peer-focus"),
418            ("peer-active:", "peer-active"),
419            ("peer-disabled:", "peer-disabled"),
420            // State variants
421            ("hover:", "hover"),
422            ("focus:", "focus"),
423            ("active:", "active"),
424            ("visited:", "visited"),
425            ("disabled:", "disabled"),
426            // Pseudo-element variants
427            ("before:", "before"),
428            ("after:", "after"),
429            // Container variants
430            ("container:", "container"),
431            // Layer variants
432            ("layer:", "layer"),
433            // Responsive variants
434            ("sm:", "sm"),
435            ("md:", "md"),
436            ("lg:", "lg"),
437            ("xl:", "xl"),
438            ("2xl:", "2xl"),
439        ];
440
441        // Parse multiple variants
442        loop {
443            let mut found = false;
444            for (prefix, variant) in &variant_patterns {
445                if remaining.starts_with(prefix) {
446                    variants.push(variant.to_string());
447                    remaining = remaining
448                        .strip_prefix(prefix)
449                        .unwrap_or(&remaining)
450                        .to_string();
451                    found = true;
452                    break;
453                }
454            }
455
456            if !found {
457                break;
458            }
459        }
460
461        (variants, remaining)
462    }
463
464    /// Generate variant combination
465    fn generate_variant_combination(
466        &self,
467        variants: Vec<String>,
468        base_class: String,
469    ) -> Result<VariantCombination> {
470        let mut parsed_variants = Vec::new();
471        let mut selector_parts = Vec::new();
472        let mut media_query = None;
473        let mut total_specificity = 10; // Base specificity
474        let mut errors = Vec::new();
475
476        // Parse each variant
477        for (i, variant_name) in variants.iter().enumerate() {
478            if let Some(variant_def) = self.variants.get(variant_name) {
479                let parsed_variant = ParsedVariant {
480                    name: variant_name.clone(),
481                    variant_type: variant_def.variant_type.clone(),
482                    selector_fragment: variant_def.selector_pattern.clone(),
483                    specificity: variant_def.specificity,
484                    position: i,
485                };
486
487                parsed_variants.push(parsed_variant.clone());
488                selector_parts.push(variant_def.selector_pattern.clone());
489                total_specificity += variant_def.specificity;
490
491                // Handle media queries for responsive variants
492                if let Some(mq) = &variant_def.media_query {
493                    media_query = Some(mq.clone());
494                }
495            } else if let Some(custom_variant) = self.custom_variants.get(variant_name) {
496                let parsed_variant = ParsedVariant {
497                    name: variant_name.clone(),
498                    variant_type: VariantType::Custom,
499                    selector_fragment: custom_variant.selector_pattern.clone(),
500                    specificity: custom_variant.specificity,
501                    position: i,
502                };
503
504                parsed_variants.push(parsed_variant.clone());
505                selector_parts.push(custom_variant.selector_pattern.clone());
506                total_specificity += custom_variant.specificity;
507
508                if let Some(mq) = &custom_variant.media_query {
509                    media_query = Some(mq.clone());
510                }
511            } else {
512                errors.push(format!("Unknown variant: {}", variant_name));
513            }
514        }
515
516        // Generate final selector
517        let selector = self.generate_selector(selector_parts, &base_class);
518
519        // Validate combination
520        let is_valid = self.validate_combination(&parsed_variants, &errors);
521
522        Ok(VariantCombination {
523            variants: parsed_variants,
524            base_class,
525            selector,
526            media_query,
527            specificity: total_specificity,
528            is_valid,
529            errors,
530        })
531    }
532
533    /// Generate CSS selector from parts
534    fn generate_selector(&self, parts: Vec<String>, base_class: &str) -> String {
535        let mut selector = String::new();
536
537        // Add variant parts
538        for part in parts {
539            if part.starts_with('.') {
540                selector.push_str(&part);
541                selector.push(' ');
542            } else if part.starts_with(':') {
543                selector.push_str(&part);
544            } else if part.starts_with('@') {
545                // Handle at-rules like @container, @layer
546                selector.push_str(&part);
547                selector.push(' ');
548            }
549        }
550
551        // Add base class
552        selector.push_str(&format!(".{}", base_class));
553
554        selector
555    }
556
557    /// Validate variant combination
558    fn validate_combination(&self, variants: &[ParsedVariant], errors: &[String]) -> bool {
559        if !errors.is_empty() {
560            return false;
561        }
562
563        // Check for conflicting variants
564        let mut variant_types = std::collections::HashSet::new();
565        for variant in variants {
566            if !variant_types.insert(&variant.variant_type) {
567                // Check if this variant type allows multiple instances
568                match variant.variant_type {
569                    VariantType::State => {
570                        // Multiple state variants are allowed
571                    }
572                    VariantType::Responsive => {
573                        // Only one responsive variant allowed
574                        return false;
575                    }
576                    VariantType::DarkMode => {
577                        // Only one dark mode variant allowed
578                        return false;
579                    }
580                    _ => {
581                        // Other types may have specific rules
582                    }
583                }
584            }
585        }
586
587        true
588    }
589
590    /// Get variant definition
591    pub fn get_variant(&self, name: &str) -> Option<&VariantDefinition> {
592        self.variants.get(name)
593    }
594
595    /// Get all variants
596    pub fn get_all_variants(&self) -> &HashMap<String, VariantDefinition> {
597        &self.variants
598    }
599
600    /// Get custom variants
601    pub fn get_custom_variants(&self) -> &HashMap<String, CustomVariant> {
602        &self.custom_variants
603    }
604
605    /// Remove variant
606    pub fn remove_variant(&mut self, name: &str) -> Option<VariantDefinition> {
607        self.variants.remove(name)
608    }
609
610    /// Remove custom variant
611    pub fn remove_custom_variant(&mut self, name: &str) -> Option<CustomVariant> {
612        self.custom_variants.remove(name)
613    }
614
615    /// Clear all variants
616    pub fn clear_variants(&mut self) {
617        self.variants.clear();
618        self.custom_variants.clear();
619    }
620
621    /// Reset to default variants
622    pub fn reset_to_defaults(&mut self) {
623        self.clear_variants();
624        self.initialize_default_variants();
625    }
626}
627
628impl Default for EnhancedVariantParser {
629    fn default() -> Self {
630        Self::new()
631    }
632}
633
634#[cfg(test)]
635mod tests {
636    use super::*;
637
638    #[test]
639    fn test_enhanced_variant_parser_creation() {
640        let parser = EnhancedVariantParser::new();
641        assert!(!parser.variants.is_empty());
642        assert!(!parser.breakpoints.is_empty());
643    }
644
645    #[test]
646    fn test_variant_definition_creation() {
647        let variant = VariantDefinition {
648            name: "hover".to_string(),
649            variant_type: VariantType::State,
650            selector_pattern: ":hover".to_string(),
651            media_query: None,
652            specificity: 10,
653            combinable: true,
654            dependencies: Vec::new(),
655        };
656
657        assert_eq!(variant.name, "hover");
658        assert_eq!(variant.variant_type, VariantType::State);
659        assert_eq!(variant.specificity, 10);
660    }
661
662    #[test]
663    fn test_parse_simple_class() {
664        let parser = EnhancedVariantParser::new();
665        let result = parser.parse_class("p-4");
666
667        assert!(result.is_ok());
668        let result = result.unwrap();
669        assert_eq!(result.combination.base_class, "p-4");
670        assert!(result.combination.variants.is_empty());
671        assert!(result.combination.is_valid);
672    }
673
674    #[test]
675    fn test_parse_single_variant() {
676        let parser = EnhancedVariantParser::new();
677        let result = parser.parse_class("hover:bg-blue-500");
678
679        assert!(result.is_ok());
680        let result = result.unwrap();
681        assert_eq!(result.combination.base_class, "bg-blue-500");
682        assert_eq!(result.combination.variants.len(), 1);
683        assert_eq!(result.combination.variants[0].name, "hover");
684        assert!(result.combination.is_valid);
685    }
686
687    #[test]
688    fn test_parse_multiple_variants() {
689        let parser = EnhancedVariantParser::new();
690        let result = parser.parse_class("dark:hover:bg-blue-500");
691
692        assert!(result.is_ok());
693        let result = result.unwrap();
694        assert_eq!(result.combination.base_class, "bg-blue-500");
695        assert_eq!(result.combination.variants.len(), 2);
696        assert!(result.combination.is_valid);
697    }
698
699    #[test]
700    fn test_parse_responsive_variant() {
701        let parser = EnhancedVariantParser::new();
702        let result = parser.parse_class("md:p-4");
703
704        assert!(result.is_ok());
705        let result = result.unwrap();
706        assert_eq!(result.combination.base_class, "p-4");
707        assert_eq!(result.combination.variants.len(), 1);
708        assert_eq!(result.combination.variants[0].name, "md");
709        assert!(result.combination.is_valid);
710    }
711
712    #[test]
713    fn test_parse_complex_combination() {
714        let parser = EnhancedVariantParser::new();
715        let result = parser.parse_class("dark:group-hover:focus:bg-blue-500");
716
717        assert!(result.is_ok());
718        let result = result.unwrap();
719        assert_eq!(result.combination.base_class, "bg-blue-500");
720        assert_eq!(result.combination.variants.len(), 3);
721        assert!(result.combination.is_valid);
722    }
723
724    #[test]
725    fn test_custom_variant_addition() {
726        let mut parser = EnhancedVariantParser::new();
727        let custom_variant = CustomVariant {
728            name: "custom".to_string(),
729            selector_pattern: ".custom".to_string(),
730            media_query: None,
731            specificity: 15,
732            combinable: true,
733        };
734
735        parser.add_custom_variant(custom_variant);
736        assert!(parser.custom_variants.contains_key("custom"));
737    }
738
739    #[test]
740    fn test_variant_removal() {
741        let mut parser = EnhancedVariantParser::new();
742        let removed = parser.remove_variant("hover");
743        assert!(removed.is_some());
744        assert!(!parser.variants.contains_key("hover"));
745    }
746
747    #[test]
748    fn test_parser_reset() {
749        let mut parser = EnhancedVariantParser::new();
750        parser.add_custom_variant(CustomVariant {
751            name: "test".to_string(),
752            selector_pattern: ".test".to_string(),
753            media_query: None,
754            specificity: 10,
755            combinable: true,
756        });
757
758        assert!(parser.custom_variants.contains_key("test"));
759
760        parser.reset_to_defaults();
761        assert!(!parser.custom_variants.contains_key("test"));
762        assert!(parser.variants.contains_key("hover"));
763    }
764}