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
24pub const TW_VAR_SPACING: f32 = 0.25;
26
27#[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 collected.sort_by(|a, b| {
45 if !a.important && b.important {
47 return Ordering::Less;
48 }
49
50 if a.important && !b.important {
51 return Ordering::Greater;
52 }
53
54 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 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#[derive(Debug, Clone, PartialEq)]
100pub struct TailwindValue {
101 pub property: TailwindProperty,
103 pub breakpoint: Option<Breakpoint>,
105 pub important: bool,
107}
108
109fn 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, 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 pub fn parse(mut token: &str) -> Option<Self> {
165 let mut important = false;
166 let mut breakpoint = None;
167
168 if let Some((breakpoint_token, rest)) = split_variant(token) {
170 breakpoint = Some(Breakpoint::parse(breakpoint_token)?);
171 token = rest;
172 }
173
174 if let Some(stripped) = token.strip_prefix('!') {
176 important = true;
177 token = stripped;
178 }
179
180 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#[derive(Debug, Clone, Copy, PartialEq)]
196pub struct Breakpoint(pub Length);
197
198impl Breakpoint {
199 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 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#[derive(Debug, Clone, PartialEq)]
230pub enum TailwindProperty {
231 BackgroundClip(BackgroundClip),
233 BoxSizing(BoxSizing),
235 FlexGrow(FlexGrow),
237 FlexShrink(FlexGrow),
239 Aspect(AspectRatio),
241 Items(AlignItems),
243 Justify(JustifyContent),
245 Content(JustifyContent),
247 JustifySelf(AlignItems),
249 JustifyItems(AlignItems),
251 AlignSelf(AlignItems),
253 FlexDirection(FlexDirection),
255 FlexWrap(FlexWrap),
257 Flex(Flex),
259 FlexBasis(Length),
261 Overflow(Overflow),
263 OverflowX(Overflow),
265 OverflowY(Overflow),
267 Position(Position),
269 FontStyle(FontStyle),
271 FontWeight(FontWeight),
273 FontStretch(FontStretch),
275 FontFamily(FontFamily),
277 LineClamp(LineClamp),
279 TextOverflow(TextOverflow),
281 TextWrap(TextWrap),
283 WhiteSpace(WhiteSpace),
285 WordBreak(WordBreak),
287 OverflowWrap(OverflowWrap),
289 Truncate,
291 TextAlign(TextAlign),
293 TextDecorationLine(TextDecorationLines),
295 TextDecorationColor(ColorInput),
297 TextDecorationThickness(TextDecorationThickness),
299 TextTransform(TextTransform),
301 Size(Length),
303 Width(Length),
305 Height(Length),
307 MinWidth(Length),
309 MinHeight(Length),
311 MaxWidth(Length),
313 MaxHeight(Length),
315 Shadow(BoxShadow),
317 ShadowColor(ColorInput),
319 Display(Display),
321 ObjectPosition(ObjectPosition),
323 ObjectFit(ObjectFit),
325 BackgroundPosition(BackgroundPosition),
327 BackgroundSize(BackgroundSize),
329 BackgroundRepeat(BackgroundRepeat),
331 BackgroundImage(BackgroundImage),
333 MaskImage(BackgroundImage),
335 Gap(LengthDefaultsToZero),
337 GapX(LengthDefaultsToZero),
339 GapY(LengthDefaultsToZero),
341 GridAutoFlow(GridAutoFlow),
343 GridAutoColumns(GridTrackSize),
345 GridAutoRows(GridTrackSize),
347 GridColumn(GridLine),
349 GridRow(GridLine),
351 GridColumnSpan(GridPlacementSpan),
353 GridRowSpan(GridPlacementSpan),
355 GridColumnStart(GridPlacement),
357 GridColumnEnd(GridPlacement),
359 GridRowStart(GridPlacement),
361 GridRowEnd(GridPlacement),
363 GridTemplateColumns(TwGridTemplate),
365 GridTemplateRows(TwGridTemplate),
367 LetterSpacing(TwLetterSpacing),
369 BorderDefault,
371 BorderWidth(TwBorderWidth),
373 BorderStyle(BorderStyle),
375 Color(ColorInput),
377 Opacity(PercentageNumber),
379 BackgroundColor(ColorDefaultsToTransparent),
381 BorderColor(ColorInput),
383 BorderTopWidth(TwBorderWidth),
385 BorderRightWidth(TwBorderWidth),
387 BorderBottomWidth(TwBorderWidth),
389 BorderLeftWidth(TwBorderWidth),
391 BorderXWidth(TwBorderWidth),
393 BorderYWidth(TwBorderWidth),
395 BorderTopColor(ColorInput),
397 BorderRightColor(ColorInput),
399 BorderBottomColor(ColorInput),
401 BorderLeftColor(ColorInput),
403 BorderXColor(ColorInput),
405 BorderYColor(ColorInput),
407 OutlineDefault,
409 OutlineWidth(TwBorderWidth),
411 OutlineColor(ColorInput),
413 OutlineStyle(BorderStyle),
415 OutlineOffset(TwBorderWidth),
417 Rounded(TwRounded),
419 RoundedTopLeft(TwRounded),
421 RoundedTopRight(TwRounded),
423 RoundedBottomRight(TwRounded),
425 RoundedBottomLeft(TwRounded),
427 RoundedTop(TwRounded),
429 RoundedRight(TwRounded),
431 RoundedBottom(TwRounded),
433 RoundedLeft(TwRounded),
435 FontSize(TwFontSize),
437 LineHeight(LineHeight),
439 Translate(Length),
441 TranslateX(Length),
443 TranslateY(Length),
445 Rotate(Angle),
447 Scale(PercentageNumber),
449 ScaleX(PercentageNumber),
451 ScaleY(PercentageNumber),
453 TransformOrigin(TransformOrigin),
455 Margin(LengthDefaultsToZero),
457 MarginX(LengthDefaultsToZero),
459 MarginY(LengthDefaultsToZero),
461 MarginTop(LengthDefaultsToZero),
463 MarginRight(LengthDefaultsToZero),
465 MarginBottom(LengthDefaultsToZero),
467 MarginLeft(LengthDefaultsToZero),
469 MarginInlineStart(LengthDefaultsToZero),
470 MarginInlineEnd(LengthDefaultsToZero),
471 Padding(LengthDefaultsToZero),
473 PaddingX(LengthDefaultsToZero),
475 PaddingY(LengthDefaultsToZero),
477 PaddingTop(LengthDefaultsToZero),
479 PaddingRight(LengthDefaultsToZero),
481 PaddingBottom(LengthDefaultsToZero),
483 PaddingLeft(LengthDefaultsToZero),
485 PaddingInlineStart(LengthDefaultsToZero),
486 PaddingInlineEnd(LengthDefaultsToZero),
487 Inset(Length),
489 InsetX(Length),
491 InsetY(Length),
493 Top(Length),
495 Right(Length),
497 Bottom(Length),
499 Left(Length),
501 Blur(TwBlur),
503 Brightness(PercentageNumber),
505 Contrast(PercentageNumber),
507 DropShadow(TextShadow),
509 Grayscale(PercentageNumber),
511 HueRotate(Angle),
513 Invert(PercentageNumber),
515 Saturate(PercentageNumber),
517 Sepia(PercentageNumber),
519 Filter(Filters),
521 BackdropBlur(TwBlur),
523 BackdropBrightness(PercentageNumber),
525 BackdropContrast(PercentageNumber),
527 BackdropGrayscale(PercentageNumber),
529 BackdropHueRotate(Angle),
531 BackdropInvert(PercentageNumber),
533 BackdropOpacity(PercentageNumber),
535 BackdropSaturate(PercentageNumber),
537 BackdropSepia(PercentageNumber),
539 BackdropFilter(Filters),
541 TextShadow(TextShadow),
543 TextShadowColor(ColorInput),
545 ShadowList(&'static [BoxShadow]),
546 TextShadowList(&'static [TextShadow]),
547 Isolation(Isolation),
549 MixBlendMode(BlendMode),
551 BackgroundBlendMode(BlendMode),
553 Visibility(Visibility),
555 VerticalAlign(VerticalAlign),
557 Animation(Animations),
559 BgLinearAngle(Angle),
561 BgRadial,
563 BgConicAngle(Angle),
565 GradientFrom(ColorInput),
567 GradientTo(ColorInput),
569 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
604fn 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
658pub trait TailwindPropertyParser: Sized + for<'i> FromCss<'i> {
660 fn parse_tw(token: &str) -> Option<Self>;
662
663 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 pub fn parse(token: &str) -> Option<TailwindProperty> {
727 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;