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#[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
38impl<'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}