mod builder;
pub mod map;
pub mod parser;
use builder::TailwindDeclarationBuilder;
pub use builder::{TwGradientState, TwGradientType};
use std::{borrow::Cow, cmp::Ordering, str::FromStr};
use cssparser::match_ignore_ascii_case;
use serde::{Deserialize, Deserializer, de::Error as DeError};
use crate::{
Viewport,
style::{
tw::{
map::{FIXED_PROPERTIES, PREFIX_PARSERS},
parser::*,
},
*,
},
};
pub const TW_VAR_SPACING: f32 = 0.25;
#[derive(Debug, Clone, PartialEq)]
pub struct TailwindValues {
inner: Vec<TailwindValue>,
}
impl FromStr for TailwindValues {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut collected = s
.split_whitespace()
.filter_map(TailwindValue::parse)
.collect::<Vec<_>>();
collected.sort_by(|a, b| {
if !a.important && b.important {
return Ordering::Less;
}
if a.important && !b.important {
return Ordering::Greater;
}
match (&a.breakpoint, &b.breakpoint) {
(None, Some(_)) => Ordering::Less,
(Some(_), None) => Ordering::Greater,
_ => Ordering::Equal,
}
});
Ok(TailwindValues { inner: collected })
}
}
impl TailwindValues {
pub fn resource_urls(&self, viewport: Viewport) -> impl Iterator<Item = &str> {
self
.inner
.iter()
.filter_map(move |value| value.resource_url(viewport))
}
#[inline(never)]
pub fn into_declaration_block(self, viewport: Viewport) -> StyleDeclarationBlock {
let mut builder = TailwindDeclarationBuilder::default();
for value in self.inner {
value.apply(&mut builder, viewport);
}
builder.finish()
}
}
impl<'de> Deserialize<'de> for TailwindValues {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let string = String::deserialize(deserializer)?;
TailwindValues::from_str(&string).map_err(D::Error::custom)
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct TailwindValue {
pub property: TailwindProperty,
pub breakpoint: Option<Breakpoint>,
pub important: bool,
}
fn split_variant(token: &str) -> Option<(&str, &str)> {
let bytes = token.as_bytes();
let mut stack: Vec<u8> = Vec::new();
let mut index = 0;
while index < bytes.len() {
match bytes[index] {
b'\\' => index += 1, quote @ (b'\'' | b'"') => {
index += 1;
while index < bytes.len() && bytes[index] != quote {
index += if bytes[index] == b'\\' { 2 } else { 1 };
}
}
b'(' => stack.push(b')'),
b'[' => stack.push(b']'),
b'{' => stack.push(b'}'),
closing @ (b')' | b']' | b'}') => {
if stack.last() == Some(&closing) {
stack.pop();
}
}
b':' if stack.is_empty() => return Some((&token[..index], &token[index + 1..])),
_ => {}
}
index += 1;
}
None
}
impl TailwindValue {
fn resource_url(&self, viewport: Viewport) -> Option<&str> {
if let Some(breakpoint) = self.breakpoint
&& !breakpoint.matches(viewport)
{
return None;
}
self.property.resource_url()
}
#[inline(never)]
fn apply(self, builder: &mut TailwindDeclarationBuilder, viewport: Viewport) {
if let Some(breakpoint) = self.breakpoint
&& !breakpoint.matches(viewport)
{
return;
}
self.property.apply(builder, self.important);
}
pub fn parse(mut token: &str) -> Option<Self> {
let mut important = false;
let mut breakpoint = None;
if let Some((breakpoint_token, rest)) = split_variant(token) {
breakpoint = Some(Breakpoint::parse(breakpoint_token)?);
token = rest;
}
if let Some(stripped) = token.strip_prefix('!') {
important = true;
token = stripped;
}
if let Some(stripped) = token.strip_suffix('!') {
important = true;
token = stripped;
}
Some(TailwindValue {
property: TailwindProperty::parse(token)?,
breakpoint,
important,
})
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Breakpoint(pub Length);
impl Breakpoint {
pub fn parse(token: &str) -> Option<Self> {
match_ignore_ascii_case! {token,
"sm" => Some(Breakpoint(Length::Rem(40.0))),
"md" => Some(Breakpoint(Length::Rem(48.0))),
"lg" => Some(Breakpoint(Length::Rem(64.0))),
"xl" => Some(Breakpoint(Length::Rem(80.0))),
"2xl" => Some(Breakpoint(Length::Rem(96.0))),
_ => None,
}
}
pub fn matches(&self, viewport: Viewport) -> bool {
let Some(viewport_width) = viewport.size.width else {
return false;
};
let breakpoint_width = match self.0 {
Length::Rem(value) => value * viewport.font_size * viewport.device_pixel_ratio,
Length::Px(value) => value * viewport.device_pixel_ratio,
Length::Vw(value) => (value / 100.0) * viewport_width as f32,
_ => return false,
};
viewport_width >= breakpoint_width as u32
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum TailwindProperty {
BackgroundClip(BackgroundClip),
BoxSizing(BoxSizing),
FlexGrow(FlexGrow),
FlexShrink(FlexGrow),
Aspect(AspectRatio),
Items(AlignItems),
Justify(JustifyContent),
Content(JustifyContent),
JustifySelf(AlignItems),
JustifyItems(AlignItems),
AlignSelf(AlignItems),
FlexDirection(FlexDirection),
FlexWrap(FlexWrap),
Flex(Flex),
FlexBasis(Length),
Overflow(Overflow),
OverflowX(Overflow),
OverflowY(Overflow),
Position(Position),
FontStyle(FontStyle),
FontWeight(FontWeight),
FontStretch(FontStretch),
FontFamily(FontFamily),
LineClamp(LineClamp),
TextOverflow(TextOverflow),
TextWrap(TextWrap),
WhiteSpace(WhiteSpace),
WordBreak(WordBreak),
OverflowWrap(OverflowWrap),
Truncate,
TextAlign(TextAlign),
TextDecorationLine(TextDecorationLines),
TextDecorationColor(ColorInput),
TextDecorationThickness(TextDecorationThickness),
TextTransform(TextTransform),
Size(Length),
Width(Length),
Height(Length),
MinWidth(Length),
MinHeight(Length),
MaxWidth(Length),
MaxHeight(Length),
Shadow(BoxShadow),
ShadowColor(ColorInput),
Display(Display),
ObjectPosition(ObjectPosition),
ObjectFit(ObjectFit),
BackgroundPosition(BackgroundPosition),
BackgroundSize(BackgroundSize),
BackgroundRepeat(BackgroundRepeat),
BackgroundImage(BackgroundImage),
MaskImage(BackgroundImage),
Gap(LengthDefaultsToZero),
GapX(LengthDefaultsToZero),
GapY(LengthDefaultsToZero),
GridAutoFlow(GridAutoFlow),
GridAutoColumns(GridTrackSize),
GridAutoRows(GridTrackSize),
GridColumn(GridLine),
GridRow(GridLine),
GridColumnSpan(GridPlacementSpan),
GridRowSpan(GridPlacementSpan),
GridColumnStart(GridPlacement),
GridColumnEnd(GridPlacement),
GridRowStart(GridPlacement),
GridRowEnd(GridPlacement),
GridTemplateColumns(TwGridTemplate),
GridTemplateRows(TwGridTemplate),
LetterSpacing(TwLetterSpacing),
BorderDefault,
BorderWidth(TwBorderWidth),
BorderStyle(BorderStyle),
Color(ColorInput),
Opacity(PercentageNumber),
BackgroundColor(ColorDefaultsToTransparent),
BorderColor(ColorInput),
BorderTopWidth(TwBorderWidth),
BorderRightWidth(TwBorderWidth),
BorderBottomWidth(TwBorderWidth),
BorderLeftWidth(TwBorderWidth),
BorderXWidth(TwBorderWidth),
BorderYWidth(TwBorderWidth),
BorderTopColor(ColorInput),
BorderRightColor(ColorInput),
BorderBottomColor(ColorInput),
BorderLeftColor(ColorInput),
BorderXColor(ColorInput),
BorderYColor(ColorInput),
OutlineDefault,
OutlineWidth(TwBorderWidth),
OutlineColor(ColorInput),
OutlineStyle(BorderStyle),
OutlineOffset(TwBorderWidth),
Rounded(TwRounded),
RoundedTopLeft(TwRounded),
RoundedTopRight(TwRounded),
RoundedBottomRight(TwRounded),
RoundedBottomLeft(TwRounded),
RoundedTop(TwRounded),
RoundedRight(TwRounded),
RoundedBottom(TwRounded),
RoundedLeft(TwRounded),
FontSize(TwFontSize),
LineHeight(LineHeight),
Translate(Length),
TranslateX(Length),
TranslateY(Length),
Rotate(Angle),
Scale(PercentageNumber),
ScaleX(PercentageNumber),
ScaleY(PercentageNumber),
TransformOrigin(TransformOrigin),
Margin(LengthDefaultsToZero),
MarginX(LengthDefaultsToZero),
MarginY(LengthDefaultsToZero),
MarginTop(LengthDefaultsToZero),
MarginRight(LengthDefaultsToZero),
MarginBottom(LengthDefaultsToZero),
MarginLeft(LengthDefaultsToZero),
MarginInlineStart(LengthDefaultsToZero),
MarginInlineEnd(LengthDefaultsToZero),
Padding(LengthDefaultsToZero),
PaddingX(LengthDefaultsToZero),
PaddingY(LengthDefaultsToZero),
PaddingTop(LengthDefaultsToZero),
PaddingRight(LengthDefaultsToZero),
PaddingBottom(LengthDefaultsToZero),
PaddingLeft(LengthDefaultsToZero),
PaddingInlineStart(LengthDefaultsToZero),
PaddingInlineEnd(LengthDefaultsToZero),
Inset(Length),
InsetX(Length),
InsetY(Length),
Top(Length),
Right(Length),
Bottom(Length),
Left(Length),
Blur(TwBlur),
Brightness(PercentageNumber),
Contrast(PercentageNumber),
DropShadow(TextShadow),
Grayscale(PercentageNumber),
HueRotate(Angle),
Invert(PercentageNumber),
Saturate(PercentageNumber),
Sepia(PercentageNumber),
Filter(Filters),
BackdropBlur(TwBlur),
BackdropBrightness(PercentageNumber),
BackdropContrast(PercentageNumber),
BackdropGrayscale(PercentageNumber),
BackdropHueRotate(Angle),
BackdropInvert(PercentageNumber),
BackdropOpacity(PercentageNumber),
BackdropSaturate(PercentageNumber),
BackdropSepia(PercentageNumber),
BackdropFilter(Filters),
TextShadow(TextShadow),
TextShadowColor(ColorInput),
ShadowList(&'static [BoxShadow]),
TextShadowList(&'static [TextShadow]),
Isolation(Isolation),
MixBlendMode(BlendMode),
BackgroundBlendMode(BlendMode),
Visibility(Visibility),
VerticalAlign(VerticalAlign),
Animation(Animations),
BgLinearAngle(Angle),
BgRadial,
BgConicAngle(Angle),
GradientFrom(ColorInput),
GradientTo(ColorInput),
GradientVia(ColorInput),
GradientFromPosition(Length),
GradientViaPosition(Length),
GradientToPosition(Length),
}
fn extract_arbitrary_value(suffix: &str) -> Option<Cow<'_, str>> {
let value = suffix.strip_prefix('[')?.strip_suffix(']')?;
Some(decode_arbitrary_value(value))
}
enum FnKind {
Url,
VarTheme,
Other,
}
impl FnKind {
fn from_name(name: &str) -> FnKind {
if name == "url" || name.ends_with("_url") {
FnKind::Url
} else if matches!(name, "var" | "theme") || name.ends_with("_var") || name.ends_with("_theme")
{
FnKind::VarTheme
} else {
FnKind::Other
}
}
}
fn is_ident_byte(byte: u8) -> bool {
byte.is_ascii_alphanumeric() || byte == b'-' || byte == b'_'
}
fn decode_arbitrary_value(value: &str) -> Cow<'_, str> {
if !value.contains('_') {
return Cow::Borrowed(value);
}
let bytes = value.as_bytes();
let mut out = String::with_capacity(value.len());
let mut stack: Vec<(FnKind, bool)> = Vec::new();
let mut ident_start = 0;
let mut index = 0;
while index < bytes.len() {
let byte = bytes[index];
if byte == b'\\' && bytes.get(index + 1) == Some(&b'_') {
out.push('_');
index += 2;
ident_start = index;
} else if byte == b'_' {
let preserved = stack.iter().any(|(kind, _)| matches!(kind, FnKind::Url))
|| matches!(stack.last(), Some((FnKind::VarTheme, true)));
out.push(if preserved { '_' } else { ' ' });
index += 1;
} else if byte == b'(' {
stack.push((FnKind::from_name(&value[ident_start..index]), true));
out.push('(');
index += 1;
ident_start = index;
} else if byte == b')' {
stack.pop();
out.push(')');
index += 1;
ident_start = index;
} else if byte == b',' {
if let Some((_, first_arg)) = stack.last_mut() {
*first_arg = false;
}
out.push(',');
index += 1;
ident_start = index;
} else if is_ident_byte(byte) {
out.push(byte as char);
index += 1;
} else {
let char_len = value[index..].chars().next().map_or(1, char::len_utf8);
out.push_str(&value[index..index + char_len]);
index += char_len;
ident_start = index;
}
}
Cow::Owned(out)
}
pub trait TailwindPropertyParser: Sized + for<'i> FromCss<'i> {
fn parse_tw(token: &str) -> Option<Self>;
fn parse_tw_with_arbitrary(token: &str) -> Option<Self> {
if let Some(value) = extract_arbitrary_value(token) {
return Self::from_str(&value).ok();
}
Self::parse_tw(token)
}
}
macro_rules! try_neg {
($self:expr;
try_negative: $($neg:ident),+ $(,)?;
unary: $($un:ident),+ $(,)?;
grid: $($grid:ident),+ $(,)?
) => {
Some(match $self {
$(TailwindProperty::$neg(v) => TailwindProperty::$neg(v.try_negative()?),)+
$(TailwindProperty::$un(v) => TailwindProperty::$un(-v),)+
$(TailwindProperty::$grid(p) => TailwindProperty::$grid(p.try_negative()?),)+
_ => return None,
})
};
}
impl TailwindProperty {
fn try_neg(self) -> Option<Self> {
try_neg!(self;
try_negative:
Margin, MarginX, MarginY, MarginTop, MarginRight, MarginBottom, MarginLeft,
MarginInlineStart, MarginInlineEnd, Inset, InsetX, InsetY, Top, Right, Bottom, Left,
Translate, TranslateX, TranslateY;
unary: Scale, ScaleX, ScaleY, Rotate, LetterSpacing, HueRotate, BackdropHueRotate;
grid: GridColumnStart, GridColumnEnd, GridRowStart, GridRowEnd
)
}
}
macro_rules! push_decl {
($builder:expr, $important:expr $(, $property:ident($value:expr))* $(,)?) => {{
$(
$builder.push(StyleDeclaration::$property($value), $important);
)*
}};
}
macro_rules! rounded_corners {
($builder:expr, $important:expr, $rounded:expr $(, $corner:ident)+ $(,)?) => {{
let value = SpacePair::from_single($rounded.0);
push_decl!($builder, $important $(, $corner(value))+);
}};
}
impl TailwindProperty {
fn resource_url(&self) -> Option<&str> {
match self {
TailwindProperty::BackgroundImage(BackgroundImage::Url(url))
| TailwindProperty::MaskImage(BackgroundImage::Url(url)) => Some(url.as_ref()),
_ => None,
}
}
pub fn parse(token: &str) -> Option<TailwindProperty> {
if let Some(property) = FIXED_PROPERTIES.get(token) {
return Some(property.clone());
}
if let Some(stripped) = token.strip_prefix('-') {
return Self::parse_prefix_suffix(stripped).and_then(Self::try_neg);
}
Self::parse_prefix_suffix(token)
}
fn parse_prefix_suffix(token: &str) -> Option<TailwindProperty> {
let bytes = token.as_bytes();
for dash_pos in (0..bytes.len()).rev() {
if bytes[dash_pos] != b'-' {
continue;
}
let prefix = &token[..dash_pos];
let Some(parsers) = PREFIX_PARSERS.get(prefix) else {
continue;
};
let suffix = &token[dash_pos + 1..];
for parser in *parsers {
if let Some(property) = parser.parse(suffix) {
return Some(property);
}
}
}
None
}
#[inline(never)]
fn apply(self, builder: &mut TailwindDeclarationBuilder, important: bool) {
match self {
TailwindProperty::BgLinearAngle(angle) => {
builder.gradient_state.gradient_type = TwGradientType::Linear;
builder.gradient_state.angle = Some(angle);
builder.gradient_state.important = important;
}
TailwindProperty::BgRadial => {
builder.gradient_state.gradient_type = TwGradientType::Radial;
builder.gradient_state.important = important;
}
TailwindProperty::BgConicAngle(angle) => {
builder.gradient_state.gradient_type = TwGradientType::Conic;
builder.gradient_state.angle = Some(angle);
builder.gradient_state.important = important;
}
TailwindProperty::GradientFrom(color) => {
builder.gradient_state.from = Some(color);
builder.gradient_state.important = important;
}
TailwindProperty::GradientTo(color) => {
builder.gradient_state.to = Some(color);
builder.gradient_state.important = important;
}
TailwindProperty::GradientVia(color) => {
builder.gradient_state.via = Some(color);
builder.gradient_state.important = important;
}
TailwindProperty::GradientFromPosition(pos) => {
builder.gradient_state.from_position = Some(pos);
builder.gradient_state.important = important;
}
TailwindProperty::GradientViaPosition(pos) => {
builder.gradient_state.via_position = Some(pos);
builder.gradient_state.important = important;
}
TailwindProperty::GradientToPosition(pos) => {
builder.gradient_state.to_position = Some(pos);
builder.gradient_state.important = important;
}
TailwindProperty::BackgroundClip(background_clip) => {
push_decl!(builder, important, background_clip(background_clip));
}
TailwindProperty::Gap(gap) => {
push_decl!(builder, important, row_gap(gap), column_gap(gap));
}
TailwindProperty::GapX(gap_x) => push_decl!(builder, important, column_gap(gap_x)),
TailwindProperty::GapY(gap_y) => push_decl!(builder, important, row_gap(gap_y)),
TailwindProperty::BoxSizing(box_sizing) => {
push_decl!(builder, important, box_sizing(box_sizing))
}
TailwindProperty::FlexGrow(flex_grow) => {
push_decl!(builder, important, flex_grow(Some(flex_grow)))
}
TailwindProperty::FlexShrink(flex_shrink) => {
push_decl!(builder, important, flex_shrink(Some(flex_shrink)))
}
TailwindProperty::Aspect(ratio) => push_decl!(builder, important, aspect_ratio(ratio)),
TailwindProperty::Items(align_items) => {
push_decl!(builder, important, align_items(align_items))
}
TailwindProperty::Justify(justify_content) => {
push_decl!(builder, important, justify_content(justify_content))
}
TailwindProperty::Content(align_content) => {
push_decl!(builder, important, align_content(align_content))
}
TailwindProperty::AlignSelf(align_self) => {
push_decl!(builder, important, align_self(align_self))
}
TailwindProperty::FlexDirection(flex_direction) => {
push_decl!(builder, important, flex_direction(flex_direction))
}
TailwindProperty::FlexWrap(flex_wrap) => push_decl!(builder, important, flex_wrap(flex_wrap)),
TailwindProperty::Flex(flex) => {
push_decl!(
builder,
important,
flex_grow(Some(FlexGrow(flex.grow))),
flex_shrink(Some(FlexGrow(flex.shrink))),
flex_basis(Some(flex.basis))
);
}
TailwindProperty::FlexBasis(flex_basis) => {
push_decl!(builder, important, flex_basis(Some(flex_basis)))
}
TailwindProperty::Overflow(overflow) => {
push_decl!(
builder,
important,
overflow_x(overflow),
overflow_y(overflow)
);
}
TailwindProperty::Position(position) => push_decl!(builder, important, position(position)),
TailwindProperty::FontStyle(font_style) => {
push_decl!(builder, important, font_style(font_style))
}
TailwindProperty::FontWeight(font_weight) => {
push_decl!(builder, important, font_weight(font_weight))
}
TailwindProperty::FontStretch(font_stretch) => {
push_decl!(builder, important, font_stretch(font_stretch))
}
TailwindProperty::FontFamily(font_family) => {
push_decl!(builder, important, font_family(font_family))
}
TailwindProperty::LineClamp(line_clamp) => {
push_decl!(builder, important, line_clamp(Some(line_clamp)))
}
TailwindProperty::TextAlign(text_align) => {
push_decl!(builder, important, text_align(text_align))
}
TailwindProperty::TextDecorationLine(text_decoration) => push_decl!(
builder,
important,
text_decoration_line(Some(text_decoration))
),
TailwindProperty::TextDecorationColor(color_input) => {
push_decl!(builder, important, text_decoration_color(color_input))
}
TailwindProperty::TextDecorationThickness(thickness) => {
push_decl!(builder, important, text_decoration_thickness(thickness))
}
TailwindProperty::TextTransform(text_transform) => {
push_decl!(builder, important, text_transform(text_transform))
}
TailwindProperty::Size(size) => {
push_decl!(builder, important, width(size), height(size));
}
TailwindProperty::Width(width) => push_decl!(builder, important, width(width)),
TailwindProperty::Height(height) => push_decl!(builder, important, height(height)),
TailwindProperty::MinWidth(min_width) => push_decl!(builder, important, min_width(min_width)),
TailwindProperty::MinHeight(min_height) => {
push_decl!(builder, important, min_height(min_height))
}
TailwindProperty::MaxWidth(max_width) => push_decl!(builder, important, max_width(max_width)),
TailwindProperty::MaxHeight(max_height) => {
push_decl!(builder, important, max_height(max_height))
}
TailwindProperty::Shadow(box_shadow) => builder.set_shadow_layers([box_shadow], important),
TailwindProperty::ShadowList(&[]) => builder.reset_shadow(important),
TailwindProperty::ShadowList(layers) => {
builder.set_shadow_layers(layers.iter().copied(), important)
}
TailwindProperty::ShadowColor(color) => builder.set_shadow_color(color, important),
TailwindProperty::Display(display) => {
push_decl!(builder, important, display(display));
}
TailwindProperty::OverflowX(overflow) => push_decl!(builder, important, overflow_x(overflow)),
TailwindProperty::OverflowY(overflow) => push_decl!(builder, important, overflow_y(overflow)),
TailwindProperty::ObjectPosition(background_position) => {
push_decl!(builder, important, object_position(background_position))
}
TailwindProperty::ObjectFit(object_fit) => {
push_decl!(builder, important, object_fit(object_fit))
}
TailwindProperty::BackgroundPosition(background_position) => push_decl!(
builder,
important,
background_position([background_position].into())
),
TailwindProperty::BackgroundSize(background_size) => push_decl!(
builder,
important,
background_size([background_size].into())
),
TailwindProperty::BackgroundRepeat(background_repeat) => push_decl!(
builder,
important,
background_repeat([background_repeat].into())
),
TailwindProperty::BackgroundImage(background_image) => push_decl!(
builder,
important,
background_image(Some([background_image].into()))
),
TailwindProperty::MaskImage(mask_image) => {
push_decl!(builder, important, mask_image(Some([mask_image].into())))
}
TailwindProperty::BorderDefault => {
push_decl!(
builder,
important,
border_top_width(Length::Px(1.0)),
border_right_width(Length::Px(1.0)),
border_bottom_width(Length::Px(1.0)),
border_left_width(Length::Px(1.0))
);
}
TailwindProperty::BorderWidth(tw_border_width) => {
push_decl!(
builder,
important,
border_top_width(tw_border_width.0),
border_right_width(tw_border_width.0),
border_bottom_width(tw_border_width.0),
border_left_width(tw_border_width.0)
);
}
TailwindProperty::BorderStyle(border_style) => {
push_decl!(
builder,
important,
border_top_style(border_style),
border_right_style(border_style),
border_bottom_style(border_style),
border_left_style(border_style)
)
}
TailwindProperty::JustifySelf(align_items) => {
push_decl!(builder, important, justify_self(align_items))
}
TailwindProperty::JustifyItems(align_items) => {
push_decl!(builder, important, justify_items(align_items))
}
TailwindProperty::Color(color_input) => push_decl!(builder, important, color(color_input)),
TailwindProperty::Opacity(percentage_number) => {
push_decl!(builder, important, opacity(percentage_number))
}
TailwindProperty::BackgroundColor(color_input) => {
push_decl!(builder, important, background_color(color_input))
}
TailwindProperty::BorderColor(color_input) => {
push_decl!(
builder,
important,
border_top_color(color_input),
border_right_color(color_input),
border_bottom_color(color_input),
border_left_color(color_input)
)
}
TailwindProperty::BorderTopWidth(tw_border_width) => {
push_decl!(builder, important, border_top_width(tw_border_width.0))
}
TailwindProperty::BorderRightWidth(tw_border_width) => {
push_decl!(builder, important, border_right_width(tw_border_width.0))
}
TailwindProperty::BorderBottomWidth(tw_border_width) => {
push_decl!(builder, important, border_bottom_width(tw_border_width.0))
}
TailwindProperty::BorderLeftWidth(tw_border_width) => {
push_decl!(builder, important, border_left_width(tw_border_width.0))
}
TailwindProperty::BorderXWidth(tw_border_width) => {
push_decl!(
builder,
important,
border_left_width(tw_border_width.0),
border_right_width(tw_border_width.0)
);
}
TailwindProperty::BorderYWidth(tw_border_width) => {
push_decl!(
builder,
important,
border_top_width(tw_border_width.0),
border_bottom_width(tw_border_width.0)
);
}
TailwindProperty::BorderTopColor(color_input) => {
push_decl!(builder, important, border_top_color(color_input))
}
TailwindProperty::BorderRightColor(color_input) => {
push_decl!(builder, important, border_right_color(color_input))
}
TailwindProperty::BorderBottomColor(color_input) => {
push_decl!(builder, important, border_bottom_color(color_input))
}
TailwindProperty::BorderLeftColor(color_input) => {
push_decl!(builder, important, border_left_color(color_input))
}
TailwindProperty::BorderXColor(color_input) => {
push_decl!(
builder,
important,
border_left_color(color_input),
border_right_color(color_input)
);
}
TailwindProperty::BorderYColor(color_input) => {
push_decl!(
builder,
important,
border_top_color(color_input),
border_bottom_color(color_input)
);
}
TailwindProperty::OutlineDefault => {
push_decl!(
builder,
important,
outline_width(Length::Px(1.0)),
outline_style(BorderStyle::Solid)
);
}
TailwindProperty::OutlineWidth(tw_border_width) => {
push_decl!(builder, important, outline_width(tw_border_width.0))
}
TailwindProperty::OutlineColor(color_input) => {
push_decl!(builder, important, outline_color(color_input))
}
TailwindProperty::OutlineStyle(outline_style) => {
push_decl!(builder, important, outline_style(outline_style))
}
TailwindProperty::OutlineOffset(outline_offset) => {
push_decl!(builder, important, outline_offset(outline_offset.0))
}
TailwindProperty::Rounded(rounded) => rounded_corners!(
builder,
important,
rounded,
border_top_left_radius,
border_top_right_radius,
border_bottom_right_radius,
border_bottom_left_radius
),
TailwindProperty::VerticalAlign(vertical_align) => {
push_decl!(builder, important, vertical_align(vertical_align))
}
TailwindProperty::RoundedTopLeft(rounded) => {
rounded_corners!(builder, important, rounded, border_top_left_radius)
}
TailwindProperty::RoundedTopRight(rounded) => {
rounded_corners!(builder, important, rounded, border_top_right_radius)
}
TailwindProperty::RoundedBottomRight(rounded) => {
rounded_corners!(builder, important, rounded, border_bottom_right_radius)
}
TailwindProperty::RoundedBottomLeft(rounded) => {
rounded_corners!(builder, important, rounded, border_bottom_left_radius)
}
TailwindProperty::RoundedTop(rounded) => rounded_corners!(
builder,
important,
rounded,
border_top_left_radius,
border_top_right_radius
),
TailwindProperty::RoundedRight(rounded) => rounded_corners!(
builder,
important,
rounded,
border_top_right_radius,
border_bottom_right_radius
),
TailwindProperty::RoundedBottom(rounded) => rounded_corners!(
builder,
important,
rounded,
border_bottom_left_radius,
border_bottom_right_radius
),
TailwindProperty::RoundedLeft(rounded) => rounded_corners!(
builder,
important,
rounded,
border_top_left_radius,
border_bottom_left_radius
),
TailwindProperty::TextOverflow(text_overflow) => {
push_decl!(builder, important, text_overflow(text_overflow))
}
TailwindProperty::Truncate => {
push_decl!(
builder,
important,
text_overflow(TextOverflow::Ellipsis),
text_wrap_mode(TextWrapMode::NoWrap),
white_space_collapse(WhiteSpaceCollapse::Collapse),
overflow_x(Overflow::Hidden),
overflow_y(Overflow::Hidden)
);
}
TailwindProperty::TextWrap(text_wrap) => {
push_decl!(
builder,
important,
text_wrap_mode(text_wrap.mode),
text_wrap_style(text_wrap.style)
);
}
TailwindProperty::WhiteSpace(white_space) => {
push_decl!(
builder,
important,
text_wrap_mode(white_space.text_wrap_mode),
white_space_collapse(white_space.white_space_collapse)
);
}
TailwindProperty::WordBreak(word_break) => {
push_decl!(builder, important, word_break(word_break))
}
TailwindProperty::Isolation(isolation) => {
push_decl!(builder, important, isolation(isolation))
}
TailwindProperty::MixBlendMode(blend_mode) => {
push_decl!(builder, important, mix_blend_mode(blend_mode))
}
TailwindProperty::BackgroundBlendMode(blend_mode) => push_decl!(
builder,
important,
background_blend_mode([blend_mode].into())
),
TailwindProperty::OverflowWrap(overflow_wrap) => {
push_decl!(builder, important, overflow_wrap(overflow_wrap))
}
TailwindProperty::FontSize(font_size) => {
push_decl!(builder, important, font_size(font_size.font_size));
if let Some(line_height) = font_size.line_height {
push_decl!(builder, important, line_height(line_height));
}
}
TailwindProperty::LineHeight(line_height) => {
push_decl!(builder, important, line_height(line_height))
}
TailwindProperty::Translate(length) => {
builder
.transform_state
.set_translate(SpacePair::from_single(length), important);
}
TailwindProperty::TranslateX(length) => {
builder.transform_state.translate_mut(important).x = length;
}
TailwindProperty::TranslateY(length) => {
builder.transform_state.translate_mut(important).y = length;
}
TailwindProperty::Rotate(angle) => push_decl!(builder, important, rotate(Some(angle))),
TailwindProperty::Scale(percentage_number) => {
builder
.transform_state
.set_scale(SpacePair::from_single(percentage_number), important);
}
TailwindProperty::ScaleX(percentage_number) => {
builder.transform_state.scale_mut(important).x = percentage_number;
}
TailwindProperty::ScaleY(percentage_number) => {
builder.transform_state.scale_mut(important).y = percentage_number;
}
TailwindProperty::TransformOrigin(background_position) => {
push_decl!(builder, important, transform_origin(background_position))
}
TailwindProperty::Margin(length) => {
push_decl!(
builder,
important,
margin_top(length),
margin_right(length),
margin_bottom(length),
margin_left(length)
);
}
TailwindProperty::MarginX(length) => {
push_decl!(
builder,
important,
margin_left(length),
margin_right(length)
);
}
TailwindProperty::MarginY(length) => {
push_decl!(
builder,
important,
margin_top(length),
margin_bottom(length)
);
}
TailwindProperty::MarginTop(length) => push_decl!(builder, important, margin_top(length)),
TailwindProperty::MarginRight(length) => push_decl!(builder, important, margin_right(length)),
TailwindProperty::MarginBottom(length) => {
push_decl!(builder, important, margin_bottom(length))
}
TailwindProperty::MarginLeft(length) => push_decl!(builder, important, margin_left(length)),
TailwindProperty::MarginInlineStart(length) => {
push_decl!(builder, important, margin_inline_start(length))
}
TailwindProperty::MarginInlineEnd(length) => {
push_decl!(builder, important, margin_inline_end(length))
}
TailwindProperty::Padding(length) => {
push_decl!(
builder,
important,
padding_top(length),
padding_right(length),
padding_bottom(length),
padding_left(length)
);
}
TailwindProperty::PaddingX(length) => {
push_decl!(
builder,
important,
padding_left(length),
padding_right(length)
);
}
TailwindProperty::PaddingY(length) => {
push_decl!(
builder,
important,
padding_top(length),
padding_bottom(length)
);
}
TailwindProperty::PaddingTop(length) => push_decl!(builder, important, padding_top(length)),
TailwindProperty::PaddingRight(length) => {
push_decl!(builder, important, padding_right(length))
}
TailwindProperty::PaddingBottom(length) => {
push_decl!(builder, important, padding_bottom(length))
}
TailwindProperty::PaddingLeft(length) => push_decl!(builder, important, padding_left(length)),
TailwindProperty::PaddingInlineStart(length) => {
push_decl!(builder, important, padding_inline_start(length))
}
TailwindProperty::PaddingInlineEnd(length) => {
push_decl!(builder, important, padding_inline_end(length))
}
TailwindProperty::Inset(length) => {
push_decl!(
builder,
important,
top(length),
right(length),
bottom(length),
left(length)
);
}
TailwindProperty::InsetX(length) => {
push_decl!(builder, important, left(length), right(length));
}
TailwindProperty::InsetY(length) => {
push_decl!(builder, important, top(length), bottom(length));
}
TailwindProperty::Top(length) => push_decl!(builder, important, top(length)),
TailwindProperty::Right(length) => push_decl!(builder, important, right(length)),
TailwindProperty::Bottom(length) => push_decl!(builder, important, bottom(length)),
TailwindProperty::Left(length) => push_decl!(builder, important, left(length)),
TailwindProperty::GridAutoColumns(grid_auto_size) => push_decl!(
builder,
important,
grid_auto_columns(Some([grid_auto_size].into()))
),
TailwindProperty::GridAutoRows(grid_auto_size) => push_decl!(
builder,
important,
grid_auto_rows(Some([grid_auto_size].into()))
),
TailwindProperty::GridColumn(tw_grid_span) => {
builder.set_grid_column(tw_grid_span, important)
}
TailwindProperty::GridRow(tw_grid_span) => builder.set_grid_row(tw_grid_span, important),
TailwindProperty::GridColumnStart(tw_grid_placement) => {
let start = builder
.grid_column
.start
.get_or_insert_with(GridPlacement::auto);
*start = tw_grid_placement;
builder.grid_column.start_important = important;
}
TailwindProperty::GridColumnEnd(tw_grid_placement) => {
let end = builder
.grid_column
.end
.get_or_insert_with(GridPlacement::auto);
*end = tw_grid_placement;
builder.grid_column.end_important = important;
}
TailwindProperty::GridRowStart(tw_grid_placement) => {
let start = builder
.grid_row
.start
.get_or_insert_with(GridPlacement::auto);
*start = tw_grid_placement;
builder.grid_row.start_important = important;
}
TailwindProperty::GridRowEnd(tw_grid_placement) => {
let end = builder.grid_row.end.get_or_insert_with(GridPlacement::auto);
*end = tw_grid_placement;
builder.grid_row.end_important = important;
}
TailwindProperty::GridTemplateColumns(tw_grid_template) => push_decl!(
builder,
important,
grid_template_columns(Some(tw_grid_template.0))
),
TailwindProperty::GridTemplateRows(tw_grid_template) => push_decl!(
builder,
important,
grid_template_rows(Some(tw_grid_template.0))
),
TailwindProperty::LetterSpacing(tw_letter_spacing) => {
push_decl!(builder, important, letter_spacing(tw_letter_spacing.0))
}
TailwindProperty::GridAutoFlow(grid_auto_flow) => {
push_decl!(builder, important, grid_auto_flow(grid_auto_flow))
}
TailwindProperty::GridColumnSpan(grid_placement_span) => {
builder.set_grid_column(GridLine::span(grid_placement_span), important)
}
TailwindProperty::GridRowSpan(grid_placement_span) => {
builder.set_grid_row(GridLine::span(grid_placement_span), important)
}
TailwindProperty::Blur(tw_blur) => builder.push_filter(Filter::Blur(tw_blur.0), important),
TailwindProperty::Brightness(percentage_number) => {
builder.push_filter(Filter::Brightness(percentage_number), important)
}
TailwindProperty::Contrast(percentage_number) => {
builder.push_filter(Filter::Contrast(percentage_number), important)
}
TailwindProperty::DropShadow(text_shadow) => {
builder.push_filter(Filter::DropShadow(text_shadow), important)
}
TailwindProperty::Grayscale(percentage_number) => {
builder.push_filter(Filter::Grayscale(percentage_number), important)
}
TailwindProperty::HueRotate(angle) => {
builder.push_filter(Filter::HueRotate(angle), important)
}
TailwindProperty::Invert(percentage_number) => {
builder.push_filter(Filter::Invert(percentage_number), important)
}
TailwindProperty::Saturate(percentage_number) => {
builder.push_filter(Filter::Saturate(percentage_number), important)
}
TailwindProperty::Sepia(percentage_number) => {
builder.push_filter(Filter::Sepia(percentage_number), important)
}
TailwindProperty::Filter(filters) => {
if filters.is_empty() {
builder.set_filter_reset(important, false);
} else {
for filter in filters {
builder.push_filter(filter, important);
}
}
}
TailwindProperty::BackdropBlur(tw_blur) => {
builder.push_backdrop_filter(Filter::Blur(tw_blur.0), important)
}
TailwindProperty::BackdropBrightness(percentage_number) => {
builder.push_backdrop_filter(Filter::Brightness(percentage_number), important)
}
TailwindProperty::BackdropContrast(percentage_number) => {
builder.push_backdrop_filter(Filter::Contrast(percentage_number), important)
}
TailwindProperty::BackdropGrayscale(percentage_number) => {
builder.push_backdrop_filter(Filter::Grayscale(percentage_number), important)
}
TailwindProperty::BackdropHueRotate(angle) => {
builder.push_backdrop_filter(Filter::HueRotate(angle), important)
}
TailwindProperty::BackdropInvert(percentage_number) => {
builder.push_backdrop_filter(Filter::Invert(percentage_number), important)
}
TailwindProperty::BackdropOpacity(percentage_number) => {
builder.push_backdrop_filter(Filter::Opacity(percentage_number), important)
}
TailwindProperty::BackdropSaturate(percentage_number) => {
builder.push_backdrop_filter(Filter::Saturate(percentage_number), important)
}
TailwindProperty::BackdropSepia(percentage_number) => {
builder.push_backdrop_filter(Filter::Sepia(percentage_number), important)
}
TailwindProperty::BackdropFilter(filters) => {
if filters.is_empty() {
builder.set_filter_reset(important, true);
} else {
for filter in filters {
builder.push_backdrop_filter(filter, important);
}
}
}
TailwindProperty::TextShadow(text_shadow) => {
builder.set_text_shadow_layers([text_shadow], important)
}
TailwindProperty::TextShadowList(&[]) => builder.reset_text_shadow(important),
TailwindProperty::TextShadowList(layers) => {
builder.set_text_shadow_layers(layers.iter().copied(), important)
}
TailwindProperty::TextShadowColor(color) => builder.set_text_shadow_color(color, important),
TailwindProperty::Visibility(visibility) => {
push_decl!(builder, important, visibility(visibility))
}
TailwindProperty::Animation(animations) => {
push_decl!(
builder,
important,
animation_duration(
animations
.iter()
.map(|animation| animation.duration)
.collect()
),
animation_delay(animations.iter().map(|animation| animation.delay).collect()),
animation_timing_function(
animations
.iter()
.map(|animation| animation.timing_function)
.collect()
),
animation_iteration_count(
animations
.iter()
.map(|animation| animation.iteration_count)
.collect()
),
animation_direction(
animations
.iter()
.map(|animation| animation.direction)
.collect()
),
animation_fill_mode(
animations
.iter()
.map(|animation| animation.fill_mode)
.collect()
),
animation_play_state(
animations
.iter()
.map(|animation| animation.play_state)
.collect()
),
animation_name(
animations
.into_iter()
.map(|animation| animation.name)
.collect()
)
);
}
}
}
}
#[cfg(test)]
#[allow(clippy::panic, clippy::unwrap_used)]
mod tests;