Skip to main content

takumi_css/style/tw/
mod.rs

1mod builder;
2pub mod map;
3pub mod parser;
4
5use builder::TailwindDeclarationBuilder;
6pub use builder::{TwGradientState, TwGradientType};
7
8use std::{borrow::Cow, cmp::Ordering, str::FromStr};
9
10use cssparser::match_ignore_ascii_case;
11use serde::{Deserialize, Deserializer, de::Error as DeError};
12
13use crate::{
14  Viewport,
15  style::{
16    tw::{
17      map::{FIXED_PROPERTIES, PREFIX_PARSERS},
18      parser::*,
19    },
20    *,
21  },
22};
23
24/// Tailwind v4 `--spacing` (rem per unit). Prefer [`Length::from_spacing`].
25pub const TW_VAR_SPACING: f32 = 0.25;
26
27/// Represents a collection of tailwind properties.
28#[derive(Debug, Clone, PartialEq)]
29pub struct TailwindValues {
30  inner: Vec<TailwindValue>,
31}
32
33impl FromStr for TailwindValues {
34  type Err = String;
35
36  fn from_str(s: &str) -> Result<Self, Self::Err> {
37    let mut collected = s
38      .split_whitespace()
39      .filter_map(TailwindValue::parse)
40      .collect::<Vec<_>>();
41
42    // sort in reverse order by is important, then has breakpoint, then rest is last.
43    // Stable sort so equal-priority utilities keep source order (later one wins).
44    collected.sort_by(|a, b| {
45      // Not important comes before important
46      if !a.important && b.important {
47        return Ordering::Less;
48      }
49
50      if a.important && !b.important {
51        return Ordering::Greater;
52      }
53
54      // No breakpoint comes before breakpoint
55      match (&a.breakpoint, &b.breakpoint) {
56        (None, Some(_)) => Ordering::Less,
57        (Some(_), None) => Ordering::Greater,
58        _ => Ordering::Equal,
59      }
60    });
61
62    Ok(TailwindValues { inner: collected })
63  }
64}
65
66impl TailwindValues {
67  /// Collects resource URLs referenced by active Tailwind utilities for the given viewport.
68  pub fn resource_urls(&self, viewport: Viewport) -> impl Iterator<Item = &str> {
69    self
70      .inner
71      .iter()
72      .filter_map(move |value| value.resource_url(viewport))
73  }
74
75  #[inline(never)]
76  pub fn into_declaration_block(self, viewport: Viewport) -> StyleDeclarationBlock {
77    let mut builder = TailwindDeclarationBuilder::default();
78
79    for value in self.inner {
80      value.apply(&mut builder, viewport);
81    }
82
83    builder.finish()
84  }
85}
86
87impl<'de> Deserialize<'de> for TailwindValues {
88  fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
89  where
90    D: Deserializer<'de>,
91  {
92    let string = String::deserialize(deserializer)?;
93
94    TailwindValues::from_str(&string).map_err(D::Error::custom)
95  }
96}
97
98/// Represents a tailwind value.
99#[derive(Debug, Clone, PartialEq)]
100pub struct TailwindValue {
101  /// The tailwind property.
102  pub property: TailwindProperty,
103  /// The breakpoint.
104  pub breakpoint: Option<Breakpoint>,
105  /// Whether the value is important.
106  pub important: bool,
107}
108
109/// Splits a token at the first top-level variant `:`, mirroring Tailwind's
110/// `segment`: a `:` nested in brackets, quotes, or escaped by `\` is not a
111/// separator, so `url('https://…')` stays intact.
112fn split_variant(token: &str) -> Option<(&str, &str)> {
113  let bytes = token.as_bytes();
114  let mut stack: Vec<u8> = Vec::new();
115  let mut index = 0;
116  while index < bytes.len() {
117    match bytes[index] {
118      b'\\' => index += 1, // skip the escaped character
119      quote @ (b'\'' | b'"') => {
120        index += 1;
121        while index < bytes.len() && bytes[index] != quote {
122          index += if bytes[index] == b'\\' { 2 } else { 1 };
123        }
124      }
125      b'(' => stack.push(b')'),
126      b'[' => stack.push(b']'),
127      b'{' => stack.push(b'}'),
128      closing @ (b')' | b']' | b'}') => {
129        if stack.last() == Some(&closing) {
130          stack.pop();
131        }
132      }
133      b':' if stack.is_empty() => return Some((&token[..index], &token[index + 1..])),
134      _ => {}
135    }
136    index += 1;
137  }
138  None
139}
140
141impl TailwindValue {
142  fn resource_url(&self, viewport: Viewport) -> Option<&str> {
143    if let Some(breakpoint) = self.breakpoint
144      && !breakpoint.matches(viewport)
145    {
146      return None;
147    }
148
149    self.property.resource_url()
150  }
151
152  #[inline(never)]
153  fn apply(self, builder: &mut TailwindDeclarationBuilder, viewport: Viewport) {
154    if let Some(breakpoint) = self.breakpoint
155      && !breakpoint.matches(viewport)
156    {
157      return;
158    }
159
160    self.property.apply(builder, self.important);
161  }
162
163  /// Parse a tailwind value from a token.
164  pub fn parse(mut token: &str) -> Option<Self> {
165    let mut important = false;
166    let mut breakpoint = None;
167
168    // Breakpoint. sm:mt-0
169    if let Some((breakpoint_token, rest)) = split_variant(token) {
170      breakpoint = Some(Breakpoint::parse(breakpoint_token)?);
171      token = rest;
172    }
173
174    // Check for important flag. !mt-0
175    if let Some(stripped) = token.strip_prefix('!') {
176      important = true;
177      token = stripped;
178    }
179
180    // Check for important flag. mt-0!
181    if let Some(stripped) = token.strip_suffix('!') {
182      important = true;
183      token = stripped;
184    }
185
186    Some(TailwindValue {
187      property: TailwindProperty::parse(token)?,
188      breakpoint,
189      important,
190    })
191  }
192}
193
194/// Represents a breakpoint.
195#[derive(Debug, Clone, Copy, PartialEq)]
196pub struct Breakpoint(pub Length);
197
198impl Breakpoint {
199  /// Parse a breakpoint from a token.
200  pub fn parse(token: &str) -> Option<Self> {
201    match_ignore_ascii_case! {token,
202      "sm" => Some(Breakpoint(Length::Rem(40.0))),
203      "md" => Some(Breakpoint(Length::Rem(48.0))),
204      "lg" => Some(Breakpoint(Length::Rem(64.0))),
205      "xl" => Some(Breakpoint(Length::Rem(80.0))),
206      "2xl" => Some(Breakpoint(Length::Rem(96.0))),
207      _ => None,
208    }
209  }
210
211  /// Check if the breakpoint matches the viewport width.
212  pub fn matches(&self, viewport: Viewport) -> bool {
213    let Some(viewport_width) = viewport.size.width else {
214      return false;
215    };
216
217    let breakpoint_width = match self.0 {
218      Length::Rem(value) => value * viewport.font_size * viewport.device_pixel_ratio,
219      Length::Px(value) => value * viewport.device_pixel_ratio,
220      Length::Vw(value) => (value / 100.0) * viewport_width as f32,
221      _ => return false,
222    };
223
224    viewport_width >= breakpoint_width as u32
225  }
226}
227
228/// Represents a tailwind property.
229#[derive(Debug, Clone, PartialEq)]
230pub enum TailwindProperty {
231  /// `background-clip` property.
232  BackgroundClip(BackgroundClip),
233  /// `box-sizing` property.
234  BoxSizing(BoxSizing),
235  /// `flex-grow` property.
236  FlexGrow(FlexGrow),
237  /// `flex-shrink` property.
238  FlexShrink(FlexGrow),
239  /// `aspect-ratio` property.
240  Aspect(AspectRatio),
241  /// `align-items` property.
242  Items(AlignItems),
243  /// `justify-content` property.
244  Justify(JustifyContent),
245  /// `align-content` property.
246  Content(JustifyContent),
247  /// `align-self` property.
248  JustifySelf(AlignItems),
249  /// `justify-items` property.
250  JustifyItems(AlignItems),
251  /// `flex-direction` property.
252  AlignSelf(AlignItems),
253  /// `flex-direction` property.
254  FlexDirection(FlexDirection),
255  /// `flex-wrap` property.
256  FlexWrap(FlexWrap),
257  /// `flex` property.
258  Flex(Flex),
259  /// `flex-basis` property.
260  FlexBasis(Length),
261  /// `overflow` property.
262  Overflow(Overflow),
263  /// `overflow-x` property.
264  OverflowX(Overflow),
265  /// `overflow-y` property.
266  OverflowY(Overflow),
267  /// `position` property.
268  Position(Position),
269  /// `font-style` property.
270  FontStyle(FontStyle),
271  /// `font-weight` property.
272  FontWeight(FontWeight),
273  /// `font-stretch` property.
274  FontStretch(FontStretch),
275  /// `font-family` property.
276  FontFamily(FontFamily),
277  /// `line-clamp` property.
278  LineClamp(LineClamp),
279  /// `text-overflow` property.
280  TextOverflow(TextOverflow),
281  /// `text-wrap` property.
282  TextWrap(TextWrap),
283  /// `white-space` property.
284  WhiteSpace(WhiteSpace),
285  /// `word-break` property.
286  WordBreak(WordBreak),
287  /// `overflow-wrap` property.
288  OverflowWrap(OverflowWrap),
289  /// Set `text-overflow: ellipsis`, `white-space: nowrap` and `overflow: hidden`.
290  Truncate,
291  /// `text-align` property.
292  TextAlign(TextAlign),
293  /// `text-decoration` property.
294  TextDecorationLine(TextDecorationLines),
295  /// `text-decoration-color` property.
296  TextDecorationColor(ColorInput),
297  /// `text-decoration-thickness` property.
298  TextDecorationThickness(TextDecorationThickness),
299  /// `text-transform` property.
300  TextTransform(TextTransform),
301  /// `width` and `height` property.
302  Size(Length),
303  /// `width` property.
304  Width(Length),
305  /// `height` property.
306  Height(Length),
307  /// `min-width` property.
308  MinWidth(Length),
309  /// `min-height` property.
310  MinHeight(Length),
311  /// `max-width` property.
312  MaxWidth(Length),
313  /// `max-height` property.
314  MaxHeight(Length),
315  /// `box-shadow` property.
316  Shadow(BoxShadow),
317  /// `box-shadow` color override.
318  ShadowColor(ColorInput),
319  /// `display` property.
320  Display(Display),
321  /// `object-position` property.
322  ObjectPosition(ObjectPosition),
323  /// `object-fit` property.
324  ObjectFit(ObjectFit),
325  /// `background-position` property.
326  BackgroundPosition(BackgroundPosition),
327  /// `background-size` property.
328  BackgroundSize(BackgroundSize),
329  /// `background-repeat` property.
330  BackgroundRepeat(BackgroundRepeat),
331  /// `background-image` property.
332  BackgroundImage(BackgroundImage),
333  /// `mask-image` property.
334  MaskImage(BackgroundImage),
335  /// `gap` property.
336  Gap(LengthDefaultsToZero),
337  /// `column-gap` property.
338  GapX(LengthDefaultsToZero),
339  /// `row-gap` property.
340  GapY(LengthDefaultsToZero),
341  /// `grid-auto-flow` property.
342  GridAutoFlow(GridAutoFlow),
343  /// `grid-auto-columns` property.
344  GridAutoColumns(GridTrackSize),
345  /// `grid-auto-rows` property.
346  GridAutoRows(GridTrackSize),
347  /// `grid-column` property.
348  GridColumn(GridLine),
349  /// `grid-row` property.
350  GridRow(GridLine),
351  /// `grid-column: span <number> / span <number>` property.
352  GridColumnSpan(GridPlacementSpan),
353  /// `grid-row: span <number> / span <number>` property.
354  GridRowSpan(GridPlacementSpan),
355  /// `grid-column-start` property.
356  GridColumnStart(GridPlacement),
357  /// `grid-column-end` property.
358  GridColumnEnd(GridPlacement),
359  /// `grid-row-start` property.
360  GridRowStart(GridPlacement),
361  /// `grid-row-end` property.
362  GridRowEnd(GridPlacement),
363  /// `grid-template-columns` property.
364  GridTemplateColumns(TwGridTemplate),
365  /// `grid-template-rows` property.
366  GridTemplateRows(TwGridTemplate),
367  /// `letter-spacing` property.
368  LetterSpacing(TwLetterSpacing),
369  /// Tailwind `border` utility (`border-width: 1px; border-style: solid`).
370  BorderDefault,
371  /// `border-width` property.
372  BorderWidth(TwBorderWidth),
373  /// `border-style` property.
374  BorderStyle(BorderStyle),
375  /// `color` property.
376  Color(ColorInput),
377  /// `opacity` property.
378  Opacity(PercentageNumber),
379  /// `background-color` property.
380  BackgroundColor(ColorDefaultsToTransparent),
381  /// `border-color` property.
382  BorderColor(ColorInput),
383  /// `border-top-width` property.
384  BorderTopWidth(TwBorderWidth),
385  /// `border-right-width` property.
386  BorderRightWidth(TwBorderWidth),
387  /// `border-bottom-width` property.
388  BorderBottomWidth(TwBorderWidth),
389  /// `border-left-width` property.
390  BorderLeftWidth(TwBorderWidth),
391  /// `border-inline-width` property.
392  BorderXWidth(TwBorderWidth),
393  /// `border-block-width` property.
394  BorderYWidth(TwBorderWidth),
395  /// `border-top-color` property.
396  BorderTopColor(ColorInput),
397  /// `border-right-color` property.
398  BorderRightColor(ColorInput),
399  /// `border-bottom-color` property.
400  BorderBottomColor(ColorInput),
401  /// `border-left-color` property.
402  BorderLeftColor(ColorInput),
403  /// `border-inline-color` property.
404  BorderXColor(ColorInput),
405  /// `border-block-color` property.
406  BorderYColor(ColorInput),
407  /// Tailwind `outline` utility (`outline-width: 1px; outline-style: solid`).
408  OutlineDefault,
409  /// `outline-width` property.
410  OutlineWidth(TwBorderWidth),
411  /// `outline-color` property.
412  OutlineColor(ColorInput),
413  /// `outline-style` property.
414  OutlineStyle(BorderStyle),
415  /// `outline-offset` property.
416  OutlineOffset(TwBorderWidth),
417  /// `border-radius` property.
418  Rounded(TwRounded),
419  /// `border-top-left-radius` property.
420  RoundedTopLeft(TwRounded),
421  /// `border-top-right-radius` property.
422  RoundedTopRight(TwRounded),
423  /// `border-bottom-right-radius` property.
424  RoundedBottomRight(TwRounded),
425  /// `border-bottom-left-radius` property.
426  RoundedBottomLeft(TwRounded),
427  /// `border-top-left-radius`, `border-top-right-radius` property.
428  RoundedTop(TwRounded),
429  /// `border-top-right-radius`, `border-bottom-right-radius` property.
430  RoundedRight(TwRounded),
431  /// `border-bottom-left-radius`, `border-bottom-right-radius` property.
432  RoundedBottom(TwRounded),
433  /// `border-top-left-radius`, `border-bottom-left-radius` property.
434  RoundedLeft(TwRounded),
435  /// `font-size` property.
436  FontSize(TwFontSize),
437  /// `line-height` property.
438  LineHeight(LineHeight),
439  /// `translate` property.
440  Translate(Length),
441  /// `translate-x` property.
442  TranslateX(Length),
443  /// `translate-y` property.
444  TranslateY(Length),
445  /// `rotate` property.
446  Rotate(Angle),
447  /// `scale` property.
448  Scale(PercentageNumber),
449  /// `scale-x` property.
450  ScaleX(PercentageNumber),
451  /// `scale-y` property.
452  ScaleY(PercentageNumber),
453  /// `transform-origin` property.
454  TransformOrigin(TransformOrigin),
455  /// `margin` property.
456  Margin(LengthDefaultsToZero),
457  /// `margin-inline` property.
458  MarginX(LengthDefaultsToZero),
459  /// `margin-block` property.
460  MarginY(LengthDefaultsToZero),
461  /// `margin-top` property.
462  MarginTop(LengthDefaultsToZero),
463  /// `margin-right` property.
464  MarginRight(LengthDefaultsToZero),
465  /// `margin-bottom` property.
466  MarginBottom(LengthDefaultsToZero),
467  /// `margin-left` property.
468  MarginLeft(LengthDefaultsToZero),
469  MarginInlineStart(LengthDefaultsToZero),
470  MarginInlineEnd(LengthDefaultsToZero),
471  /// `padding` property.
472  Padding(LengthDefaultsToZero),
473  /// `padding-inline` property.
474  PaddingX(LengthDefaultsToZero),
475  /// `padding-block` property.
476  PaddingY(LengthDefaultsToZero),
477  /// `padding-top` property.
478  PaddingTop(LengthDefaultsToZero),
479  /// `padding-right` property.
480  PaddingRight(LengthDefaultsToZero),
481  /// `padding-bottom` property.
482  PaddingBottom(LengthDefaultsToZero),
483  /// `padding-left` property.
484  PaddingLeft(LengthDefaultsToZero),
485  PaddingInlineStart(LengthDefaultsToZero),
486  PaddingInlineEnd(LengthDefaultsToZero),
487  /// `inset` property.
488  Inset(Length),
489  /// `inset-inline` property.
490  InsetX(Length),
491  /// `inset-block` property.
492  InsetY(Length),
493  /// `top` property.
494  Top(Length),
495  /// `right` property.
496  Right(Length),
497  /// `bottom` property.
498  Bottom(Length),
499  /// `left` property.
500  Left(Length),
501  /// `filter: blur()` property.
502  Blur(TwBlur),
503  /// `filter: brightness()` property.
504  Brightness(PercentageNumber),
505  /// `filter: contrast()` property.
506  Contrast(PercentageNumber),
507  /// `filter: drop-shadow()` property.
508  DropShadow(TextShadow),
509  /// `filter: grayscale()` property.
510  Grayscale(PercentageNumber),
511  /// `filter: hue-rotate()` property.
512  HueRotate(Angle),
513  /// `filter: invert()` property.
514  Invert(PercentageNumber),
515  /// `filter: saturate()` property.
516  Saturate(PercentageNumber),
517  /// `filter: sepia()` property.
518  Sepia(PercentageNumber),
519  /// `filter` property.
520  Filter(Filters),
521  /// `backdrop-filter: blur()` property.
522  BackdropBlur(TwBlur),
523  /// `backdrop-filter: brightness()` property.
524  BackdropBrightness(PercentageNumber),
525  /// `backdrop-filter: contrast()` property.
526  BackdropContrast(PercentageNumber),
527  /// `backdrop-filter: grayscale()` property.
528  BackdropGrayscale(PercentageNumber),
529  /// `backdrop-filter: hue-rotate()` property.
530  BackdropHueRotate(Angle),
531  /// `backdrop-filter: invert()` property.
532  BackdropInvert(PercentageNumber),
533  /// `backdrop-filter: opacity()` property.
534  BackdropOpacity(PercentageNumber),
535  /// `backdrop-filter: saturate()` property.
536  BackdropSaturate(PercentageNumber),
537  /// `backdrop-filter: sepia()` property.
538  BackdropSepia(PercentageNumber),
539  /// `backdrop-filter` property.
540  BackdropFilter(Filters),
541  /// `text-shadow` property.
542  TextShadow(TextShadow),
543  /// `text-shadow` color override.
544  TextShadowColor(ColorInput),
545  ShadowList(&'static [BoxShadow]),
546  TextShadowList(&'static [TextShadow]),
547  /// `isolation` property.
548  Isolation(Isolation),
549  /// `mix-blend-mode` property.
550  MixBlendMode(BlendMode),
551  /// `background-blend-mode` property.
552  BackgroundBlendMode(BlendMode),
553  /// `visibility` property.
554  Visibility(Visibility),
555  /// `vertical-align` property.
556  VerticalAlign(VerticalAlign),
557  /// `animation` shorthand.
558  Animation(Animations),
559  /// `bg-linear` property.
560  BgLinearAngle(Angle),
561  /// `bg-radial` property.
562  BgRadial,
563  /// `bg-conic` property.
564  BgConicAngle(Angle),
565  /// `from` property.
566  GradientFrom(ColorInput),
567  /// `to` property.
568  GradientTo(ColorInput),
569  /// `via` property.
570  GradientVia(ColorInput),
571  GradientFromPosition(Length),
572  GradientViaPosition(Length),
573  GradientToPosition(Length),
574}
575
576fn extract_arbitrary_value(suffix: &str) -> Option<Cow<'_, str>> {
577  let value = suffix.strip_prefix('[')?.strip_suffix(']')?;
578  Some(decode_arbitrary_value(value))
579}
580
581enum FnKind {
582  Url,
583  VarTheme,
584  Other,
585}
586
587impl FnKind {
588  fn from_name(name: &str) -> FnKind {
589    if name == "url" || name.ends_with("_url") {
590      FnKind::Url
591    } else if matches!(name, "var" | "theme") || name.ends_with("_var") || name.ends_with("_theme")
592    {
593      FnKind::VarTheme
594    } else {
595      FnKind::Other
596    }
597  }
598}
599
600fn is_ident_byte(byte: u8) -> bool {
601  byte.is_ascii_alphanumeric() || byte == b'-' || byte == b'_'
602}
603
604/// Mirrors Tailwind's `decodeArbitraryValue`: `_` becomes a space and `\_` a
605/// literal `_`, but underscores inside `url(...)` and the first argument of
606/// `var()`/`theme()` are preserved.
607fn decode_arbitrary_value(value: &str) -> Cow<'_, str> {
608  if !value.contains('_') {
609    return Cow::Borrowed(value);
610  }
611
612  let bytes = value.as_bytes();
613  let mut out = String::with_capacity(value.len());
614  let mut stack: Vec<(FnKind, bool)> = Vec::new();
615  let mut ident_start = 0;
616  let mut index = 0;
617  while index < bytes.len() {
618    let byte = bytes[index];
619    if byte == b'\\' && bytes.get(index + 1) == Some(&b'_') {
620      out.push('_');
621      index += 2;
622      ident_start = index;
623    } else if byte == b'_' {
624      let preserved = stack.iter().any(|(kind, _)| matches!(kind, FnKind::Url))
625        || matches!(stack.last(), Some((FnKind::VarTheme, true)));
626      out.push(if preserved { '_' } else { ' ' });
627      index += 1;
628    } else if byte == b'(' {
629      stack.push((FnKind::from_name(&value[ident_start..index]), true));
630      out.push('(');
631      index += 1;
632      ident_start = index;
633    } else if byte == b')' {
634      stack.pop();
635      out.push(')');
636      index += 1;
637      ident_start = index;
638    } else if byte == b',' {
639      if let Some((_, first_arg)) = stack.last_mut() {
640        *first_arg = false;
641      }
642      out.push(',');
643      index += 1;
644      ident_start = index;
645    } else if is_ident_byte(byte) {
646      out.push(byte as char);
647      index += 1;
648    } else {
649      let char_len = value[index..].chars().next().map_or(1, char::len_utf8);
650      out.push_str(&value[index..index + char_len]);
651      index += char_len;
652      ident_start = index;
653    }
654  }
655  Cow::Owned(out)
656}
657
658/// A trait for parsing tailwind properties.
659pub trait TailwindPropertyParser: Sized + for<'i> FromCss<'i> {
660  /// Parse a tailwind property from a token.
661  fn parse_tw(token: &str) -> Option<Self>;
662
663  /// Parse a tailwind property from a token, with support for arbitrary values.
664  fn parse_tw_with_arbitrary(token: &str) -> Option<Self> {
665    if let Some(value) = extract_arbitrary_value(token) {
666      return Self::from_str(&value).ok();
667    }
668
669    Self::parse_tw(token)
670  }
671}
672
673macro_rules! try_neg {
674  ($self:expr;
675    try_negative: $($neg:ident),+ $(,)?;
676    unary: $($un:ident),+ $(,)?;
677    grid: $($grid:ident),+ $(,)?
678  ) => {
679    Some(match $self {
680      $(TailwindProperty::$neg(v) => TailwindProperty::$neg(v.try_negative()?),)+
681      $(TailwindProperty::$un(v) => TailwindProperty::$un(-v),)+
682      $(TailwindProperty::$grid(p) => TailwindProperty::$grid(p.try_negative()?),)+
683      _ => return None,
684    })
685  };
686}
687
688impl TailwindProperty {
689  fn try_neg(self) -> Option<Self> {
690    try_neg!(self;
691      try_negative:
692        Margin, MarginX, MarginY, MarginTop, MarginRight, MarginBottom, MarginLeft,
693        MarginInlineStart, MarginInlineEnd, Inset, InsetX, InsetY, Top, Right, Bottom, Left,
694        Translate, TranslateX, TranslateY;
695      unary: Scale, ScaleX, ScaleY, Rotate, LetterSpacing, HueRotate, BackdropHueRotate;
696      grid: GridColumnStart, GridColumnEnd, GridRowStart, GridRowEnd
697    )
698  }
699}
700
701macro_rules! push_decl {
702  ($builder:expr, $important:expr $(, $property:ident($value:expr))* $(,)?) => {{
703    $(
704      $builder.push(StyleDeclaration::$property($value), $important);
705    )*
706  }};
707}
708
709macro_rules! rounded_corners {
710  ($builder:expr, $important:expr, $rounded:expr $(, $corner:ident)+ $(,)?) => {{
711    let value = SpacePair::from_single($rounded.0);
712    push_decl!($builder, $important $(, $corner(value))+);
713  }};
714}
715
716impl TailwindProperty {
717  fn resource_url(&self) -> Option<&str> {
718    match self {
719      TailwindProperty::BackgroundImage(BackgroundImage::Url(url))
720      | TailwindProperty::MaskImage(BackgroundImage::Url(url)) => Some(url.as_ref()),
721      _ => None,
722    }
723  }
724
725  /// Parse a single tailwind property from a token.
726  pub fn parse(token: &str) -> Option<TailwindProperty> {
727    // Check fixed properties first
728    if let Some(property) = FIXED_PROPERTIES.get(token) {
729      return Some(property.clone());
730    }
731
732    if let Some(stripped) = token.strip_prefix('-') {
733      return Self::parse_prefix_suffix(stripped).and_then(Self::try_neg);
734    }
735
736    Self::parse_prefix_suffix(token)
737  }
738
739  fn parse_prefix_suffix(token: &str) -> Option<TailwindProperty> {
740    let bytes = token.as_bytes();
741
742    for dash_pos in (0..bytes.len()).rev() {
743      if bytes[dash_pos] != b'-' {
744        continue;
745      }
746
747      let prefix = &token[..dash_pos];
748      let Some(parsers) = PREFIX_PARSERS.get(prefix) else {
749        continue;
750      };
751
752      let suffix = &token[dash_pos + 1..];
753      for parser in *parsers {
754        if let Some(property) = parser.parse(suffix) {
755          return Some(property);
756        }
757      }
758    }
759
760    None
761  }
762
763  #[inline(never)]
764  fn apply(self, builder: &mut TailwindDeclarationBuilder, important: bool) {
765    match self {
766      TailwindProperty::BgLinearAngle(angle) => {
767        builder.gradient_state.gradient_type = TwGradientType::Linear;
768        builder.gradient_state.angle = Some(angle);
769        builder.gradient_state.important = important;
770      }
771      TailwindProperty::BgRadial => {
772        builder.gradient_state.gradient_type = TwGradientType::Radial;
773        builder.gradient_state.important = important;
774      }
775      TailwindProperty::BgConicAngle(angle) => {
776        builder.gradient_state.gradient_type = TwGradientType::Conic;
777        builder.gradient_state.angle = Some(angle);
778        builder.gradient_state.important = important;
779      }
780      TailwindProperty::GradientFrom(color) => {
781        builder.gradient_state.from = Some(color);
782        builder.gradient_state.important = important;
783      }
784      TailwindProperty::GradientTo(color) => {
785        builder.gradient_state.to = Some(color);
786        builder.gradient_state.important = important;
787      }
788      TailwindProperty::GradientVia(color) => {
789        builder.gradient_state.via = Some(color);
790        builder.gradient_state.important = important;
791      }
792      TailwindProperty::GradientFromPosition(pos) => {
793        builder.gradient_state.from_position = Some(pos);
794        builder.gradient_state.important = important;
795      }
796      TailwindProperty::GradientViaPosition(pos) => {
797        builder.gradient_state.via_position = Some(pos);
798        builder.gradient_state.important = important;
799      }
800      TailwindProperty::GradientToPosition(pos) => {
801        builder.gradient_state.to_position = Some(pos);
802        builder.gradient_state.important = important;
803      }
804      TailwindProperty::BackgroundClip(background_clip) => {
805        push_decl!(builder, important, background_clip(background_clip));
806      }
807      TailwindProperty::Gap(gap) => {
808        push_decl!(builder, important, row_gap(gap), column_gap(gap));
809      }
810      TailwindProperty::GapX(gap_x) => push_decl!(builder, important, column_gap(gap_x)),
811      TailwindProperty::GapY(gap_y) => push_decl!(builder, important, row_gap(gap_y)),
812      TailwindProperty::BoxSizing(box_sizing) => {
813        push_decl!(builder, important, box_sizing(box_sizing))
814      }
815      TailwindProperty::FlexGrow(flex_grow) => {
816        push_decl!(builder, important, flex_grow(Some(flex_grow)))
817      }
818      TailwindProperty::FlexShrink(flex_shrink) => {
819        push_decl!(builder, important, flex_shrink(Some(flex_shrink)))
820      }
821      TailwindProperty::Aspect(ratio) => push_decl!(builder, important, aspect_ratio(ratio)),
822      TailwindProperty::Items(align_items) => {
823        push_decl!(builder, important, align_items(align_items))
824      }
825      TailwindProperty::Justify(justify_content) => {
826        push_decl!(builder, important, justify_content(justify_content))
827      }
828      TailwindProperty::Content(align_content) => {
829        push_decl!(builder, important, align_content(align_content))
830      }
831      TailwindProperty::AlignSelf(align_self) => {
832        push_decl!(builder, important, align_self(align_self))
833      }
834      TailwindProperty::FlexDirection(flex_direction) => {
835        push_decl!(builder, important, flex_direction(flex_direction))
836      }
837      TailwindProperty::FlexWrap(flex_wrap) => push_decl!(builder, important, flex_wrap(flex_wrap)),
838      TailwindProperty::Flex(flex) => {
839        push_decl!(
840          builder,
841          important,
842          flex_grow(Some(FlexGrow(flex.grow))),
843          flex_shrink(Some(FlexGrow(flex.shrink))),
844          flex_basis(Some(flex.basis))
845        );
846      }
847      TailwindProperty::FlexBasis(flex_basis) => {
848        push_decl!(builder, important, flex_basis(Some(flex_basis)))
849      }
850      TailwindProperty::Overflow(overflow) => {
851        push_decl!(
852          builder,
853          important,
854          overflow_x(overflow),
855          overflow_y(overflow)
856        );
857      }
858      TailwindProperty::Position(position) => push_decl!(builder, important, position(position)),
859      TailwindProperty::FontStyle(font_style) => {
860        push_decl!(builder, important, font_style(font_style))
861      }
862      TailwindProperty::FontWeight(font_weight) => {
863        push_decl!(builder, important, font_weight(font_weight))
864      }
865      TailwindProperty::FontStretch(font_stretch) => {
866        push_decl!(builder, important, font_stretch(font_stretch))
867      }
868      TailwindProperty::FontFamily(font_family) => {
869        push_decl!(builder, important, font_family(font_family))
870      }
871      TailwindProperty::LineClamp(line_clamp) => {
872        push_decl!(builder, important, line_clamp(Some(line_clamp)))
873      }
874      TailwindProperty::TextAlign(text_align) => {
875        push_decl!(builder, important, text_align(text_align))
876      }
877      TailwindProperty::TextDecorationLine(text_decoration) => push_decl!(
878        builder,
879        important,
880        text_decoration_line(Some(text_decoration))
881      ),
882      TailwindProperty::TextDecorationColor(color_input) => {
883        push_decl!(builder, important, text_decoration_color(color_input))
884      }
885      TailwindProperty::TextDecorationThickness(thickness) => {
886        push_decl!(builder, important, text_decoration_thickness(thickness))
887      }
888      TailwindProperty::TextTransform(text_transform) => {
889        push_decl!(builder, important, text_transform(text_transform))
890      }
891      TailwindProperty::Size(size) => {
892        push_decl!(builder, important, width(size), height(size));
893      }
894      TailwindProperty::Width(width) => push_decl!(builder, important, width(width)),
895      TailwindProperty::Height(height) => push_decl!(builder, important, height(height)),
896      TailwindProperty::MinWidth(min_width) => push_decl!(builder, important, min_width(min_width)),
897      TailwindProperty::MinHeight(min_height) => {
898        push_decl!(builder, important, min_height(min_height))
899      }
900      TailwindProperty::MaxWidth(max_width) => push_decl!(builder, important, max_width(max_width)),
901      TailwindProperty::MaxHeight(max_height) => {
902        push_decl!(builder, important, max_height(max_height))
903      }
904      TailwindProperty::Shadow(box_shadow) => builder.set_shadow_layers([box_shadow], important),
905      TailwindProperty::ShadowList(&[]) => builder.reset_shadow(important),
906      TailwindProperty::ShadowList(layers) => {
907        builder.set_shadow_layers(layers.iter().copied(), important)
908      }
909      TailwindProperty::ShadowColor(color) => builder.set_shadow_color(color, important),
910      TailwindProperty::Display(display) => {
911        push_decl!(builder, important, display(display));
912      }
913      TailwindProperty::OverflowX(overflow) => push_decl!(builder, important, overflow_x(overflow)),
914      TailwindProperty::OverflowY(overflow) => push_decl!(builder, important, overflow_y(overflow)),
915      TailwindProperty::ObjectPosition(background_position) => {
916        push_decl!(builder, important, object_position(background_position))
917      }
918      TailwindProperty::ObjectFit(object_fit) => {
919        push_decl!(builder, important, object_fit(object_fit))
920      }
921      TailwindProperty::BackgroundPosition(background_position) => push_decl!(
922        builder,
923        important,
924        background_position([background_position].into())
925      ),
926      TailwindProperty::BackgroundSize(background_size) => push_decl!(
927        builder,
928        important,
929        background_size([background_size].into())
930      ),
931      TailwindProperty::BackgroundRepeat(background_repeat) => push_decl!(
932        builder,
933        important,
934        background_repeat([background_repeat].into())
935      ),
936      TailwindProperty::BackgroundImage(background_image) => push_decl!(
937        builder,
938        important,
939        background_image(Some([background_image].into()))
940      ),
941      TailwindProperty::MaskImage(mask_image) => {
942        push_decl!(builder, important, mask_image(Some([mask_image].into())))
943      }
944      TailwindProperty::BorderDefault => {
945        push_decl!(
946          builder,
947          important,
948          border_top_width(Length::Px(1.0)),
949          border_right_width(Length::Px(1.0)),
950          border_bottom_width(Length::Px(1.0)),
951          border_left_width(Length::Px(1.0))
952        );
953      }
954      TailwindProperty::BorderWidth(tw_border_width) => {
955        push_decl!(
956          builder,
957          important,
958          border_top_width(tw_border_width.0),
959          border_right_width(tw_border_width.0),
960          border_bottom_width(tw_border_width.0),
961          border_left_width(tw_border_width.0)
962        );
963      }
964      TailwindProperty::BorderStyle(border_style) => {
965        push_decl!(
966          builder,
967          important,
968          border_top_style(border_style),
969          border_right_style(border_style),
970          border_bottom_style(border_style),
971          border_left_style(border_style)
972        )
973      }
974      TailwindProperty::JustifySelf(align_items) => {
975        push_decl!(builder, important, justify_self(align_items))
976      }
977      TailwindProperty::JustifyItems(align_items) => {
978        push_decl!(builder, important, justify_items(align_items))
979      }
980      TailwindProperty::Color(color_input) => push_decl!(builder, important, color(color_input)),
981      TailwindProperty::Opacity(percentage_number) => {
982        push_decl!(builder, important, opacity(percentage_number))
983      }
984      TailwindProperty::BackgroundColor(color_input) => {
985        push_decl!(builder, important, background_color(color_input))
986      }
987      TailwindProperty::BorderColor(color_input) => {
988        push_decl!(
989          builder,
990          important,
991          border_top_color(color_input),
992          border_right_color(color_input),
993          border_bottom_color(color_input),
994          border_left_color(color_input)
995        )
996      }
997      TailwindProperty::BorderTopWidth(tw_border_width) => {
998        push_decl!(builder, important, border_top_width(tw_border_width.0))
999      }
1000      TailwindProperty::BorderRightWidth(tw_border_width) => {
1001        push_decl!(builder, important, border_right_width(tw_border_width.0))
1002      }
1003      TailwindProperty::BorderBottomWidth(tw_border_width) => {
1004        push_decl!(builder, important, border_bottom_width(tw_border_width.0))
1005      }
1006      TailwindProperty::BorderLeftWidth(tw_border_width) => {
1007        push_decl!(builder, important, border_left_width(tw_border_width.0))
1008      }
1009      TailwindProperty::BorderXWidth(tw_border_width) => {
1010        push_decl!(
1011          builder,
1012          important,
1013          border_left_width(tw_border_width.0),
1014          border_right_width(tw_border_width.0)
1015        );
1016      }
1017      TailwindProperty::BorderYWidth(tw_border_width) => {
1018        push_decl!(
1019          builder,
1020          important,
1021          border_top_width(tw_border_width.0),
1022          border_bottom_width(tw_border_width.0)
1023        );
1024      }
1025      TailwindProperty::BorderTopColor(color_input) => {
1026        push_decl!(builder, important, border_top_color(color_input))
1027      }
1028      TailwindProperty::BorderRightColor(color_input) => {
1029        push_decl!(builder, important, border_right_color(color_input))
1030      }
1031      TailwindProperty::BorderBottomColor(color_input) => {
1032        push_decl!(builder, important, border_bottom_color(color_input))
1033      }
1034      TailwindProperty::BorderLeftColor(color_input) => {
1035        push_decl!(builder, important, border_left_color(color_input))
1036      }
1037      TailwindProperty::BorderXColor(color_input) => {
1038        push_decl!(
1039          builder,
1040          important,
1041          border_left_color(color_input),
1042          border_right_color(color_input)
1043        );
1044      }
1045      TailwindProperty::BorderYColor(color_input) => {
1046        push_decl!(
1047          builder,
1048          important,
1049          border_top_color(color_input),
1050          border_bottom_color(color_input)
1051        );
1052      }
1053      TailwindProperty::OutlineDefault => {
1054        push_decl!(
1055          builder,
1056          important,
1057          outline_width(Length::Px(1.0)),
1058          outline_style(BorderStyle::Solid)
1059        );
1060      }
1061      TailwindProperty::OutlineWidth(tw_border_width) => {
1062        push_decl!(builder, important, outline_width(tw_border_width.0))
1063      }
1064      TailwindProperty::OutlineColor(color_input) => {
1065        push_decl!(builder, important, outline_color(color_input))
1066      }
1067      TailwindProperty::OutlineStyle(outline_style) => {
1068        push_decl!(builder, important, outline_style(outline_style))
1069      }
1070      TailwindProperty::OutlineOffset(outline_offset) => {
1071        push_decl!(builder, important, outline_offset(outline_offset.0))
1072      }
1073      TailwindProperty::Rounded(rounded) => rounded_corners!(
1074        builder,
1075        important,
1076        rounded,
1077        border_top_left_radius,
1078        border_top_right_radius,
1079        border_bottom_right_radius,
1080        border_bottom_left_radius
1081      ),
1082      TailwindProperty::VerticalAlign(vertical_align) => {
1083        push_decl!(builder, important, vertical_align(vertical_align))
1084      }
1085      TailwindProperty::RoundedTopLeft(rounded) => {
1086        rounded_corners!(builder, important, rounded, border_top_left_radius)
1087      }
1088      TailwindProperty::RoundedTopRight(rounded) => {
1089        rounded_corners!(builder, important, rounded, border_top_right_radius)
1090      }
1091      TailwindProperty::RoundedBottomRight(rounded) => {
1092        rounded_corners!(builder, important, rounded, border_bottom_right_radius)
1093      }
1094      TailwindProperty::RoundedBottomLeft(rounded) => {
1095        rounded_corners!(builder, important, rounded, border_bottom_left_radius)
1096      }
1097      TailwindProperty::RoundedTop(rounded) => rounded_corners!(
1098        builder,
1099        important,
1100        rounded,
1101        border_top_left_radius,
1102        border_top_right_radius
1103      ),
1104      TailwindProperty::RoundedRight(rounded) => rounded_corners!(
1105        builder,
1106        important,
1107        rounded,
1108        border_top_right_radius,
1109        border_bottom_right_radius
1110      ),
1111      TailwindProperty::RoundedBottom(rounded) => rounded_corners!(
1112        builder,
1113        important,
1114        rounded,
1115        border_bottom_left_radius,
1116        border_bottom_right_radius
1117      ),
1118      TailwindProperty::RoundedLeft(rounded) => rounded_corners!(
1119        builder,
1120        important,
1121        rounded,
1122        border_top_left_radius,
1123        border_bottom_left_radius
1124      ),
1125      TailwindProperty::TextOverflow(text_overflow) => {
1126        push_decl!(builder, important, text_overflow(text_overflow))
1127      }
1128      TailwindProperty::Truncate => {
1129        push_decl!(
1130          builder,
1131          important,
1132          text_overflow(TextOverflow::Ellipsis),
1133          text_wrap_mode(TextWrapMode::NoWrap),
1134          white_space_collapse(WhiteSpaceCollapse::Collapse),
1135          overflow_x(Overflow::Hidden),
1136          overflow_y(Overflow::Hidden)
1137        );
1138      }
1139      TailwindProperty::TextWrap(text_wrap) => {
1140        push_decl!(
1141          builder,
1142          important,
1143          text_wrap_mode(text_wrap.mode),
1144          text_wrap_style(text_wrap.style)
1145        );
1146      }
1147      TailwindProperty::WhiteSpace(white_space) => {
1148        push_decl!(
1149          builder,
1150          important,
1151          text_wrap_mode(white_space.text_wrap_mode),
1152          white_space_collapse(white_space.white_space_collapse)
1153        );
1154      }
1155      TailwindProperty::WordBreak(word_break) => {
1156        push_decl!(builder, important, word_break(word_break))
1157      }
1158      TailwindProperty::Isolation(isolation) => {
1159        push_decl!(builder, important, isolation(isolation))
1160      }
1161      TailwindProperty::MixBlendMode(blend_mode) => {
1162        push_decl!(builder, important, mix_blend_mode(blend_mode))
1163      }
1164      TailwindProperty::BackgroundBlendMode(blend_mode) => push_decl!(
1165        builder,
1166        important,
1167        background_blend_mode([blend_mode].into())
1168      ),
1169      TailwindProperty::OverflowWrap(overflow_wrap) => {
1170        push_decl!(builder, important, overflow_wrap(overflow_wrap))
1171      }
1172      TailwindProperty::FontSize(font_size) => {
1173        push_decl!(builder, important, font_size(font_size.font_size));
1174        if let Some(line_height) = font_size.line_height {
1175          push_decl!(builder, important, line_height(line_height));
1176        }
1177      }
1178      TailwindProperty::LineHeight(line_height) => {
1179        push_decl!(builder, important, line_height(line_height))
1180      }
1181      TailwindProperty::Translate(length) => {
1182        builder
1183          .transform_state
1184          .set_translate(SpacePair::from_single(length), important);
1185      }
1186      TailwindProperty::TranslateX(length) => {
1187        builder.transform_state.translate_mut(important).x = length;
1188      }
1189      TailwindProperty::TranslateY(length) => {
1190        builder.transform_state.translate_mut(important).y = length;
1191      }
1192      TailwindProperty::Rotate(angle) => push_decl!(builder, important, rotate(Some(angle))),
1193      TailwindProperty::Scale(percentage_number) => {
1194        builder
1195          .transform_state
1196          .set_scale(SpacePair::from_single(percentage_number), important);
1197      }
1198      TailwindProperty::ScaleX(percentage_number) => {
1199        builder.transform_state.scale_mut(important).x = percentage_number;
1200      }
1201      TailwindProperty::ScaleY(percentage_number) => {
1202        builder.transform_state.scale_mut(important).y = percentage_number;
1203      }
1204      TailwindProperty::TransformOrigin(background_position) => {
1205        push_decl!(builder, important, transform_origin(background_position))
1206      }
1207      TailwindProperty::Margin(length) => {
1208        push_decl!(
1209          builder,
1210          important,
1211          margin_top(length),
1212          margin_right(length),
1213          margin_bottom(length),
1214          margin_left(length)
1215        );
1216      }
1217      TailwindProperty::MarginX(length) => {
1218        push_decl!(
1219          builder,
1220          important,
1221          margin_left(length),
1222          margin_right(length)
1223        );
1224      }
1225      TailwindProperty::MarginY(length) => {
1226        push_decl!(
1227          builder,
1228          important,
1229          margin_top(length),
1230          margin_bottom(length)
1231        );
1232      }
1233      TailwindProperty::MarginTop(length) => push_decl!(builder, important, margin_top(length)),
1234      TailwindProperty::MarginRight(length) => push_decl!(builder, important, margin_right(length)),
1235      TailwindProperty::MarginBottom(length) => {
1236        push_decl!(builder, important, margin_bottom(length))
1237      }
1238      TailwindProperty::MarginLeft(length) => push_decl!(builder, important, margin_left(length)),
1239      TailwindProperty::MarginInlineStart(length) => {
1240        push_decl!(builder, important, margin_inline_start(length))
1241      }
1242      TailwindProperty::MarginInlineEnd(length) => {
1243        push_decl!(builder, important, margin_inline_end(length))
1244      }
1245      TailwindProperty::Padding(length) => {
1246        push_decl!(
1247          builder,
1248          important,
1249          padding_top(length),
1250          padding_right(length),
1251          padding_bottom(length),
1252          padding_left(length)
1253        );
1254      }
1255      TailwindProperty::PaddingX(length) => {
1256        push_decl!(
1257          builder,
1258          important,
1259          padding_left(length),
1260          padding_right(length)
1261        );
1262      }
1263      TailwindProperty::PaddingY(length) => {
1264        push_decl!(
1265          builder,
1266          important,
1267          padding_top(length),
1268          padding_bottom(length)
1269        );
1270      }
1271      TailwindProperty::PaddingTop(length) => push_decl!(builder, important, padding_top(length)),
1272      TailwindProperty::PaddingRight(length) => {
1273        push_decl!(builder, important, padding_right(length))
1274      }
1275      TailwindProperty::PaddingBottom(length) => {
1276        push_decl!(builder, important, padding_bottom(length))
1277      }
1278      TailwindProperty::PaddingLeft(length) => push_decl!(builder, important, padding_left(length)),
1279      TailwindProperty::PaddingInlineStart(length) => {
1280        push_decl!(builder, important, padding_inline_start(length))
1281      }
1282      TailwindProperty::PaddingInlineEnd(length) => {
1283        push_decl!(builder, important, padding_inline_end(length))
1284      }
1285      TailwindProperty::Inset(length) => {
1286        push_decl!(
1287          builder,
1288          important,
1289          top(length),
1290          right(length),
1291          bottom(length),
1292          left(length)
1293        );
1294      }
1295      TailwindProperty::InsetX(length) => {
1296        push_decl!(builder, important, left(length), right(length));
1297      }
1298      TailwindProperty::InsetY(length) => {
1299        push_decl!(builder, important, top(length), bottom(length));
1300      }
1301      TailwindProperty::Top(length) => push_decl!(builder, important, top(length)),
1302      TailwindProperty::Right(length) => push_decl!(builder, important, right(length)),
1303      TailwindProperty::Bottom(length) => push_decl!(builder, important, bottom(length)),
1304      TailwindProperty::Left(length) => push_decl!(builder, important, left(length)),
1305      TailwindProperty::GridAutoColumns(grid_auto_size) => push_decl!(
1306        builder,
1307        important,
1308        grid_auto_columns(Some([grid_auto_size].into()))
1309      ),
1310      TailwindProperty::GridAutoRows(grid_auto_size) => push_decl!(
1311        builder,
1312        important,
1313        grid_auto_rows(Some([grid_auto_size].into()))
1314      ),
1315      TailwindProperty::GridColumn(tw_grid_span) => {
1316        builder.set_grid_column(tw_grid_span, important)
1317      }
1318      TailwindProperty::GridRow(tw_grid_span) => builder.set_grid_row(tw_grid_span, important),
1319      TailwindProperty::GridColumnStart(tw_grid_placement) => {
1320        let start = builder
1321          .grid_column
1322          .start
1323          .get_or_insert_with(GridPlacement::auto);
1324        *start = tw_grid_placement;
1325        builder.grid_column.start_important = important;
1326      }
1327      TailwindProperty::GridColumnEnd(tw_grid_placement) => {
1328        let end = builder
1329          .grid_column
1330          .end
1331          .get_or_insert_with(GridPlacement::auto);
1332        *end = tw_grid_placement;
1333        builder.grid_column.end_important = important;
1334      }
1335      TailwindProperty::GridRowStart(tw_grid_placement) => {
1336        let start = builder
1337          .grid_row
1338          .start
1339          .get_or_insert_with(GridPlacement::auto);
1340        *start = tw_grid_placement;
1341        builder.grid_row.start_important = important;
1342      }
1343      TailwindProperty::GridRowEnd(tw_grid_placement) => {
1344        let end = builder.grid_row.end.get_or_insert_with(GridPlacement::auto);
1345        *end = tw_grid_placement;
1346        builder.grid_row.end_important = important;
1347      }
1348      TailwindProperty::GridTemplateColumns(tw_grid_template) => push_decl!(
1349        builder,
1350        important,
1351        grid_template_columns(Some(tw_grid_template.0))
1352      ),
1353      TailwindProperty::GridTemplateRows(tw_grid_template) => push_decl!(
1354        builder,
1355        important,
1356        grid_template_rows(Some(tw_grid_template.0))
1357      ),
1358      TailwindProperty::LetterSpacing(tw_letter_spacing) => {
1359        push_decl!(builder, important, letter_spacing(tw_letter_spacing.0))
1360      }
1361      TailwindProperty::GridAutoFlow(grid_auto_flow) => {
1362        push_decl!(builder, important, grid_auto_flow(grid_auto_flow))
1363      }
1364      TailwindProperty::GridColumnSpan(grid_placement_span) => {
1365        builder.set_grid_column(GridLine::span(grid_placement_span), important)
1366      }
1367      TailwindProperty::GridRowSpan(grid_placement_span) => {
1368        builder.set_grid_row(GridLine::span(grid_placement_span), important)
1369      }
1370      TailwindProperty::Blur(tw_blur) => builder.push_filter(Filter::Blur(tw_blur.0), important),
1371      TailwindProperty::Brightness(percentage_number) => {
1372        builder.push_filter(Filter::Brightness(percentage_number), important)
1373      }
1374      TailwindProperty::Contrast(percentage_number) => {
1375        builder.push_filter(Filter::Contrast(percentage_number), important)
1376      }
1377      TailwindProperty::DropShadow(text_shadow) => {
1378        builder.push_filter(Filter::DropShadow(text_shadow), important)
1379      }
1380      TailwindProperty::Grayscale(percentage_number) => {
1381        builder.push_filter(Filter::Grayscale(percentage_number), important)
1382      }
1383      TailwindProperty::HueRotate(angle) => {
1384        builder.push_filter(Filter::HueRotate(angle), important)
1385      }
1386      TailwindProperty::Invert(percentage_number) => {
1387        builder.push_filter(Filter::Invert(percentage_number), important)
1388      }
1389      TailwindProperty::Saturate(percentage_number) => {
1390        builder.push_filter(Filter::Saturate(percentage_number), important)
1391      }
1392      TailwindProperty::Sepia(percentage_number) => {
1393        builder.push_filter(Filter::Sepia(percentage_number), important)
1394      }
1395      TailwindProperty::Filter(filters) => {
1396        if filters.is_empty() {
1397          builder.set_filter_reset(important, false);
1398        } else {
1399          for filter in filters {
1400            builder.push_filter(filter, important);
1401          }
1402        }
1403      }
1404      TailwindProperty::BackdropBlur(tw_blur) => {
1405        builder.push_backdrop_filter(Filter::Blur(tw_blur.0), important)
1406      }
1407      TailwindProperty::BackdropBrightness(percentage_number) => {
1408        builder.push_backdrop_filter(Filter::Brightness(percentage_number), important)
1409      }
1410      TailwindProperty::BackdropContrast(percentage_number) => {
1411        builder.push_backdrop_filter(Filter::Contrast(percentage_number), important)
1412      }
1413      TailwindProperty::BackdropGrayscale(percentage_number) => {
1414        builder.push_backdrop_filter(Filter::Grayscale(percentage_number), important)
1415      }
1416      TailwindProperty::BackdropHueRotate(angle) => {
1417        builder.push_backdrop_filter(Filter::HueRotate(angle), important)
1418      }
1419      TailwindProperty::BackdropInvert(percentage_number) => {
1420        builder.push_backdrop_filter(Filter::Invert(percentage_number), important)
1421      }
1422      TailwindProperty::BackdropOpacity(percentage_number) => {
1423        builder.push_backdrop_filter(Filter::Opacity(percentage_number), important)
1424      }
1425      TailwindProperty::BackdropSaturate(percentage_number) => {
1426        builder.push_backdrop_filter(Filter::Saturate(percentage_number), important)
1427      }
1428      TailwindProperty::BackdropSepia(percentage_number) => {
1429        builder.push_backdrop_filter(Filter::Sepia(percentage_number), important)
1430      }
1431      TailwindProperty::BackdropFilter(filters) => {
1432        if filters.is_empty() {
1433          builder.set_filter_reset(important, true);
1434        } else {
1435          for filter in filters {
1436            builder.push_backdrop_filter(filter, important);
1437          }
1438        }
1439      }
1440      TailwindProperty::TextShadow(text_shadow) => {
1441        builder.set_text_shadow_layers([text_shadow], important)
1442      }
1443      TailwindProperty::TextShadowList(&[]) => builder.reset_text_shadow(important),
1444      TailwindProperty::TextShadowList(layers) => {
1445        builder.set_text_shadow_layers(layers.iter().copied(), important)
1446      }
1447      TailwindProperty::TextShadowColor(color) => builder.set_text_shadow_color(color, important),
1448      TailwindProperty::Visibility(visibility) => {
1449        push_decl!(builder, important, visibility(visibility))
1450      }
1451      TailwindProperty::Animation(animations) => {
1452        push_decl!(
1453          builder,
1454          important,
1455          animation_duration(
1456            animations
1457              .iter()
1458              .map(|animation| animation.duration)
1459              .collect()
1460          ),
1461          animation_delay(animations.iter().map(|animation| animation.delay).collect()),
1462          animation_timing_function(
1463            animations
1464              .iter()
1465              .map(|animation| animation.timing_function)
1466              .collect()
1467          ),
1468          animation_iteration_count(
1469            animations
1470              .iter()
1471              .map(|animation| animation.iteration_count)
1472              .collect()
1473          ),
1474          animation_direction(
1475            animations
1476              .iter()
1477              .map(|animation| animation.direction)
1478              .collect()
1479          ),
1480          animation_fill_mode(
1481            animations
1482              .iter()
1483              .map(|animation| animation.fill_mode)
1484              .collect()
1485          ),
1486          animation_play_state(
1487            animations
1488              .iter()
1489              .map(|animation| animation.play_state)
1490              .collect()
1491          ),
1492          animation_name(
1493            animations
1494              .into_iter()
1495              .map(|animation| animation.name)
1496              .collect()
1497          )
1498        );
1499      }
1500    }
1501  }
1502}
1503
1504#[cfg(test)]
1505#[allow(clippy::panic, clippy::unwrap_used)]
1506mod tests;