1use smallvec::SmallVec;
2use smol_str::{format_smolstr, SmolStr};
3use thiserror::Error;
4
5use crate::{
6 common::StrReplaceExt,
7 css::{rule::RuleList, Rule},
8 parsing::VariantCandidate,
9};
10
11#[rustfmt::skip]
12pub trait VariantMatchingFn: Fn(RuleList) -> Option<RuleList> + Sync + Send {}
13
14#[rustfmt::skip]
15impl<T: Fn(RuleList) -> Option<RuleList> + Sync + Send> VariantMatchingFn for T {}
16
17pub trait VariantHandlerExt {
18 fn handle(&self, candidate: &VariantCandidate<'_>, rule: RuleList) -> RuleList;
19}
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
22pub enum VariantKind {
23 Static,
24 Dynamic,
25 Composable,
26}
27
28#[derive(Debug, Clone, PartialEq, Eq, Hash)]
29pub enum VariantHandler {
30 Static(StaticHandler),
31 Dynamic(DynamicHandler),
32 Composable(ComposableHandler),
33}
34
35impl VariantHandler {
36 pub fn take_composable(self) -> Option<ComposableHandler> {
37 match self {
38 Self::Composable(handler) => Some(handler),
39 _ => None,
40 }
41 }
42}
43
44#[derive(Debug, Clone, PartialEq, Eq, Hash)]
45pub struct Variant {
46 pub handler: VariantHandler,
47 pub composable: bool,
48 pub kind: VariantKind,
49 pub ordering: VariantOrdering,
50 pub nested: bool,
51}
52
53#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Hash)]
54pub enum VariantOrdering {
55 Unset,
56 Insertion(u64),
58 Length(u64),
60 Arbitrary,
62}
63
64#[derive(Debug, Error)]
65pub enum OrderingParseError {
66 #[error("Invalid unit, only `px` and `rem` are supported")]
67 InvalidUnit,
68 #[error("Invalid value {0}, expected a u64 integer")]
69 InvalidValue(#[from] std::num::ParseIntError),
70}
71
72static PX_PER_REM: u64 = 16;
73
74impl VariantOrdering {
75 pub fn from_length(s: &str) -> Result<Self, OrderingParseError> {
76 match s {
77 _ if s.ends_with("px") => {
78 let value = s.trim_end_matches("px").parse::<u64>()?;
79 Ok(Self::Length(value))
80 }
81 _ if s.ends_with("rem") => {
82 let value = s.trim_end_matches("rem").parse::<u64>()?;
83 Ok(Self::Length(value * PX_PER_REM))
84 }
85 _ => Err(OrderingParseError::InvalidUnit),
86 }
87 }
88}
89
90impl Variant {
91 pub fn new_static<T>(matcher: T) -> Self
92 where
93 T: IntoIterator,
94 T::Item: Into<SmolStr>,
95 T::IntoIter: ExactSizeIterator,
96 {
97 let handler = StaticHandler::new(matcher);
98 Self {
99 nested: handler.is_nested(),
100 handler: VariantHandler::Static(handler),
101 composable: true,
102 kind: VariantKind::Static,
103 ordering: VariantOrdering::Unset,
104 }
105 }
106
107 pub fn new_composable(handler: fn(RuleList, &VariantCandidate) -> RuleList) -> Self {
108 Self {
109 handler: VariantHandler::Composable(ComposableHandler::new(handler)),
110 composable: true,
111 kind: VariantKind::Composable,
112 ordering: VariantOrdering::Unset,
113 nested: false,
115 }
116 }
117
118 pub fn new_dynamic(handler: fn(RuleList, &VariantCandidate) -> RuleList, nested: bool) -> Self {
119 Self {
120 handler: VariantHandler::Dynamic(DynamicHandler::new(handler)),
121 composable: true,
122 kind: VariantKind::Dynamic,
123 ordering: VariantOrdering::Unset,
124 nested,
125 }
126 }
127
128 pub fn with_ordering(self, ordering: VariantOrdering) -> Self {
129 Self { ordering, ..self }
130 }
131
132 pub fn process(&self, candidate: &VariantCandidate<'_>, rule: RuleList) -> RuleList {
133 match &self.handler {
134 VariantHandler::Static(handler) => handler.handle(candidate, rule),
135 VariantHandler::Dynamic(handler) => handler.handle(candidate, rule),
136 VariantHandler::Composable(handler) => handler.handle(candidate, rule),
137 }
138 }
139
140 pub fn take_composable(&self) -> Option<&ComposableHandler> {
141 match &self.handler {
142 VariantHandler::Composable(handler) => Some(handler),
143 _ => None,
144 }
145 }
146
147 pub fn is_composable(&self) -> bool {
148 self.composable
149 }
150}
151
152impl VariantHandlerExt for Variant {
153 fn handle(&self, candidate: &VariantCandidate<'_>, rule: RuleList) -> RuleList {
154 self.process(candidate, rule)
155 }
156}
157
158#[derive(Debug, Clone, PartialEq, Eq, Hash)]
159pub enum StaticHandler {
160 Selector(SmolStr),
162 PseudoElement(SmolStr),
164 Nested(SmolStr),
166 Duplicate(SmallVec<[SmolStr; 2]>),
168}
169
170impl StaticHandler {
171 pub fn new<T>(matcher: T) -> Self
172 where
173 T: IntoIterator,
174 T::Item: Into<SmolStr>,
175 T::IntoIter: ExactSizeIterator,
176 {
177 let mut iter = matcher.into_iter();
178 let is_duplicate = iter.len() > 1;
179 if !is_duplicate {
180 let next = iter.next().unwrap().into();
181 match next.chars().next() {
182 Some('&') => {
183 if next.starts_with("&::") {
184 Self::PseudoElement(next)
185 } else {
186 Self::Selector(next)
187 }
188 }
189 Some('@') => Self::Nested(next),
190 _ => Self::Selector(format_smolstr!("&:is({})", next)),
191 }
192 } else {
193 Self::new_duplicate(iter)
194 }
195 }
196
197 pub fn new_duplicate<T>(matcher: T) -> Self
198 where
199 T: IntoIterator,
200 T::Item: Into<SmolStr>,
201 {
202 Self::Duplicate(matcher.into_iter().map(Into::into).collect())
203 }
204
205 pub fn is_nested(&self) -> bool {
206 matches!(self, Self::Nested(_))
207 }
208}
209
210impl VariantHandlerExt for StaticHandler {
211 fn handle(&self, _candidate: &VariantCandidate<'_>, rules: RuleList) -> RuleList {
212 match self {
213 Self::Selector(a) | Self::PseudoElement(a) => rules
214 .into_iter()
215 .map(|rule| rule.modify_with(|selector| selector.replace_char('&', a)))
216 .collect(),
217 Self::Nested(a) => RuleList::new(Rule { selector: a.clone(), decls: vec![], rules }),
218 Self::Duplicate(list) => list
219 .iter()
220 .flat_map(move |a| {
221 rules
222 .clone()
223 .into_iter()
224 .map(|rule| rule.modify_with(|selector| selector.replace_char('&', a)))
225 })
226 .collect(),
227 }
228 }
229}
230
231#[derive(Debug, Clone, PartialEq, Eq, Hash)]
232pub struct DynamicHandler {
233 pub handler: fn(RuleList, &VariantCandidate) -> RuleList,
234 pub composable: bool,
235}
236
237impl VariantHandlerExt for DynamicHandler {
238 fn handle(&self, candidate: &VariantCandidate<'_>, rule: RuleList) -> RuleList {
239 (self.handler)(rule, candidate)
240 }
241}
242
243impl DynamicHandler {
244 pub fn new(handler: fn(RuleList, &VariantCandidate) -> RuleList) -> Self {
245 Self { handler, composable: true }
246 }
247}
248
249#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
250pub struct ComposableHandler {
251 pub handler: fn(RuleList, &VariantCandidate) -> RuleList,
252 pub composable: bool,
253}
254
255impl ComposableHandler {
256 pub fn new(handler: fn(RuleList, &VariantCandidate) -> RuleList) -> Self {
257 Self { handler, composable: true }
258 }
259
260 pub fn composable(self) -> Self {
261 Self { composable: true, ..self }
262 }
263}
264
265impl VariantHandlerExt for ComposableHandler {
266 fn handle(&self, candidate: &VariantCandidate<'_>, rule: RuleList) -> RuleList {
267 (self.handler)(rule, candidate)
268 }
269}
270
271#[cfg(test)]
272mod tests {
273 use rswind_css_macro::css;
274 use smol_str::format_smolstr;
275
276 use super::{DynamicHandler, VariantHandlerExt};
277 use crate::{
278 context::DesignSystem,
279 css::{rule::RuleList, Decl, Rule},
280 parsing::candidate::CandidateParser,
281 };
282
283 #[test]
284 fn test_variant_process() {
285 let mut design = DesignSystem::default();
286 design.add_variant("hover", ["&:hover"]);
287 design.add_variant("active", ["&:active"]);
288
289 let candidates = vec![
290 CandidateParser::new("hover").parse_variant(&design.variants).unwrap(),
291 CandidateParser::new("active").parse_variant(&design.variants).unwrap(),
292 ];
293
294 let _input = css! {
295 ".flex" {
296 "display": "flex";
297 }
298 };
299
300 let selector = RuleList::new(Rule {
301 selector: "&".into(),
302 rules: RuleList::default(),
303 decls: vec![Decl { name: "display".into(), value: "flex".into() }],
304 });
305
306 let _res = candidates
307 .into_iter()
308 .map(|candidate| {
309 let processor = design.variants.get(candidate.key).unwrap();
310 (processor, candidate)
311 })
312 .fold(selector, |acc, (processor, candidate)| processor.process(&candidate, acc));
313 }
314
315 #[test]
316 fn test_dynamic_process() {
317 let mut design = DesignSystem::default();
318 design.add_variant("hover", ["&:hover"]);
319 design.add_variant("active", ["&:active"]);
320
321 let candidate = CandidateParser::new("hover").parse_variant(&design.variants).unwrap();
322 let input = css! {
323 ".flex" {
324 "display": "flex";
325 }
326 }
327 .to_rule_list();
328 let variant = DynamicHandler::new(|rule, _| {
330 let hovered = rule
331 .into_iter()
332 .map(|rule| rule.modify_with(|s| format_smolstr!("{}:hover", s)))
333 .collect();
334 Rule {
335 selector: "@media (hover: hover) and (pointer: fine)".into(),
336 decls: vec![],
337 rules: hovered,
338 }
339 .to_rule_list()
340 });
341
342 let res = variant.handle(&candidate, input);
343
344 println!("{:#?}", res);
345 }
346}