tailwind_css_fixes/modules/background/image/
mod.rs

1use super::*;
2
3#[doc=include_str!("readme.md")]
4#[derive(Clone, Debug)]
5pub struct TailwindBackgroundImage {
6    // The type of image/gradient
7    kind: BgImageKind,
8    // The value (e.g., direction, angle, url). Optional for defaults like `bg-radial`.
9    value: Option<UnitValue>,
10    // Flag for negative angles
11    is_negative: bool,
12}
13
14#[derive(Clone, Debug, PartialEq)]
15enum BgImageKind {
16    None,
17    Url,
18    Linear,
19    Radial,
20    Conic,
21}
22
23impl Display for TailwindBackgroundImage {
24    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
25        if self.is_negative {
26            write!(f, "-")?;
27        }
28
29        match (&self.kind, &self.value) {
30            (BgImageKind::None, _) => write!(f, "bg-none"),
31            (BgImageKind::Url, Some(val)) => write!(f, "bg-[url({})]", val),
32            (BgImageKind::Linear, Some(val)) => write!(f, "bg-linear-{}", val),
33            (BgImageKind::Radial, Some(val)) => write!(f, "bg-radial-{}", val),
34            (BgImageKind::Conic, Some(val)) => write!(f, "bg-conic-{}", val),
35            (BgImageKind::Linear, None) => write!(f, "bg-linear"),
36            (BgImageKind::Radial, None) => write!(f, "bg-radial"),
37            (BgImageKind::Conic, None) => write!(f, "bg-conic"),
38            _ => Ok(()), // Should not happen
39        }
40    }
41}
42
43impl TailwindInstance for TailwindBackgroundImage {
44    fn attributes(&self, _: &TailwindBuilder) -> CssAttributes {
45        match &self.kind {
46            BgImageKind::None => css_attributes! { "background-image" => "none" },
47            BgImageKind::Url => {
48                let url_value = self.value.as_ref().unwrap().to_string();
49                css_attributes! { "background-image" => format!("url({})", url_value) }
50            }
51            BgImageKind::Linear | BgImageKind::Radial | BgImageKind::Conic => {
52                let gradient_type = match self.kind {
53                    BgImageKind::Linear => "linear-gradient",
54                    BgImageKind::Radial => "radial-gradient",
55                    BgImageKind::Conic => "conic-gradient",
56                    _ => unreachable!(),
57                };
58
59                let direction_or_angle = match self.value.as_ref() {
60                    // No value, e.g., `bg-radial` -> `in oklab`
61                    None => "in oklab".to_string(),
62                    // Directional keyword, e.g., `to-t` -> `to top in oklab`
63                    Some(UnitValue::Keyword(k)) if k.starts_with("to-") => {
64                        let direction = k.strip_prefix("to-").unwrap_or(k);
65                        let mut full_direction = String::new();
66                        if direction.contains('t') { full_direction.push_str("top "); }
67                        if direction.contains('b') { full_direction.push_str("bottom "); }
68                        if direction.contains('l') { full_direction.push_str("left "); }
69                        if direction.contains('r') { full_direction.push_str("right "); }
70                        format!("to {} in oklab", full_direction.trim())
71                    }
72                    // An angle or other arbitrary value, e.g., `45deg`
73                    Some(value) => {
74                        // Get the inner value of an arbitrary, e.g., `45deg` from `[45deg]`
75                        let inner_value = match value {
76                             UnitValue::Arbitrary(a) => a.get_properties(),
77                             _ => value.to_string()
78                        };
79                    
80                        if self.is_negative {
81                            format!("calc({} * -1)", inner_value)
82                        } else {
83                            format!("{} in oklab", inner_value)
84                        }
85                    }
86                };
87
88                // For arbitrary angles, the position is added as a fallback variable
89                let image = match self.value.as_ref() {
90                     Some(UnitValue::Arbitrary(_)) | Some(UnitValue::Number {..}) | Some(UnitValue::Length(_)) => {
91                        format!("{}(var(--tw-gradient-stops, {}))", gradient_type, direction_or_angle)
92                     },
93                     _ => format!("{}(var(--tw-gradient-stops))", gradient_type)
94                };
95
96                css_attributes! {
97                    "--tw-gradient-position" => direction_or_angle,
98                    "background-image" => image
99                }
100            }
101        }
102    }
103}
104
105impl TailwindBackgroundImage {
106    pub fn parse(pattern: &[&str], arbitrary: &TailwindArbitrary, neg: Negative) -> Result<Self> {
107        let (kind, rest) = match pattern {
108            ["none"] => (BgImageKind::None, &pattern[1..]),
109            ["gradient", rest @ ..] | ["linear", rest @ ..] => (BgImageKind::Linear, rest),
110            ["radial", rest @ ..] => (BgImageKind::Radial, rest),
111            ["conic", rest @ ..] => (BgImageKind::Conic, rest),
112            [] if arbitrary.as_str().starts_with("url(") => (BgImageKind::Url, pattern),
113            _ => return syntax_error!("Unknown background-image pattern: {}", pattern.join("-")),
114        };
115
116        let value = match kind {
117            BgImageKind::None => None,
118            BgImageKind::Url => {
119                let url = arbitrary.as_str().strip_prefix("url(").and_then(|s| s.strip_suffix(')')).unwrap_or("");
120                Some(UnitValue::Keyword(url.to_string()))
121            }
122            BgImageKind::Linear 
123            | BgImageKind::Radial 
124            | BgImageKind::Conic => {
125                if rest.is_empty() && arbitrary.is_none() {
126                    // This is a default gradient, like `bg-radial`
127                    None
128                } else {
129                    let joined = rest.join("-");
130                    if joined.starts_with("to-") {
131                        // It's a directional keyword like `to-t` or `to-b-r`
132                        Some(UnitValue::Keyword(joined.replace("-b-r", "-br").replace("-b-l", "-bl").replace("-t-r", "-tr").replace("-t-l", "-tl")))
133                    } else {
134                        // Otherwise, parse as a number, angle, or other length value.
135                        // The `is_length: false` flag tells the parser to prefer angles over lengths.
136                        Some(UnitValue::negative_parser("bg-image", |_| false, false, false, false)(rest, arbitrary, neg)?)
137                    }
138                }
139            }
140        };
141
142        Ok(Self { kind, value, is_negative: neg.0 })
143    }
144}