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