Skip to main content

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	#[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		// Bare form: `stuck: top`
258		if c == Kind::Ident
259			&& matches!(p.to_atom::<CssAtomSet>(c), CssAtomSet::Stuck | CssAtomSet::Snapped | CssAtomSet::Scrollable)
260		{
261			return true;
262		}
263		// Paren-wrapped form: `(stuck: top)`
264		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}