css_ast/rules/
keyframes.rs

1use super::prelude::*;
2use crate::Percentage;
3use css_parse::NoBlockAllowed;
4
5// https://drafts.csswg.org/css-animations/#at-ruledef-keyframes
6#[derive(Peek, Parse, ToSpan, ToCursors, SemanticEq, Debug, 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)]
9#[cfg_attr(feature = "css_feature_data", derive(::csskit_derives::ToCSSFeature), css_feature("css.at-rules.keyframes"))]
10pub struct KeyframesRule<'a> {
11	#[cfg_attr(feature = "visitable", visit(skip))]
12	#[atom(CssAtomSet::Keyframes)]
13	pub name: T![AtKeyword],
14	pub prelude: KeyframesName,
15	pub block: KeyframesRuleBlock<'a>,
16}
17
18impl<'a> NodeWithMetadata<CssMetadata> for KeyframesRule<'a> {
19	fn metadata(&self) -> CssMetadata {
20		let mut meta = self.block.0.metadata();
21		meta.used_at_rules |= AtRuleId::Keyframes;
22		meta
23	}
24}
25
26#[derive(Peek, ToCursors, IntoCursor, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
27#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
28#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
29pub enum KeyframesName {
30	Ident(T![Ident]),
31	String(T![String]),
32}
33
34impl KeyframesName {
35	fn invalid_ident(atom: CssAtomSet) -> bool {
36		matches!(atom, CssAtomSet::Default | CssAtomSet::Initial | CssAtomSet::Unset | CssAtomSet::None)
37	}
38}
39
40// Must use Parse rather than Build so ReservedKeyframeName errors can be emitted
41impl<'a> Parse<'a> for KeyframesName {
42	fn parse<I>(p: &mut Parser<'a, I>) -> ParserResult<Self>
43	where
44		I: Iterator<Item = Cursor> + Clone,
45	{
46		if p.peek::<T![String]>() {
47			return Ok(Self::String(p.parse::<T![String]>()?));
48		}
49		let ident = p.parse::<T![Ident]>()?;
50		if KeyframesName::invalid_ident(p.to_atom::<CssAtomSet>(ident.into())) {
51			Err(Diagnostic::new(ident.into(), Diagnostic::reserved_keyframe_name))?
52		}
53		Ok(Self::Ident(ident))
54	}
55}
56
57#[derive(Parse, Peek, ToSpan, ToCursors, SemanticEq, Debug, 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)]
60pub struct KeyframesRuleBlock<'a>(pub RuleList<'a, Keyframe<'a>, CssMetadata>);
61
62#[derive(Parse, Peek, ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
63#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
64#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
65pub struct Keyframe<'a>(QualifiedRule<'a, KeyframeSelectors<'a>, StyleValue<'a>, NoBlockAllowed, CssMetadata>);
66
67impl<'a> NodeWithMetadata<CssMetadata> for Keyframe<'a> {
68	fn metadata(&self) -> CssMetadata {
69		self.0.metadata()
70	}
71}
72
73#[derive(Peek, Parse, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
74#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
75#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(children))]
76pub struct KeyframeSelectors<'a>(pub CommaSeparated<'a, KeyframeSelector>);
77
78#[derive(Peek, Parse, ToCursors, IntoCursor, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
79#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
80#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
81pub enum KeyframeSelector {
82	#[atom(CssAtomSet::From)]
83	From(T![Ident]),
84	#[atom(CssAtomSet::To)]
85	To(T![Ident]),
86	Percent(Percentage),
87}
88
89#[cfg(test)]
90mod tests {
91	use super::*;
92	use crate::CssAtomSet;
93	use css_parse::assert_parse;
94
95	#[test]
96	fn size_test() {
97		assert_eq!(std::mem::size_of::<KeyframesRule>(), 120);
98		assert_eq!(std::mem::size_of::<KeyframeSelector>(), 16);
99		assert_eq!(std::mem::size_of::<KeyframesName>(), 16);
100		assert_eq!(std::mem::size_of::<KeyframesRuleBlock>(), 88);
101	}
102
103	#[test]
104	fn test_writes() {
105		assert_parse!(CssAtomSet::ATOMS, KeyframesRule, "@keyframes foo{}");
106		assert_parse!(CssAtomSet::ATOMS, KeyframesRule, "@keyframes\"include\"{}");
107		assert_parse!(CssAtomSet::ATOMS, KeyframesRule, "@keyframes spin{0%{rotate:0deg}100%{rotate:360deg}}");
108		assert_parse!(CssAtomSet::ATOMS, KeyframesRule, "@keyframes spin{from,0%{rotate:0deg}to,100%{rotate:360deg}}");
109		assert_parse!(CssAtomSet::ATOMS, KeyframesRule, "@keyframes spin{to{rotate:360deg}}");
110		assert_parse!(
111			CssAtomSet::ATOMS,
112			KeyframesRule,
113			"@keyframes x{to{animation-timing-function:cubic-bezier(0,0,0.2,1)}}"
114		);
115	}
116}