css_ast/rules/
keyframes.rs

1use crate::{StyleValue, diagnostics};
2use css_parse::{
3	AtRule, CommaSeparated, Cursor, NoBlockAllowed, Parse, Parser, Peek, QualifiedRule, Result as ParserResult,
4	RuleList, T, ToSpan, atkeyword_set, keyword_set,
5};
6use csskit_derives::{IntoCursor, Parse, Peek, ToCursors, ToSpan, Visitable};
7
8atkeyword_set!(pub struct AtKeyframesKeyword "keyframes");
9
10// https://drafts.csswg.org/css-animations/#at-ruledef-keyframes
11#[derive(Peek, Parse, ToSpan, ToCursors, Visitable, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
12#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
13#[cfg_attr(feature = "css_feature_data", derive(::csskit_derives::ToCSSFeature), css_feature("css.at-rules.keyframes"))]
14#[visit]
15pub struct KeyframesRule<'a>(pub AtRule<AtKeyframesKeyword, KeyframesName, KeyframesRuleBlock<'a>>);
16
17#[derive(Peek, ToCursors, IntoCursor, Visitable, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
18#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
19#[visit(self)]
20pub enum KeyframesName {
21	Ident(T![Ident]),
22	String(T![String]),
23}
24
25impl KeyframesName {
26	const INVALID: phf::Map<&'static str, bool> = phf::phf_map! {
27		"default" => true,
28		"initial" => true,
29		"unset" => true,
30		"none" => true,
31	};
32
33	fn valid_ident(str: &str) -> bool {
34		!*Self::INVALID.get(str).unwrap_or(&false)
35	}
36}
37
38// Must use Parse rather than Build so ReservedKeyframeName errors can be emitted
39impl<'a> Parse<'a> for KeyframesName {
40	fn parse(p: &mut Parser<'a>) -> ParserResult<Self> {
41		if p.peek::<T![String]>() {
42			return Ok(Self::String(p.parse::<T![String]>()?));
43		}
44		let ident = p.parse::<T![Ident]>()?;
45		let str = p.parse_str_lower(ident.into());
46		if !KeyframesName::valid_ident(str) {
47			Err(diagnostics::ReservedKeyframeName(str.into(), ident.to_span()))?
48		}
49		Ok(Self::Ident(ident))
50	}
51}
52
53#[derive(Parse, Peek, ToSpan, ToCursors, Visitable, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
54#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
55#[visit]
56pub struct KeyframesRuleBlock<'a>(RuleList<'a, Keyframe<'a>>);
57
58#[derive(Parse, Peek, ToSpan, ToCursors, Visitable, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
59#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
60#[visit]
61pub struct Keyframe<'a>(QualifiedRule<'a, KeyframeSelectors<'a>, StyleValue<'a>, NoBlockAllowed>);
62
63#[derive(Peek, Parse, ToCursors, ToSpan, Visitable, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
64#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
65#[visit(children)]
66pub struct KeyframeSelectors<'a>(pub CommaSeparated<'a, KeyframeSelector>);
67
68#[derive(ToCursors, IntoCursor, Visitable, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
69#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
70#[visit(self)]
71pub enum KeyframeSelector {
72	From(T![Ident]),
73	To(T![Ident]),
74	Percent(T![Dimension::%]),
75}
76
77keyword_set!(pub enum KeyframeSelectorKeyword { From: "from", To: "to" });
78
79impl<'a> Peek<'a> for KeyframeSelector {
80	fn peek(p: &Parser<'a>, c: Cursor) -> bool {
81		KeyframeSelectorKeyword::peek(p, c) || <T![Dimension::%]>::peek(p, c)
82	}
83}
84
85impl<'a> Parse<'a> for KeyframeSelector {
86	fn parse(p: &mut Parser<'a>) -> ParserResult<Self> {
87		if let Some(keyword) = p.parse_if_peek::<KeyframeSelectorKeyword>()? {
88			return match keyword {
89				KeyframeSelectorKeyword::From(ident) => Ok(Self::From(ident)),
90				KeyframeSelectorKeyword::To(ident) => Ok(Self::To(ident)),
91			};
92		}
93		let percent = p.parse::<T![Dimension::%]>()?;
94		let c: Cursor = percent.into();
95		let f: f32 = c.token().value();
96		if (0.0..=100.0).contains(&f) {
97			Ok(Self::Percent(percent))
98		} else {
99			Err(diagnostics::NumberOutOfBounds(f, format!("{:?}", 0.0..=100.0), c.into()))?
100		}
101	}
102}
103
104#[cfg(test)]
105mod tests {
106	use super::*;
107	use css_parse::assert_parse;
108
109	#[test]
110	fn size_test() {
111		assert_eq!(std::mem::size_of::<KeyframesRule>(), 112);
112		assert_eq!(std::mem::size_of::<KeyframeSelector>(), 16);
113		assert_eq!(std::mem::size_of::<KeyframesName>(), 16);
114		assert_eq!(std::mem::size_of::<KeyframesRuleBlock>(), 64);
115	}
116
117	#[test]
118	fn test_writes() {
119		assert_parse!(KeyframesRule, "@keyframes foo{}");
120		assert_parse!(KeyframesRule, "@keyframes\"include\"{}");
121		assert_parse!(KeyframesRule, "@keyframes spin{0%{rotate:0deg}100%{rotate:360deg}}");
122		assert_parse!(KeyframesRule, "@keyframes spin{from,0%{rotate:0deg}to,100%{rotate:360deg}}");
123		assert_parse!(KeyframesRule, "@keyframes spin{to{rotate:360deg}}");
124		assert_parse!(KeyframesRule, "@keyframes x{to{animation-timing-function:cubic-bezier(0,0,0.2,1)}}");
125	}
126}