Skip to main content

css_ast/rules/
keyframes.rs

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