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, TailwindError};
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.strip_prefix(prefix).unwrap_or(&remaining).to_string();
448                    found = true;
449                    break;
450                }
451            }
452            
453            if !found {
454                break;
455            }
456        }
457        
458        (variants, remaining)
459    }
460
461    /// Generate variant combination
462    fn generate_variant_combination(&self, variants: Vec<String>, base_class: String) -> Result<VariantCombination> {
463        let mut parsed_variants = Vec::new();
464        let mut selector_parts = Vec::new();
465        let mut media_query = None;
466        let mut total_specificity = 10; // Base specificity
467        let mut errors = Vec::new();
468        
469        // Parse each variant
470        for (i, variant_name) in variants.iter().enumerate() {
471            if let Some(variant_def) = self.variants.get(variant_name) {
472                let parsed_variant = ParsedVariant {
473                    name: variant_name.clone(),
474                    variant_type: variant_def.variant_type.clone(),
475                    selector_fragment: variant_def.selector_pattern.clone(),
476                    specificity: variant_def.specificity,
477                    position: i,
478                };
479                
480                parsed_variants.push(parsed_variant.clone());
481                selector_parts.push(variant_def.selector_pattern.clone());
482                total_specificity += variant_def.specificity;
483                
484                // Handle media queries for responsive variants
485                if let Some(mq) = &variant_def.media_query {
486                    media_query = Some(mq.clone());
487                }
488            } else if let Some(custom_variant) = self.custom_variants.get(variant_name) {
489                let parsed_variant = ParsedVariant {
490                    name: variant_name.clone(),
491                    variant_type: VariantType::Custom,
492                    selector_fragment: custom_variant.selector_pattern.clone(),
493                    specificity: custom_variant.specificity,
494                    position: i,
495                };
496                
497                parsed_variants.push(parsed_variant.clone());
498                selector_parts.push(custom_variant.selector_pattern.clone());
499                total_specificity += custom_variant.specificity;
500                
501                if let Some(mq) = &custom_variant.media_query {
502                    media_query = Some(mq.clone());
503                }
504            } else {
505                errors.push(format!("Unknown variant: {}", variant_name));
506            }
507        }
508        
509        // Generate final selector
510        let selector = self.generate_selector(selector_parts, &base_class);
511        
512        // Validate combination
513        let is_valid = self.validate_combination(&parsed_variants, &errors);
514        
515        Ok(VariantCombination {
516            variants: parsed_variants,
517            base_class,
518            selector,
519            media_query,
520            specificity: total_specificity,
521            is_valid,
522            errors,
523        })
524    }
525
526    /// Generate CSS selector from parts
527    fn generate_selector(&self, parts: Vec<String>, base_class: &str) -> String {
528        let mut selector = String::new();
529        
530        // Add variant parts
531        for part in parts {
532            if part.starts_with('.') {
533                selector.push_str(&part);
534                selector.push(' ');
535            } else if part.starts_with(':') {
536                selector.push_str(&part);
537            } else if part.starts_with('@') {
538                // Handle at-rules like @container, @layer
539                selector.push_str(&part);
540                selector.push(' ');
541            }
542        }
543        
544        // Add base class
545        selector.push_str(&format!(".{}", base_class));
546        
547        selector
548    }
549
550    /// Validate variant combination
551    fn validate_combination(&self, variants: &[ParsedVariant], errors: &[String]) -> bool {
552        if !errors.is_empty() {
553            return false;
554        }
555        
556        // Check for conflicting variants
557        let mut variant_types = std::collections::HashSet::new();
558        for variant in variants {
559            if !variant_types.insert(&variant.variant_type) {
560                // Check if this variant type allows multiple instances
561                match variant.variant_type {
562                    VariantType::State => {
563                        // Multiple state variants are allowed
564                    }
565                    VariantType::Responsive => {
566                        // Only one responsive variant allowed
567                        return false;
568                    }
569                    VariantType::DarkMode => {
570                        // Only one dark mode variant allowed
571                        return false;
572                    }
573                    _ => {
574                        // Other types may have specific rules
575                    }
576                }
577            }
578        }
579        
580        true
581    }
582
583    /// Get variant definition
584    pub fn get_variant(&self, name: &str) -> Option<&VariantDefinition> {
585        self.variants.get(name)
586    }
587
588    /// Get all variants
589    pub fn get_all_variants(&self) -> &HashMap<String, VariantDefinition> {
590        &self.variants
591    }
592
593    /// Get custom variants
594    pub fn get_custom_variants(&self) -> &HashMap<String, CustomVariant> {
595        &self.custom_variants
596    }
597
598    /// Remove variant
599    pub fn remove_variant(&mut self, name: &str) -> Option<VariantDefinition> {
600        self.variants.remove(name)
601    }
602
603    /// Remove custom variant
604    pub fn remove_custom_variant(&mut self, name: &str) -> Option<CustomVariant> {
605        self.custom_variants.remove(name)
606    }
607
608    /// Clear all variants
609    pub fn clear_variants(&mut self) {
610        self.variants.clear();
611        self.custom_variants.clear();
612    }
613
614    /// Reset to default variants
615    pub fn reset_to_defaults(&mut self) {
616        self.clear_variants();
617        self.initialize_default_variants();
618    }
619}
620
621impl Default for EnhancedVariantParser {
622    fn default() -> Self {
623        Self::new()
624    }
625}
626
627#[cfg(test)]
628mod tests {
629    use super::*;
630
631    #[test]
632    fn test_enhanced_variant_parser_creation() {
633        let parser = EnhancedVariantParser::new();
634        assert!(!parser.variants.is_empty());
635        assert!(!parser.breakpoints.is_empty());
636    }
637
638    #[test]
639    fn test_variant_definition_creation() {
640        let variant = VariantDefinition {
641            name: "hover".to_string(),
642            variant_type: VariantType::State,
643            selector_pattern: ":hover".to_string(),
644            media_query: None,
645            specificity: 10,
646            combinable: true,
647            dependencies: Vec::new(),
648        };
649        
650        assert_eq!(variant.name, "hover");
651        assert_eq!(variant.variant_type, VariantType::State);
652        assert_eq!(variant.specificity, 10);
653    }
654
655    #[test]
656    fn test_parse_simple_class() {
657        let parser = EnhancedVariantParser::new();
658        let result = parser.parse_class("p-4");
659        
660        assert!(result.is_ok());
661        let result = result.unwrap();
662        assert_eq!(result.combination.base_class, "p-4");
663        assert!(result.combination.variants.is_empty());
664        assert!(result.combination.is_valid);
665    }
666
667    #[test]
668    fn test_parse_single_variant() {
669        let parser = EnhancedVariantParser::new();
670        let result = parser.parse_class("hover:bg-blue-500");
671        
672        assert!(result.is_ok());
673        let result = result.unwrap();
674        assert_eq!(result.combination.base_class, "bg-blue-500");
675        assert_eq!(result.combination.variants.len(), 1);
676        assert_eq!(result.combination.variants[0].name, "hover");
677        assert!(result.combination.is_valid);
678    }
679
680    #[test]
681    fn test_parse_multiple_variants() {
682        let parser = EnhancedVariantParser::new();
683        let result = parser.parse_class("dark:hover:bg-blue-500");
684        
685        assert!(result.is_ok());
686        let result = result.unwrap();
687        assert_eq!(result.combination.base_class, "bg-blue-500");
688        assert_eq!(result.combination.variants.len(), 2);
689        assert!(result.combination.is_valid);
690    }
691
692    #[test]
693    fn test_parse_responsive_variant() {
694        let parser = EnhancedVariantParser::new();
695        let result = parser.parse_class("md:p-4");
696        
697        assert!(result.is_ok());
698        let result = result.unwrap();
699        assert_eq!(result.combination.base_class, "p-4");
700        assert_eq!(result.combination.variants.len(), 1);
701        assert_eq!(result.combination.variants[0].name, "md");
702        assert!(result.combination.is_valid);
703    }
704
705    #[test]
706    fn test_parse_complex_combination() {
707        let parser = EnhancedVariantParser::new();
708        let result = parser.parse_class("dark:group-hover:focus:bg-blue-500");
709        
710        assert!(result.is_ok());
711        let result = result.unwrap();
712        assert_eq!(result.combination.base_class, "bg-blue-500");
713        assert_eq!(result.combination.variants.len(), 3);
714        assert!(result.combination.is_valid);
715    }
716
717    #[test]
718    fn test_custom_variant_addition() {
719        let mut parser = EnhancedVariantParser::new();
720        let custom_variant = CustomVariant {
721            name: "custom".to_string(),
722            selector_pattern: ".custom".to_string(),
723            media_query: None,
724            specificity: 15,
725            combinable: true,
726        };
727        
728        parser.add_custom_variant(custom_variant);
729        assert!(parser.custom_variants.contains_key("custom"));
730    }
731
732    #[test]
733    fn test_variant_removal() {
734        let mut parser = EnhancedVariantParser::new();
735        let removed = parser.remove_variant("hover");
736        assert!(removed.is_some());
737        assert!(!parser.variants.contains_key("hover"));
738    }
739
740    #[test]
741    fn test_parser_reset() {
742        let mut parser = EnhancedVariantParser::new();
743        parser.add_custom_variant(CustomVariant {
744            name: "test".to_string(),
745            selector_pattern: ".test".to_string(),
746            media_query: None,
747            specificity: 10,
748            combinable: true,
749        });
750        
751        assert!(parser.custom_variants.contains_key("test"));
752        
753        parser.reset_to_defaults();
754        assert!(!parser.custom_variants.contains_key("test"));
755        assert!(parser.variants.contains_key("hover"));
756    }
757}