1use std::fmt;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
7pub struct Bem(&'static str);
8
9impl Bem {
10 #[must_use]
12 pub const fn new(block: &'static str) -> Self {
13 Self(block)
14 }
15 #[must_use]
17 pub const fn block(self) -> &'static str {
18 self.0
19 }
20 #[must_use]
22 pub fn element(self, element: &str) -> String {
23 format!("{}__{element}", self.0)
24 }
25 #[must_use]
27 pub(crate) fn modifier_raw(self, suffix: &str) -> String {
28 format!("{}--{suffix}", self.0)
29 }
30 #[must_use]
32 pub fn modifier<M: Modifier>(self, modifier: M) -> Option<String> {
33 modifier
34 .suffix()
35 .map(|suffix| format!("{}--{suffix}", self.0))
36 }
37}
38
39pub trait Modifier: Copy + Eq + Default + fmt::Debug + 'static {
41 const ALL: &'static [Self];
43 const BEM: Bem;
45 fn suffix(self) -> Option<&'static str>;
47}
48
49#[derive(Debug, Default, Clone, PartialEq, Eq)]
51pub struct ClassList(String);
52
53impl ClassList {
54 #[must_use]
56 pub fn new(base: &str) -> Self {
57 Self(base.to_owned())
58 }
59 #[must_use]
61 pub fn with(mut self, class: impl AsRef<str>) -> Self {
62 let class = class.as_ref().trim();
63 if !class.is_empty() {
64 if !self.0.is_empty() {
65 self.0.push(' ');
66 }
67 self.0.push_str(class);
68 }
69 self
70 }
71 #[must_use]
73 pub fn with_if(self, condition: bool, class: impl AsRef<str>) -> Self {
74 if condition {
75 self.with(class)
76 } else {
77 self
78 }
79 }
80 #[must_use]
82 pub fn with_modifier<M: Modifier>(mut self, bem: Bem, modifier: M) -> Self {
83 if let Some(suffix) = modifier.suffix() {
84 if !self.0.is_empty() {
85 self.0.push(' ');
86 }
87 self.0.push_str(bem.block());
88 self.0.push_str("--");
89 self.0.push_str(suffix);
90 }
91 self
92 }
93 #[must_use]
95 pub fn extend(self, extra: Option<&str>) -> Self {
96 match extra {
97 Some(class) => self.with(class),
98 None => self,
99 }
100 }
101}
102
103impl fmt::Display for ClassList {
104 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
105 f.write_str(&self.0)
106 }
107}
108
109#[derive(Debug, Clone, Copy, PartialEq, Eq)]
111pub struct ParseModifierError;
112impl fmt::Display for ParseModifierError {
113 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
114 f.write_str("unknown CSS modifier")
115 }
116}
117impl std::error::Error for ParseModifierError {}
118
119#[macro_export]
124macro_rules! fc_modifier {
125 (
126 $(#[$meta:meta])* $vis:vis enum $name:ident for $bem:ident {
127 $(#[$default_doc:meta])* $default:ident => None,
128 $($(#[$variant_doc:meta])* $variant:ident => $suffix:literal),* $(,)?
129 }
130 ) => {
131 $(#[$meta])*
132 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
133 #[non_exhaustive]
134 $vis enum $name {
135 $(#[$default_doc])*
136 #[default]
137 $default,
138 $($(#[$variant_doc])* $variant),*
139 }
140 impl $crate::core::class::Modifier for $name {
141 const ALL: &'static [Self] = &[Self::$default, $(Self::$variant),*];
142 const BEM: $crate::core::class::Bem = $bem;
143 fn suffix(self) -> Option<&'static str> {
144 match self { Self::$default => None, $(Self::$variant => Some($suffix)),* }
145 }
146 }
147 impl ::std::fmt::Display for $name {
148 fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
149 f.write_str($crate::core::class::Modifier::suffix(*self).unwrap_or(""))
150 }
151 }
152 impl ::std::str::FromStr for $name {
153 type Err = $crate::core::class::ParseModifierError;
154 fn from_str(value: &str) -> Result<Self, Self::Err> {
155 match value { "" => Ok(Self::$default), $($suffix => Ok(Self::$variant),)* _ => Err($crate::core::class::ParseModifierError) }
156 }
157 }
158 };
159}