use smallvec::SmallVec;
use crate::declaration::Declaration;
use crate::{css_to_rules, PseudoClass, Selector};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[cfg(feature = "tailwind_colors")]
use crate::{TAILWIND_COLORS, TAILWIND_NAME_COLORS};
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
pub struct StyleSheet {
rules: SmallVec<[(Selector, SmallVec<[Declaration; 32]>); 32]>,
tmp_generated: SmallVec<[Declaration; 32]>,
}
impl StyleSheet {
pub const fn new_const() -> Self {
Self {
rules: SmallVec::new_const(),
tmp_generated: SmallVec::new_const(),
}
}
pub fn from_css<'a>(input: &'a str) -> Self {
let rules = css_to_rules(input)
.unwrap_or_default()
.into_iter()
.map(|rule| {
let declarations: SmallVec<[Declaration; 32]> = rule
.iter_props()
.filter_map(Declaration::from_cow)
.collect();
rule.selectors
.into_iter()
.map(|selector| (selector, declarations.clone()))
.collect::<SmallVec<[(Selector, SmallVec<_>); 32]>>()
})
.flatten()
.collect();
Self {
rules,
tmp_generated: SmallVec::new(),
}
}
pub fn get_styles(
&mut self,
selector: impl AsRef<str>,
) -> SmallVec<[(Option<PseudoClass>, SmallVec<[Declaration; 32]>); 4]> {
let selector = selector.as_ref();
#[allow(unused_mut)]
let mut found = self
.rules
.iter()
.filter_map(|(sel, decls)| {
if selector
.split_whitespace()
.any(|s| sel.selector.contains(s.trim()))
{
Some((sel.pseudo_class.clone(), decls.clone()))
} else {
None
}
})
.fold(SmallVec::new(), |mut acc, (pseudo_class, declarations)| {
if let Some(existing) = acc
.iter_mut()
.find(|(ps_class, _): &&mut (Option<_>, SmallVec<_>)| ps_class == &pseudo_class)
{
existing.1.extend(declarations);
} else {
acc.push((pseudo_class, declarations));
}
acc
});
#[cfg(feature = "tailwind_colors")]
if !found.is_empty() {
return found;
}
#[cfg(feature = "tailwind_colors")]
{
let selector = Selector::from(selector);
for token in selector.selector.split_whitespace() {
let Some(class) = token.strip_prefix('.') else {
continue;
};
for prefix in ["bg", "text", "border", "outline"] {
let Some(class) = class.strip_prefix(&format!("{prefix}-")) else {
continue;
};
let class = class.to_ascii_lowercase();
let Some((name, tone_str)) = class.split_once('-') else {
continue;
};
if !TAILWIND_NAME_COLORS.contains(&name) {
continue;
}
let Ok(tone) = tone_str.parse::<usize>() else {
continue;
};
let Some(color) = TAILWIND_COLORS
.get(format!("{name}-{tone}").as_str())
.cloned()
else {
continue;
};
let declaration = match prefix {
"bg" => Declaration::BackgroundColor(color),
"text" => Declaration::Color(color),
"border" => Declaration::BorderColor(color),
"outline" => Declaration::OutlineColor(color),
_ => continue,
};
self.tmp_generated.push(declaration);
}
}
if !self.tmp_generated.is_empty() {
self.rules
.push((selector.clone(), self.tmp_generated.clone()));
found.push((selector.pseudo_class, self.tmp_generated.clone()));
}
self.tmp_generated.clear();
}
found
}
}