1use crate::generated::{PseudoClass, PseudoElement};
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
13#[non_exhaustive]
14pub enum Media {
15 Sm,
17 Md,
19 Lg,
21 Xl,
23 Xxl,
25 Dark,
27 Light,
29 ReducedMotion,
31 Print,
33 Landscape,
35 Portrait,
37 CanHover,
39 CoarsePointer,
41 ForcedColors,
43 MoreContrast,
45}
46
47impl Media {
48 pub(crate) fn name(self) -> &'static str {
50 match self {
51 Self::Sm => "sm",
52 Self::Md => "md",
53 Self::Lg => "lg",
54 Self::Xl => "xl",
55 Self::Xxl => "2xl",
56 Self::Dark => "dark",
57 Self::Light => "light",
58 Self::ReducedMotion => "motion-reduce",
59 Self::Print => "print",
60 Self::Landscape => "landscape",
61 Self::Portrait => "portrait",
62 Self::CanHover => "can-hover",
63 Self::CoarsePointer => "coarse-pointer",
64 Self::ForcedColors => "forced-colors",
65 Self::MoreContrast => "more-contrast",
66 }
67 }
68
69 pub(crate) fn query(self) -> &'static str {
71 match self {
72 Self::Sm => "(min-width: 40rem)",
73 Self::Md => "(min-width: 48rem)",
74 Self::Lg => "(min-width: 64rem)",
75 Self::Xl => "(min-width: 80rem)",
76 Self::Xxl => "(min-width: 96rem)",
77 Self::Dark => "(prefers-color-scheme: dark)",
78 Self::Light => "(prefers-color-scheme: light)",
79 Self::ReducedMotion => "(prefers-reduced-motion: reduce)",
80 Self::Print => "print",
81 Self::Landscape => "(orientation: landscape)",
82 Self::Portrait => "(orientation: portrait)",
83 Self::CanHover => "(hover: hover)",
84 Self::CoarsePointer => "(pointer: coarse)",
85 Self::ForcedColors => "(forced-colors: active)",
86 Self::MoreContrast => "(prefers-contrast: more)",
87 }
88 }
89}
90
91#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
98#[non_exhaustive]
99pub enum Container {
100 Xs3,
102 Xs2,
104 Xs,
106 Sm,
108 Md,
110 Lg,
112 Xl,
114 Xl2,
116 Xl3,
118 Xl4,
120 Xl5,
122 Xl6,
124 Xl7,
126 Xl8,
128}
129
130impl Container {
131 pub(crate) fn name(self) -> &'static str {
133 match self {
134 Self::Xs3 => "@3xs",
135 Self::Xs2 => "@2xs",
136 Self::Xs => "@xs",
137 Self::Sm => "@sm",
138 Self::Md => "@md",
139 Self::Lg => "@lg",
140 Self::Xl => "@xl",
141 Self::Xl2 => "@2xl",
142 Self::Xl3 => "@3xl",
143 Self::Xl4 => "@4xl",
144 Self::Xl5 => "@5xl",
145 Self::Xl6 => "@6xl",
146 Self::Xl7 => "@7xl",
147 Self::Xl8 => "@8xl",
148 }
149 }
150
151 pub(crate) fn query(self) -> &'static str {
153 match self {
154 Self::Xs3 => "(min-width: 16rem)",
155 Self::Xs2 => "(min-width: 18rem)",
156 Self::Xs => "(min-width: 20rem)",
157 Self::Sm => "(min-width: 24rem)",
158 Self::Md => "(min-width: 28rem)",
159 Self::Lg => "(min-width: 32rem)",
160 Self::Xl => "(min-width: 36rem)",
161 Self::Xl2 => "(min-width: 42rem)",
162 Self::Xl3 => "(min-width: 48rem)",
163 Self::Xl4 => "(min-width: 56rem)",
164 Self::Xl5 => "(min-width: 64rem)",
165 Self::Xl6 => "(min-width: 72rem)",
166 Self::Xl7 => "(min-width: 80rem)",
167 Self::Xl8 => "(min-width: 90rem)",
168 }
169 }
170}
171
172const ACTION_ORDER: &[PseudoClass] = &[
178 PseudoClass::Link,
179 PseudoClass::Visited,
180 PseudoClass::FocusWithin,
181 PseudoClass::Hover,
182 PseudoClass::Focus,
183 PseudoClass::FocusVisible,
184 PseudoClass::Active,
185 PseudoClass::Disabled,
186];
187
188impl PseudoClass {
189 pub(crate) fn rank(self) -> usize {
191 ACTION_ORDER
192 .iter()
193 .position(|&p| p == self)
194 .map_or(0, |i| i + 1)
195 }
196}
197
198#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
200pub enum Condition {
201 Pseudo(PseudoClass),
203 Media(Media),
205 Container(Container),
207 Group(PseudoClass),
210 Peer(PseudoClass),
213}
214
215impl From<Container> for Condition {
216 fn from(c: Container) -> Self {
217 Self::Container(c)
218 }
219}
220
221impl From<PseudoClass> for Condition {
222 fn from(p: PseudoClass) -> Self {
223 Self::Pseudo(p)
224 }
225}
226
227impl From<Media> for Condition {
228 fn from(m: Media) -> Self {
229 Self::Media(m)
230 }
231}
232
233#[derive(Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
235pub(crate) struct Conditions {
236 pub(crate) media: Vec<Media>,
238 pub(crate) container: Vec<Container>,
240 pub(crate) group: Vec<(usize, PseudoClass)>,
242 pub(crate) peer: Vec<(usize, PseudoClass)>,
243 pub(crate) pseudo: Vec<(usize, PseudoClass)>,
244 pub(crate) element: Option<PseudoElement>,
245}
246
247pub(crate) const GROUP: &str = "group";
249
250pub(crate) const PEER: &str = "peer";
252
253fn insert_ranked(list: &mut Vec<(usize, PseudoClass)>, p: PseudoClass) {
254 let key = (p.rank(), p);
255 if let Err(at) = list.binary_search(&key) {
256 list.insert(at, key);
257 }
258}
259
260impl Conditions {
261 pub(crate) fn add(&mut self, condition: Condition) {
262 match condition {
263 Condition::Media(m) => {
264 if let Err(at) = self.media.binary_search(&m) {
265 self.media.insert(at, m);
266 }
267 }
268 Condition::Container(c) => {
269 if let Err(at) = self.container.binary_search(&c) {
270 self.container.insert(at, c);
271 }
272 }
273 Condition::Pseudo(p) => insert_ranked(&mut self.pseudo, p),
274 Condition::Group(p) => insert_ranked(&mut self.group, p),
275 Condition::Peer(p) => insert_ranked(&mut self.peer, p),
276 }
277 }
278
279 pub(crate) fn has_relation(&self) -> bool {
282 !self.group.is_empty() || !self.peer.is_empty()
283 }
284
285 pub(crate) fn write_selector(&self, out: &mut String, target: impl FnOnce(&mut String)) {
288 if !self.group.is_empty() {
289 out.push('.');
290 out.push_str(GROUP);
291 for (_, p) in &self.group {
292 out.push(':');
293 out.push_str(p.name());
294 }
295 out.push(' ');
296 }
297 if !self.peer.is_empty() {
298 out.push('.');
299 out.push_str(PEER);
300 for (_, p) in &self.peer {
301 out.push(':');
302 out.push_str(p.name());
303 }
304 out.push_str(" ~ ");
305 }
306 target(out);
307 for (_, p) in &self.pseudo {
308 out.push(':');
309 out.push_str(p.name());
310 }
311 if let Some(e) = self.element {
312 out.push_str("::");
313 out.push_str(e.name());
314 }
315 }
316
317 pub(crate) fn open(&self, out: &mut String) -> usize {
320 let mut open = 0;
321 if !self.media.is_empty() {
322 out.push_str("@media ");
323 for (i, m) in self.media.iter().enumerate() {
324 if i > 0 {
325 out.push_str(" and ");
326 }
327 out.push_str(m.query());
328 }
329 out.push('{');
330 open += 1;
331 }
332 if !self.container.is_empty() {
333 out.push_str("@container ");
334 for (i, c) in self.container.iter().enumerate() {
335 if i > 0 {
336 out.push_str(" and ");
337 }
338 out.push_str(c.query());
339 }
340 out.push('{');
341 open += 1;
342 }
343 open
344 }
345
346 pub(crate) fn close(open: usize, out: &mut String) {
348 for _ in 0..open {
349 out.push('}');
350 }
351 }
352}