1use super::prelude::*;
2use crate::Percentage;
3#[cfg(feature = "visitable")]
4use crate::visit::{NodeId, QueryableNode};
5use css_parse::NoBlockAllowed;
6
7#[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
52impl<'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}