Skip to main content

css_ast/rules/container/
features.rs

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		// Bare form: `stuck: top`
287		if c == Kind::Ident
288			&& matches!(p.to_atom::<CssAtomSet>(c), CssAtomSet::Stuck | CssAtomSet::Snapped | CssAtomSet::Scrollable)
289		{
290			return true;
291		}
292		// Paren-wrapped form: `(stuck: top)`
293		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}