1use super::supports::SupportsRule;
4use super::MinifyContext;
5use super::{CssRule, CssRuleList, Location};
6use crate::context::DeclarationContext;
7use crate::declaration::DeclarationBlock;
8use crate::error::{ParserError, PrinterError};
9use crate::parser::ParserOptions;
10use crate::printer::Printer;
11use crate::properties::animation::TimelineRangeName;
12use crate::properties::custom::{CustomProperty, UnparsedProperty};
13use crate::properties::Property;
14use crate::targets::Targets;
15use crate::traits::{Parse, ToCss};
16use crate::values::color::ColorFallbackKind;
17use crate::values::ident::CustomIdent;
18use crate::values::percentage::Percentage;
19use crate::values::string::CowArcStr;
20use crate::vendor_prefix::VendorPrefix;
21#[cfg(feature = "visitor")]
22use crate::visitor::Visit;
23use cssparser::*;
24
25#[derive(Debug, PartialEq, Clone)]
27#[cfg_attr(feature = "visitor", derive(Visit))]
28#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
29#[cfg_attr(
30 feature = "serde",
31 derive(serde::Serialize, serde::Deserialize),
32 serde(rename_all = "camelCase")
33)]
34#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
35pub struct KeyframesRule<'i> {
36 #[cfg_attr(feature = "serde", serde(borrow))]
39 pub name: KeyframesName<'i>,
40 pub keyframes: Vec<Keyframe<'i>>,
42 #[cfg_attr(feature = "visitor", skip_visit)]
44 pub vendor_prefix: VendorPrefix,
45 #[cfg_attr(feature = "visitor", skip_visit)]
47 pub loc: Location,
48}
49
50#[derive(Debug, Clone, PartialEq, Eq, Hash)]
52#[cfg_attr(feature = "visitor", derive(Visit))]
53#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
54#[cfg_attr(
55 feature = "serde",
56 derive(serde::Serialize, serde::Deserialize),
57 serde(tag = "type", content = "value", rename_all = "kebab-case")
58)]
59#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
60pub enum KeyframesName<'i> {
61 #[cfg_attr(feature = "serde", serde(borrow))]
63 Ident(CustomIdent<'i>),
64
65 #[cfg_attr(feature = "serde", serde(borrow))]
67 Custom(CowArcStr<'i>),
68}
69
70impl<'i> Parse<'i> for KeyframesName<'i> {
71 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
72 match input.next()?.clone() {
73 Token::Ident(ref s) => {
74 match_ignore_ascii_case! { &*s,
76 "none" | "initial" | "inherit" | "unset" | "default" | "revert" | "revert-layer" => {
77 Err(input.new_unexpected_token_error(Token::Ident(s.clone())))
78 },
79 _ => {
80 Ok(KeyframesName::Ident(CustomIdent(s.into())))
81 }
82 }
83 }
84
85 Token::QuotedString(ref s) => Ok(KeyframesName::Custom(s.into())),
86 t => return Err(input.new_unexpected_token_error(t.clone())),
87 }
88 }
89}
90
91impl<'i> ToCss for KeyframesName<'i> {
92 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
93 where
94 W: std::fmt::Write,
95 {
96 let css_module_animation_enabled =
97 dest.css_module.as_ref().map_or(false, |css_module| css_module.config.animation);
98
99 match self {
100 KeyframesName::Ident(ident) => {
101 dest.write_ident(ident.0.as_ref(), css_module_animation_enabled)?;
102 }
103 KeyframesName::Custom(s) => {
104 match_ignore_ascii_case! { &*s,
106 "none" | "initial" | "inherit" | "unset" | "default" | "revert" | "revert-layer" => {
107 serialize_string(&s, dest)?;
108 },
109 _ => {
110 dest.write_ident(s.as_ref(), css_module_animation_enabled)?;
111 }
112 }
113 }
114 }
115 Ok(())
116 }
117}
118
119impl<'i> KeyframesRule<'i> {
120 pub(crate) fn minify(&mut self, context: &mut MinifyContext<'_, 'i>) {
121 context.handler_context.context = DeclarationContext::Keyframes;
122
123 for keyframe in &mut self.keyframes {
124 keyframe
125 .declarations
126 .minify(context.handler, context.important_handler, &mut context.handler_context)
127 }
128
129 context.handler_context.context = DeclarationContext::None;
130 }
131
132 pub(crate) fn get_fallbacks<T>(&mut self, targets: &Targets) -> Vec<CssRule<'i, T>> {
133 let mut fallbacks = ColorFallbackKind::empty();
134 for keyframe in &self.keyframes {
135 for property in &keyframe.declarations.declarations {
136 match property {
137 Property::Custom(CustomProperty { value, .. }) | Property::Unparsed(UnparsedProperty { value, .. }) => {
138 fallbacks |= value.get_necessary_fallbacks(*targets);
139 }
140 _ => {}
141 }
142 }
143 }
144
145 let mut res = Vec::new();
146 let lowest_fallback = fallbacks.lowest();
147 fallbacks.remove(lowest_fallback);
148
149 if fallbacks.contains(ColorFallbackKind::P3) {
150 res.push(self.get_fallback(ColorFallbackKind::P3));
151 }
152
153 if fallbacks.contains(ColorFallbackKind::LAB)
154 || (!lowest_fallback.is_empty() && lowest_fallback != ColorFallbackKind::LAB)
155 {
156 res.push(self.get_fallback(ColorFallbackKind::LAB));
157 }
158
159 if !lowest_fallback.is_empty() {
160 for keyframe in &mut self.keyframes {
161 for property in &mut keyframe.declarations.declarations {
162 match property {
163 Property::Custom(CustomProperty { value, .. })
164 | Property::Unparsed(UnparsedProperty { value, .. }) => {
165 *value = value.get_fallback(lowest_fallback);
166 }
167 _ => {}
168 }
169 }
170 }
171 }
172
173 res
174 }
175
176 fn get_fallback<T>(&self, kind: ColorFallbackKind) -> CssRule<'i, T> {
177 let keyframes = self
178 .keyframes
179 .iter()
180 .map(|keyframe| Keyframe {
181 selectors: keyframe.selectors.clone(),
182 declarations: DeclarationBlock {
183 important_declarations: vec![],
184 declarations: keyframe
185 .declarations
186 .declarations
187 .iter()
188 .map(|property| match property {
189 Property::Custom(custom) => Property::Custom(CustomProperty {
190 name: custom.name.clone(),
191 value: custom.value.get_fallback(kind),
192 }),
193 Property::Unparsed(unparsed) => Property::Unparsed(UnparsedProperty {
194 property_id: unparsed.property_id.clone(),
195 value: unparsed.value.get_fallback(kind),
196 }),
197 _ => property.clone(),
198 })
199 .collect(),
200 },
201 })
202 .collect();
203
204 CssRule::Supports(SupportsRule {
205 condition: kind.supports_condition(),
206 rules: CssRuleList(vec![CssRule::Keyframes(KeyframesRule {
207 name: self.name.clone(),
208 keyframes,
209 vendor_prefix: self.vendor_prefix,
210 loc: self.loc.clone(),
211 })]),
212 loc: self.loc.clone(),
213 })
214 }
215}
216
217impl<'i> ToCss for KeyframesRule<'i> {
218 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
219 where
220 W: std::fmt::Write,
221 {
222 #[cfg(feature = "sourcemap")]
223 dest.add_mapping(self.loc);
224 let mut first_rule = true;
225 macro_rules! write_prefix {
226 ($prefix: ident) => {
227 if self.vendor_prefix.contains(VendorPrefix::$prefix) {
228 #[allow(unused_assignments)]
229 if first_rule {
230 first_rule = false;
231 } else {
232 if !dest.minify {
233 dest.write_char('\n')?; }
235 dest.newline()?;
236 }
237 dest.write_char('@')?;
238 VendorPrefix::$prefix.to_css(dest)?;
239 dest.write_str("keyframes ")?;
240 self.name.to_css(dest)?;
241 dest.whitespace()?;
242 dest.write_char('{')?;
243 dest.indent();
244 let mut first = true;
245 for keyframe in &self.keyframes {
246 if first {
247 first = false;
248 } else if !dest.minify {
249 dest.write_char('\n')?; }
251 dest.newline()?;
252 keyframe.to_css(dest)?;
253 }
254 dest.dedent();
255 dest.newline()?;
256 dest.write_char('}')?;
257 }
258 };
259 }
260
261 write_prefix!(WebKit);
262 write_prefix!(Moz);
263 write_prefix!(O);
264 write_prefix!(None);
265 Ok(())
266 }
267}
268
269#[derive(Debug, PartialEq, Clone)]
271#[cfg_attr(feature = "visitor", derive(Visit))]
272#[cfg_attr(
273 feature = "serde",
274 derive(serde::Serialize, serde::Deserialize),
275 serde(tag = "type", rename_all = "camelCase")
276)]
277#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
278#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
279pub struct TimelineRangePercentage {
280 name: TimelineRangeName,
282 percentage: Percentage,
284}
285
286impl<'i> Parse<'i> for TimelineRangePercentage {
287 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
288 let name = TimelineRangeName::parse(input)?;
289 let percentage = Percentage::parse(input)?;
290 Ok(TimelineRangePercentage { name, percentage })
291 }
292}
293
294#[derive(Debug, PartialEq, Clone, Parse)]
297#[cfg_attr(feature = "visitor", derive(Visit))]
298#[cfg_attr(
299 feature = "serde",
300 derive(serde::Serialize, serde::Deserialize),
301 serde(tag = "type", content = "value", rename_all = "kebab-case")
302)]
303#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
304#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
305pub enum KeyframeSelector {
306 Percentage(Percentage),
308 From,
310 To,
312 TimelineRangePercentage(TimelineRangePercentage),
314}
315
316impl ToCss for KeyframeSelector {
317 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
318 where
319 W: std::fmt::Write,
320 {
321 match self {
322 KeyframeSelector::Percentage(p) => {
323 if dest.minify && *p == Percentage(1.0) {
324 dest.write_str("to")
325 } else {
326 p.to_css(dest)
327 }
328 }
329 KeyframeSelector::From => {
330 if dest.minify {
331 dest.write_str("0%")
332 } else {
333 dest.write_str("from")
334 }
335 }
336 KeyframeSelector::To => dest.write_str("to"),
337 KeyframeSelector::TimelineRangePercentage(TimelineRangePercentage {
338 name: timeline_range_name,
339 percentage,
340 }) => {
341 timeline_range_name.to_css(dest)?;
342 dest.write_char(' ')?;
343 percentage.to_css(dest)
344 }
345 }
346 }
347}
348
349#[derive(Debug, PartialEq, Clone)]
353#[cfg_attr(feature = "visitor", derive(Visit))]
354#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
355#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
356#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
357pub struct Keyframe<'i> {
358 pub selectors: Vec<KeyframeSelector>,
360 #[cfg_attr(feature = "serde", serde(borrow))]
362 pub declarations: DeclarationBlock<'i>,
363}
364
365impl<'i> ToCss for Keyframe<'i> {
366 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
367 where
368 W: std::fmt::Write,
369 {
370 let mut first = true;
371 for selector in &self.selectors {
372 if !first {
373 dest.delim(',', false)?;
374 }
375 first = false;
376 selector.to_css(dest)?;
377 }
378
379 self.declarations.to_css_block(dest)
380 }
381}
382
383pub(crate) struct KeyframeListParser;
384
385impl<'a, 'i> AtRuleParser<'i> for KeyframeListParser {
386 type Prelude = ();
387 type AtRule = Keyframe<'i>;
388 type Error = ParserError<'i>;
389}
390
391impl<'a, 'i> QualifiedRuleParser<'i> for KeyframeListParser {
392 type Prelude = Vec<KeyframeSelector>;
393 type QualifiedRule = Keyframe<'i>;
394 type Error = ParserError<'i>;
395
396 fn parse_prelude<'t>(
397 &mut self,
398 input: &mut Parser<'i, 't>,
399 ) -> Result<Self::Prelude, ParseError<'i, ParserError<'i>>> {
400 input.parse_comma_separated(KeyframeSelector::parse)
401 }
402
403 fn parse_block<'t>(
404 &mut self,
405 selectors: Self::Prelude,
406 _: &ParserState,
407 input: &mut Parser<'i, 't>,
408 ) -> Result<Self::QualifiedRule, ParseError<'i, ParserError<'i>>> {
409 let options = ParserOptions::default();
411 Ok(Keyframe {
412 selectors,
413 declarations: DeclarationBlock::parse(input, &options)?,
414 })
415 }
416}
417
418impl<'i> DeclarationParser<'i> for KeyframeListParser {
419 type Declaration = Keyframe<'i>;
420 type Error = ParserError<'i>;
421}
422
423impl<'i> RuleBodyItemParser<'i, Keyframe<'i>, ParserError<'i>> for KeyframeListParser {
424 fn parse_qualified(&self) -> bool {
425 true
426 }
427
428 fn parse_declarations(&self) -> bool {
429 false
430 }
431}