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