1use super::super::prelude::*;
2use crate::{types::Ratio, units::Length};
3use css_parse::{BumpBox, discrete_feature, ranged_feature};
4
5ranged_feature!(
6 #[derive(ToCursors, ToSpan, SemanticEq, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
7 #[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
8 #[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
9#[derive(csskit_derives::NodeWithMetadata)]
10 pub enum WidthContainerFeature{CssAtomSet::Width, Length}
11);
12
13ranged_feature!(
14 #[derive(ToCursors, ToSpan, SemanticEq, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
15 #[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
16 #[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
17#[derive(csskit_derives::NodeWithMetadata)]
18 pub enum HeightContainerFeature{CssAtomSet::Height, Length}
19);
20
21ranged_feature!(
22 #[derive(ToCursors, ToSpan, SemanticEq, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
23 #[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
24 #[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
25#[derive(csskit_derives::NodeWithMetadata)]
26 pub enum InlineSizeContainerFeature{CssAtomSet::InlineSize, Length}
27);
28
29ranged_feature!(
30 #[derive(ToCursors, ToSpan, SemanticEq, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
31 #[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
32 #[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
33#[derive(csskit_derives::NodeWithMetadata)]
34 pub enum BlockSizeContainerFeature{CssAtomSet::BlockSize, Length}
35);
36
37ranged_feature!(
38 #[derive(ToCursors, ToSpan, SemanticEq, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
39 #[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
40 #[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
41#[derive(csskit_derives::NodeWithMetadata)]
42 pub enum AspectRatioContainerFeature{CssAtomSet::AspectRatio, Ratio}
43);
44
45#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
46#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
47#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(skip))]
48#[derive(csskit_derives::NodeWithMetadata)]
49pub enum OrientationContainerFeatureKeyword {
50 #[atom(CssAtomSet::Portrait)]
51 Portrait(T![Ident]),
52 #[atom(CssAtomSet::Landscape)]
53 Landscape(T![Ident]),
54}
55
56discrete_feature!(
57 #[derive(ToCursors, ToSpan, SemanticEq, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
58 #[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
59 #[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
60#[derive(csskit_derives::NodeWithMetadata)]
61 pub enum OrientationContainerFeature{CssAtomSet::Orientation, OrientationContainerFeatureKeyword}
62);
63
64#[derive(ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
65#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
66#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
67#[derive(csskit_derives::NodeWithMetadata)]
68pub enum StyleQuery<'a> {
69 Is(StyleFeature<'a>),
70 Not(T![Ident], StyleFeature<'a>),
71 And(Vec<'a, (StyleFeature<'a>, Option<T![Ident]>)>),
72 Or(Vec<'a, (StyleFeature<'a>, Option<T![Ident]>)>),
73}
74
75#[derive(ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
76#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
77#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
78#[derive(csskit_derives::NodeWithMetadata)]
79pub enum StyleFeature<'a> {
80 Declaration(BumpBox<'a, Declaration<'a, StyleValue<'a>, CssMetadata>>),
81 CustomProperty(T![Ident]),
82}
83
84impl<'a> Parse<'a> for StyleFeature<'a> {
85 fn parse<I>(p: &mut Parser<'a, I>) -> ParserResult<Self>
86 where
87 I: Iterator<Item = Cursor> + Clone,
88 {
89 let c = p.peek_n(1);
90 if c == Kind::Ident && c.token().is_dashed_ident() && p.peek_n(2) != Kind::Colon {
91 return Ok(Self::CustomProperty(p.parse::<T![Ident]>()?));
92 }
93 let decl = p.parse::<Declaration<'a, StyleValue<'a>, CssMetadata>>()?;
94 Ok(Self::Declaration(BumpBox::new_in(p.bump(), decl)))
95 }
96}
97
98impl<'a> FeatureConditionList<'a> for StyleQuery<'a> {
99 type FeatureCondition = StyleFeature<'a>;
100 fn keyword_is_not<I>(p: &Parser<'a, I>, c: Cursor) -> bool
101 where
102 I: Iterator<Item = Cursor> + Clone,
103 {
104 p.equals_atom(c, &CssAtomSet::Not)
105 }
106 fn keyword_is_and<I>(p: &Parser<'a, I>, c: Cursor) -> bool
107 where
108 I: Iterator<Item = Cursor> + Clone,
109 {
110 p.equals_atom(c, &CssAtomSet::And)
111 }
112 fn keyword_is_or<I>(p: &Parser<'a, I>, c: Cursor) -> bool
113 where
114 I: Iterator<Item = Cursor> + Clone,
115 {
116 p.equals_atom(c, &CssAtomSet::Or)
117 }
118 fn build_is(feature: Self::FeatureCondition) -> Self {
119 Self::Is(feature)
120 }
121 fn build_not(keyword: T![Ident], feature: Self::FeatureCondition) -> Self {
122 Self::Not(keyword, feature)
123 }
124 fn build_and(feature: Vec<'a, (Self::FeatureCondition, Option<T![Ident]>)>) -> Self {
125 Self::And(feature)
126 }
127 fn build_or(feature: Vec<'a, (Self::FeatureCondition, Option<T![Ident]>)>) -> Self {
128 Self::Or(feature)
129 }
130}
131
132impl<'a> Parse<'a> for StyleQuery<'a> {
133 fn parse<I>(p: &mut Parser<'a, I>) -> ParserResult<Self>
134 where
135 I: Iterator<Item = Cursor> + Clone,
136 {
137 Self::parse_condition(p)
138 }
139}
140
141#[derive(ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
142#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
143#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
144#[derive(csskit_derives::NodeWithMetadata)]
145pub enum ScrollStateQuery<'a> {
146 Is(ScrollStateFeature),
147 Not(T![Ident], ScrollStateFeature),
148 And(Vec<'a, (ScrollStateFeature, Option<T![Ident]>)>),
149 Or(Vec<'a, (ScrollStateFeature, Option<T![Ident]>)>),
150}
151
152impl<'a> FeatureConditionList<'a> for ScrollStateQuery<'a> {
153 type FeatureCondition = ScrollStateFeature;
154 fn keyword_is_not<I>(p: &Parser<'a, I>, c: Cursor) -> bool
155 where
156 I: Iterator<Item = Cursor> + Clone,
157 {
158 p.equals_atom(c, &CssAtomSet::Not)
159 }
160 fn keyword_is_and<I>(p: &Parser<'a, I>, c: Cursor) -> bool
161 where
162 I: Iterator<Item = Cursor> + Clone,
163 {
164 p.equals_atom(c, &CssAtomSet::And)
165 }
166 fn keyword_is_or<I>(p: &Parser<'a, I>, c: Cursor) -> bool
167 where
168 I: Iterator<Item = Cursor> + Clone,
169 {
170 p.equals_atom(c, &CssAtomSet::Or)
171 }
172 fn build_is(feature: ScrollStateFeature) -> Self {
173 Self::Is(feature)
174 }
175 fn build_not(keyword: T![Ident], feature: ScrollStateFeature) -> Self {
176 Self::Not(keyword, feature)
177 }
178 fn build_and(feature: Vec<'a, (ScrollStateFeature, Option<T![Ident]>)>) -> Self {
179 Self::And(feature)
180 }
181 fn build_or(feature: Vec<'a, (ScrollStateFeature, Option<T![Ident]>)>) -> Self {
182 Self::Or(feature)
183 }
184}
185
186impl<'a> Parse<'a> for ScrollStateQuery<'a> {
187 fn parse<I>(p: &mut Parser<'a, I>) -> ParserResult<Self>
188 where
189 I: Iterator<Item = Cursor> + Clone,
190 {
191 Self::parse_condition(p)
192 }
193}
194
195#[derive(ToCursors, ToSpan, SemanticEq, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
196#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
197#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
198#[derive(csskit_derives::NodeWithMetadata)]
199pub enum ScrollStateFeature {
200 Stuck(
201 #[cfg_attr(feature = "visitable", visit(skip))] Option<T!['(']>,
202 #[cfg_attr(feature = "visitable", visit(skip))] T![Ident],
203 #[cfg_attr(feature = "visitable", visit(skip))] Option<T![:]>,
204 Option<StuckScrollStateFeatureKeyword>,
205 #[cfg_attr(feature = "visitable", visit(skip))] Option<T![')']>,
206 ),
207 Snapped(
208 #[cfg_attr(feature = "visitable", visit(skip))] Option<T!['(']>,
209 #[cfg_attr(feature = "visitable", visit(skip))] T![Ident],
210 #[cfg_attr(feature = "visitable", visit(skip))] Option<T![:]>,
211 Option<SnappedScrollStateFeatureKeyword>,
212 #[cfg_attr(feature = "visitable", visit(skip))] Option<T![')']>,
213 ),
214 Scrollable(
215 #[cfg_attr(feature = "visitable", visit(skip))] Option<T!['(']>,
216 #[cfg_attr(feature = "visitable", visit(skip))] T![Ident],
217 #[cfg_attr(feature = "visitable", visit(skip))] Option<T![:]>,
218 Option<ScrollableScrollStateFeatureKeyword>,
219 #[cfg_attr(feature = "visitable", visit(skip))] Option<T![')']>,
220 ),
221}
222
223impl<'a> Peek<'a> for ScrollStateFeature {
224 fn peek<I>(p: &Parser<'a, I>, c: Cursor) -> bool
225 where
226 I: Iterator<Item = Cursor> + Clone,
227 {
228 if c == Kind::Ident
230 && matches!(p.to_atom::<CssAtomSet>(c), CssAtomSet::Stuck | CssAtomSet::Snapped | CssAtomSet::Scrollable)
231 {
232 return true;
233 }
234 if c == Kind::LeftParen {
236 let c2 = p.peek_n(2);
237 return c2 == Kind::Ident
238 && matches!(
239 p.to_atom::<CssAtomSet>(c2),
240 CssAtomSet::Stuck | CssAtomSet::Snapped | CssAtomSet::Scrollable
241 );
242 }
243 false
244 }
245}
246
247impl<'a> Parse<'a> for ScrollStateFeature {
248 fn parse<I>(p: &mut Parser<'a, I>) -> ParserResult<Self>
249 where
250 I: Iterator<Item = Cursor> + Clone,
251 {
252 let open = p.parse_if_peek::<T!['(']>()?;
253 let ident = p.parse::<T![Ident]>()?;
254 let c: Cursor = ident.into();
255 let colon = p.parse_if_peek::<T![:]>()?;
256 match p.to_atom::<CssAtomSet>(c) {
257 CssAtomSet::Stuck => {
258 let value = if colon.is_some() { Some(p.parse::<StuckScrollStateFeatureKeyword>()?) } else { None };
259 let close = if open.is_some() { Some(p.parse::<T![')']>()?) } else { None };
260 Ok(Self::Stuck(open, ident, colon, value, close))
261 }
262 CssAtomSet::Snapped => {
263 let value = if colon.is_some() { Some(p.parse::<SnappedScrollStateFeatureKeyword>()?) } else { None };
264 let close = if open.is_some() { Some(p.parse::<T![')']>()?) } else { None };
265 Ok(Self::Snapped(open, ident, colon, value, close))
266 }
267 CssAtomSet::Scrollable => {
268 let value =
269 if colon.is_some() { Some(p.parse::<ScrollableScrollStateFeatureKeyword>()?) } else { None };
270 let close = if open.is_some() { Some(p.parse::<T![')']>()?) } else { None };
271 Ok(Self::Scrollable(open, ident, colon, value, close))
272 }
273 _ => Err(Diagnostic::new(c, Diagnostic::unexpected_ident))?,
274 }
275 }
276}
277
278#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
279#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
280#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(skip))]
281#[derive(csskit_derives::NodeWithMetadata)]
282pub enum ScrollableScrollStateFeatureKeyword {
283 #[atom(CssAtomSet::None)]
284 None(T![Ident]),
285 #[atom(CssAtomSet::Top)]
286 Top(T![Ident]),
287 #[atom(CssAtomSet::Right)]
288 Right(T![Ident]),
289 #[atom(CssAtomSet::Bottom)]
290 Bottom(T![Ident]),
291 #[atom(CssAtomSet::Left)]
292 Left(T![Ident]),
293 #[atom(CssAtomSet::BlockStart)]
294 BlockStart(T![Ident]),
295 #[atom(CssAtomSet::InlineStart)]
296 InlineStart(T![Ident]),
297 #[atom(CssAtomSet::BlockEnd)]
298 BlockEnd(T![Ident]),
299 #[atom(CssAtomSet::InlineEnd)]
300 InlineEnd(T![Ident]),
301 #[atom(CssAtomSet::X)]
302 X(T![Ident]),
303 #[atom(CssAtomSet::Y)]
304 Y(T![Ident]),
305 #[atom(CssAtomSet::Block)]
306 Block(T![Ident]),
307 #[atom(CssAtomSet::Inline)]
308 Inline(T![Ident]),
309 #[atom(CssAtomSet::Discrete)]
310 Discrete(T![Ident]),
311}
312
313#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
314#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
315#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(skip))]
316#[derive(csskit_derives::NodeWithMetadata)]
317pub enum SnappedScrollStateFeatureKeyword {
318 #[atom(CssAtomSet::None)]
319 None(T![Ident]),
320 #[atom(CssAtomSet::X)]
321 X(T![Ident]),
322 #[atom(CssAtomSet::Y)]
323 Y(T![Ident]),
324 #[atom(CssAtomSet::Block)]
325 Block(T![Ident]),
326 #[atom(CssAtomSet::Inline)]
327 Inline(T![Ident]),
328 #[atom(CssAtomSet::Both)]
329 Both(T![Ident]),
330 #[atom(CssAtomSet::Discrete)]
331 Discrete(T![Ident]),
332}
333
334#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
335#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
336#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(skip))]
337#[derive(csskit_derives::NodeWithMetadata)]
338pub enum StuckScrollStateFeatureKeyword {
339 #[atom(CssAtomSet::None)]
340 None(T![Ident]),
341 #[atom(CssAtomSet::Top)]
342 Top(T![Ident]),
343 #[atom(CssAtomSet::Right)]
344 Right(T![Ident]),
345 #[atom(CssAtomSet::Bottom)]
346 Bottom(T![Ident]),
347 #[atom(CssAtomSet::Left)]
348 Left(T![Ident]),
349 #[atom(CssAtomSet::BlockStart)]
350 BlockStart(T![Ident]),
351 #[atom(CssAtomSet::InlineStart)]
352 InlineStart(T![Ident]),
353 #[atom(CssAtomSet::BlockEnd)]
354 BlockEnd(T![Ident]),
355 #[atom(CssAtomSet::InlineEnd)]
356 InlineEnd(T![Ident]),
357 #[atom(CssAtomSet::Discrete)]
358 Discrete(T![Ident]),
359}
360
361#[cfg(test)]
362mod tests {
363 use super::*;
364 use crate::CssAtomSet;
365 use css_parse::{assert_parse, assert_parse_error};
366
367 #[test]
368 fn size_test() {
369 assert_eq!(std::mem::size_of::<WidthContainerFeature>(), 124);
370 assert_eq!(std::mem::size_of::<HeightContainerFeature>(), 124);
371 assert_eq!(std::mem::size_of::<InlineSizeContainerFeature>(), 124);
372 assert_eq!(std::mem::size_of::<BlockSizeContainerFeature>(), 124);
373 assert_eq!(std::mem::size_of::<AspectRatioContainerFeature>(), 180);
374 assert_eq!(std::mem::size_of::<OrientationContainerFeature>(), 64);
375 assert_eq!(std::mem::size_of::<StyleQuery>(), 40);
376 assert_eq!(std::mem::size_of::<ScrollStateQuery>(), 96);
377 assert_eq!(std::mem::size_of::<ScrollStateFeature>(), 80);
378 }
379
380 #[test]
381 fn test_writes() {
382 assert_parse!(CssAtomSet::ATOMS, WidthContainerFeature, "(width:360px)");
383 assert_parse!(CssAtomSet::ATOMS, WidthContainerFeature, "(width>=1400px)");
384 assert_parse!(CssAtomSet::ATOMS, WidthContainerFeature, "(100px<=width)");
385 assert_parse!(CssAtomSet::ATOMS, WidthContainerFeature, "(100px<=width>1400px)");
386 assert_parse!(CssAtomSet::ATOMS, HeightContainerFeature, "(height:360px)");
387 assert_parse!(CssAtomSet::ATOMS, HeightContainerFeature, "(height>=1400px)");
388 assert_parse!(CssAtomSet::ATOMS, HeightContainerFeature, "(100px<=height)");
389 assert_parse!(CssAtomSet::ATOMS, HeightContainerFeature, "(100px<=height>1400px)");
390 assert_parse!(CssAtomSet::ATOMS, StyleQuery, "--x");
391 assert_parse!(CssAtomSet::ATOMS, StyleQuery, "--x:10px");
392 assert_parse!(CssAtomSet::ATOMS, StyleQuery, "--x and --y:20px");
393 assert_parse!(CssAtomSet::ATOMS, ScrollStateQuery, "stuck:top");
394 assert_parse!(CssAtomSet::ATOMS, ScrollStateQuery, "scrollable:y and snapped:block");
395 }
396
397 #[test]
398 fn test_errors() {
399 assert_parse_error!(CssAtomSet::ATOMS, WidthContainerFeature, "(min-width > 10px)");
400 assert_parse_error!(CssAtomSet::ATOMS, WidthContainerFeature, "(width: 1%)");
401 }
402}