css_ast/rules/container/
features.rs

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		// Bare form: `stuck: top`
229		if c == Kind::Ident
230			&& matches!(p.to_atom::<CssAtomSet>(c), CssAtomSet::Stuck | CssAtomSet::Snapped | CssAtomSet::Scrollable)
231		{
232			return true;
233		}
234		// Paren-wrapped form: `(stuck: top)`
235		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}