lightningcss/properties/
animation.rs

1//! CSS properties related to keyframe animations.
2
3use std::borrow::Cow;
4
5use crate::context::PropertyHandlerContext;
6use crate::declaration::{DeclarationBlock, DeclarationList};
7use crate::error::{ParserError, PrinterError};
8use crate::macros::*;
9use crate::prefixes::Feature;
10use crate::printer::Printer;
11use crate::properties::{Property, PropertyId, TokenOrValue, VendorPrefix};
12use crate::traits::{Parse, PropertyHandler, Shorthand, ToCss, Zero};
13use crate::values::ident::DashedIdent;
14use crate::values::number::CSSNumber;
15use crate::values::percentage::Percentage;
16use crate::values::size::Size2D;
17use crate::values::string::CSSString;
18use crate::values::{easing::EasingFunction, ident::CustomIdent, time::Time};
19#[cfg(feature = "visitor")]
20use crate::visitor::Visit;
21use cssparser::*;
22use itertools::izip;
23use smallvec::SmallVec;
24
25use super::{LengthPercentage, LengthPercentageOrAuto};
26
27/// A value for the [animation-name](https://drafts.csswg.org/css-animations/#animation-name) property.
28#[derive(Debug, Clone, PartialEq, Parse)]
29#[cfg_attr(feature = "visitor", derive(Visit))]
30#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
31#[cfg_attr(
32  feature = "serde",
33  derive(serde::Serialize, serde::Deserialize),
34  serde(tag = "type", content = "value", rename_all = "kebab-case")
35)]
36#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
37pub enum AnimationName<'i> {
38  /// The `none` keyword.
39  None,
40  /// An identifier of a `@keyframes` rule.
41  #[cfg_attr(feature = "serde", serde(borrow))]
42  Ident(CustomIdent<'i>),
43  /// A `<string>` name of a `@keyframes` rule.
44  #[cfg_attr(feature = "serde", serde(borrow))]
45  String(CSSString<'i>),
46}
47
48impl<'i> ToCss for AnimationName<'i> {
49  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
50  where
51    W: std::fmt::Write,
52  {
53    let css_module_animation_enabled =
54      dest.css_module.as_ref().map_or(false, |css_module| css_module.config.animation);
55
56    match self {
57      AnimationName::None => dest.write_str("none"),
58      AnimationName::Ident(s) => {
59        if css_module_animation_enabled {
60          if let Some(css_module) = &mut dest.css_module {
61            css_module.reference(&s.0, dest.loc.source_index)
62          }
63        }
64        s.to_css_with_options(dest, css_module_animation_enabled)
65      }
66      AnimationName::String(s) => {
67        if css_module_animation_enabled {
68          if let Some(css_module) = &mut dest.css_module {
69            css_module.reference(&s, dest.loc.source_index)
70          }
71        }
72
73        // CSS-wide keywords and `none` cannot remove quotes.
74        match_ignore_ascii_case! { &*s,
75          "none" | "initial" | "inherit" | "unset" | "default" | "revert" | "revert-layer" => {
76            serialize_string(&s, dest)?;
77            Ok(())
78          },
79          _ => {
80            dest.write_ident(s.as_ref(), css_module_animation_enabled)
81          }
82        }
83      }
84    }
85  }
86}
87
88/// A list of animation names.
89pub type AnimationNameList<'i> = SmallVec<[AnimationName<'i>; 1]>;
90
91/// A value for the [animation-iteration-count](https://drafts.csswg.org/css-animations/#animation-iteration-count) property.
92#[derive(Debug, Clone, PartialEq, Parse, ToCss)]
93#[cfg_attr(feature = "visitor", derive(Visit))]
94#[cfg_attr(
95  feature = "serde",
96  derive(serde::Serialize, serde::Deserialize),
97  serde(tag = "type", content = "value", rename_all = "kebab-case")
98)]
99#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
100#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
101pub enum AnimationIterationCount {
102  /// The animation will repeat the specified number of times.
103  Number(CSSNumber),
104  /// The animation will repeat forever.
105  Infinite,
106}
107
108impl Default for AnimationIterationCount {
109  fn default() -> Self {
110    AnimationIterationCount::Number(1.0)
111  }
112}
113
114enum_property! {
115  /// A value for the [animation-direction](https://drafts.csswg.org/css-animations/#animation-direction) property.
116  pub enum AnimationDirection {
117    /// The animation is played as specified
118    Normal,
119    /// The animation is played in reverse.
120    Reverse,
121    /// The animation iterations alternate between forward and reverse.
122    Alternate,
123    /// The animation iterations alternate between forward and reverse, with reverse occurring first.
124    AlternateReverse,
125  }
126}
127
128impl Default for AnimationDirection {
129  fn default() -> Self {
130    AnimationDirection::Normal
131  }
132}
133
134enum_property! {
135  /// A value for the [animation-play-state](https://drafts.csswg.org/css-animations/#animation-play-state) property.
136  pub enum AnimationPlayState {
137    /// The animation is playing.
138    Running,
139    /// The animation is paused.
140    Paused,
141  }
142}
143
144impl Default for AnimationPlayState {
145  fn default() -> Self {
146    AnimationPlayState::Running
147  }
148}
149
150enum_property! {
151  /// A value for the [animation-fill-mode](https://drafts.csswg.org/css-animations/#animation-fill-mode) property.
152  pub enum AnimationFillMode {
153    /// The animation has no effect while not playing.
154    None,
155    /// After the animation, the ending values are applied.
156    Forwards,
157    /// Before the animation, the starting values are applied.
158    Backwards,
159    /// Both forwards and backwards apply.
160    Both,
161  }
162}
163
164impl Default for AnimationFillMode {
165  fn default() -> Self {
166    AnimationFillMode::None
167  }
168}
169
170enum_property! {
171  /// A value for the [animation-composition](https://drafts.csswg.org/css-animations-2/#animation-composition) property.
172  pub enum AnimationComposition {
173    /// The result of compositing the effect value with the underlying value is simply the effect value.
174    Replace,
175    /// The effect value is added to the underlying value.
176    Add,
177    /// The effect value is accumulated onto the underlying value.
178    Accumulate,
179  }
180}
181
182/// A value for the [animation-timeline](https://drafts.csswg.org/css-animations-2/#animation-timeline) property.
183#[derive(Debug, Clone, PartialEq, Parse, ToCss)]
184#[cfg_attr(feature = "visitor", derive(Visit))]
185#[cfg_attr(
186  feature = "serde",
187  derive(serde::Serialize, serde::Deserialize),
188  serde(tag = "type", content = "value", rename_all = "kebab-case")
189)]
190#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
191#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
192pub enum AnimationTimeline<'i> {
193  /// The animation’s timeline is a DocumentTimeline, more specifically the default document timeline.
194  Auto,
195  /// The animation is not associated with a timeline.
196  None,
197  /// A timeline referenced by name.
198  #[cfg_attr(feature = "serde", serde(borrow))]
199  DashedIdent(DashedIdent<'i>),
200  /// The scroll() function.
201  Scroll(ScrollTimeline),
202  /// The view() function.
203  View(ViewTimeline),
204}
205
206impl<'i> Default for AnimationTimeline<'i> {
207  fn default() -> Self {
208    AnimationTimeline::Auto
209  }
210}
211
212/// The [scroll()](https://drafts.csswg.org/scroll-animations-1/#scroll-notation) function.
213#[derive(Debug, Clone, PartialEq)]
214#[cfg_attr(feature = "visitor", derive(Visit))]
215#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
216#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
217#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
218pub struct ScrollTimeline {
219  /// Specifies which element to use as the scroll container.
220  pub scroller: Scroller,
221  /// Specifies which axis of the scroll container to use as the progress for the timeline.
222  pub axis: ScrollAxis,
223}
224
225impl<'i> Parse<'i> for ScrollTimeline {
226  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
227    input.expect_function_matching("scroll")?;
228    input.parse_nested_block(|input| {
229      let mut scroller = None;
230      let mut axis = None;
231      loop {
232        if scroller.is_none() {
233          scroller = input.try_parse(Scroller::parse).ok();
234        }
235
236        if axis.is_none() {
237          axis = input.try_parse(ScrollAxis::parse).ok();
238          if axis.is_some() {
239            continue;
240          }
241        }
242        break;
243      }
244
245      Ok(ScrollTimeline {
246        scroller: scroller.unwrap_or_default(),
247        axis: axis.unwrap_or_default(),
248      })
249    })
250  }
251}
252
253impl ToCss for ScrollTimeline {
254  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
255  where
256    W: std::fmt::Write,
257  {
258    dest.write_str("scroll(")?;
259
260    let mut needs_space = false;
261    if self.scroller != Scroller::default() {
262      self.scroller.to_css(dest)?;
263      needs_space = true;
264    }
265
266    if self.axis != ScrollAxis::default() {
267      if needs_space {
268        dest.write_char(' ')?;
269      }
270      self.axis.to_css(dest)?;
271    }
272
273    dest.write_char(')')
274  }
275}
276
277enum_property! {
278  /// A scroller, used in the `scroll()` function.
279  pub enum Scroller {
280    /// Specifies to use the document viewport as the scroll container.
281    "root": Root,
282    /// Specifies to use the nearest ancestor scroll container.
283    "nearest": Nearest,
284    /// Specifies to use the element’s own principal box as the scroll container.
285    "self": SelfElement,
286  }
287}
288
289impl Default for Scroller {
290  fn default() -> Self {
291    Scroller::Nearest
292  }
293}
294
295enum_property! {
296  /// A scroll axis, used in the `scroll()` function.
297  pub enum ScrollAxis {
298    /// Specifies to use the measure of progress along the block axis of the scroll container.
299    Block,
300    /// Specifies to use the measure of progress along the inline axis of the scroll container.
301    Inline,
302    /// Specifies to use the measure of progress along the horizontal axis of the scroll container.
303    X,
304    /// Specifies to use the measure of progress along the vertical axis of the scroll container.
305    Y,
306  }
307}
308
309impl Default for ScrollAxis {
310  fn default() -> Self {
311    ScrollAxis::Block
312  }
313}
314
315/// The [view()](https://drafts.csswg.org/scroll-animations-1/#view-notation) function.
316#[derive(Debug, Clone, PartialEq)]
317#[cfg_attr(feature = "visitor", derive(Visit))]
318#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
319#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
320#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
321pub struct ViewTimeline {
322  /// Specifies which axis of the scroll container to use as the progress for the timeline.
323  pub axis: ScrollAxis,
324  /// Provides an adjustment of the view progress visibility range.
325  pub inset: Size2D<LengthPercentageOrAuto>,
326}
327
328impl<'i> Parse<'i> for ViewTimeline {
329  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
330    input.expect_function_matching("view")?;
331    input.parse_nested_block(|input| {
332      let mut axis = None;
333      let mut inset = None;
334      loop {
335        if axis.is_none() {
336          axis = input.try_parse(ScrollAxis::parse).ok();
337        }
338
339        if inset.is_none() {
340          inset = input.try_parse(Size2D::parse).ok();
341          if inset.is_some() {
342            continue;
343          }
344        }
345        break;
346      }
347
348      Ok(ViewTimeline {
349        axis: axis.unwrap_or_default(),
350        inset: inset.unwrap_or(Size2D(LengthPercentageOrAuto::Auto, LengthPercentageOrAuto::Auto)),
351      })
352    })
353  }
354}
355
356impl ToCss for ViewTimeline {
357  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
358  where
359    W: std::fmt::Write,
360  {
361    dest.write_str("view(")?;
362    let mut needs_space = false;
363    if self.axis != ScrollAxis::default() {
364      self.axis.to_css(dest)?;
365      needs_space = true;
366    }
367
368    if self.inset.0 != LengthPercentageOrAuto::Auto || self.inset.1 != LengthPercentageOrAuto::Auto {
369      if needs_space {
370        dest.write_char(' ')?;
371      }
372      self.inset.to_css(dest)?;
373    }
374
375    dest.write_char(')')
376  }
377}
378
379/// A [view progress timeline range](https://drafts.csswg.org/scroll-animations/#view-timelines-ranges)
380#[derive(Debug, Clone, PartialEq, Parse, ToCss)]
381#[cfg_attr(feature = "visitor", derive(Visit))]
382#[cfg_attr(
383  feature = "serde",
384  derive(serde::Serialize, serde::Deserialize),
385  serde(rename_all = "kebab-case")
386)]
387#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
388#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
389pub enum TimelineRangeName {
390  /// Represents the full range of the view progress timeline.
391  Cover,
392  /// Represents the range during which the principal box is either fully contained by,
393  /// or fully covers, its view progress visibility range within the scrollport.
394  Contain,
395  /// Represents the range during which the principal box is entering the view progress visibility range.
396  Entry,
397  /// Represents the range during which the principal box is exiting the view progress visibility range.
398  Exit,
399  /// Represents the range during which the principal box crosses the end border edge.
400  EntryCrossing,
401  /// Represents the range during which the principal box crosses the start border edge.
402  ExitCrossing,
403}
404
405/// A value for the [animation-range-start](https://drafts.csswg.org/scroll-animations/#animation-range-start)
406/// or [animation-range-end](https://drafts.csswg.org/scroll-animations/#animation-range-end) property.
407#[derive(Debug, Clone, PartialEq)]
408#[cfg_attr(feature = "visitor", derive(Visit))]
409#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(rename_all = "lowercase"))]
410#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
411#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
412pub enum AnimationAttachmentRange {
413  /// The start of the animation’s attachment range is the start of its associated timeline.
414  Normal,
415  /// The animation attachment range starts at the specified point on the timeline measuring from the start of the timeline.
416  #[cfg_attr(feature = "serde", serde(untagged))]
417  LengthPercentage(LengthPercentage),
418  /// The animation attachment range starts at the specified point on the timeline measuring from the start of the specified named timeline range.
419  #[cfg_attr(feature = "serde", serde(untagged))]
420  TimelineRange {
421    /// The name of the timeline range.
422    name: TimelineRangeName,
423    /// The offset from the start of the named timeline range.
424    offset: LengthPercentage,
425  },
426}
427
428impl<'i> AnimationAttachmentRange {
429  fn parse<'t>(input: &mut Parser<'i, 't>, default: f32) -> Result<Self, ParseError<'i, ParserError<'i>>> {
430    if input.try_parse(|input| input.expect_ident_matching("normal")).is_ok() {
431      return Ok(AnimationAttachmentRange::Normal);
432    }
433
434    if let Ok(val) = input.try_parse(LengthPercentage::parse) {
435      return Ok(AnimationAttachmentRange::LengthPercentage(val));
436    }
437
438    let name = TimelineRangeName::parse(input)?;
439    let offset = input
440      .try_parse(LengthPercentage::parse)
441      .unwrap_or(LengthPercentage::Percentage(Percentage(default)));
442    Ok(AnimationAttachmentRange::TimelineRange { name, offset })
443  }
444
445  fn to_css<W>(&self, dest: &mut Printer<W>, default: f32) -> Result<(), PrinterError>
446  where
447    W: std::fmt::Write,
448  {
449    match self {
450      Self::Normal => dest.write_str("normal"),
451      Self::LengthPercentage(val) => val.to_css(dest),
452      Self::TimelineRange { name, offset } => {
453        name.to_css(dest)?;
454        if *offset != LengthPercentage::Percentage(Percentage(default)) {
455          dest.write_char(' ')?;
456          offset.to_css(dest)?;
457        }
458        Ok(())
459      }
460    }
461  }
462}
463
464impl Default for AnimationAttachmentRange {
465  fn default() -> Self {
466    AnimationAttachmentRange::Normal
467  }
468}
469
470/// A value for the [animation-range-start](https://drafts.csswg.org/scroll-animations/#animation-range-start) property.
471#[derive(Debug, Clone, PartialEq)]
472#[cfg_attr(feature = "visitor", derive(Visit))]
473#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
474#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
475#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
476pub struct AnimationRangeStart(pub AnimationAttachmentRange);
477
478impl<'i> Parse<'i> for AnimationRangeStart {
479  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
480    let range = AnimationAttachmentRange::parse(input, 0.0)?;
481    Ok(Self(range))
482  }
483}
484
485impl ToCss for AnimationRangeStart {
486  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
487  where
488    W: std::fmt::Write,
489  {
490    self.0.to_css(dest, 0.0)
491  }
492}
493
494/// A value for the [animation-range-end](https://drafts.csswg.org/scroll-animations/#animation-range-end) property.
495#[derive(Debug, Clone, PartialEq)]
496#[cfg_attr(feature = "visitor", derive(Visit))]
497#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
498#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
499#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
500pub struct AnimationRangeEnd(pub AnimationAttachmentRange);
501
502impl<'i> Parse<'i> for AnimationRangeEnd {
503  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
504    let range = AnimationAttachmentRange::parse(input, 1.0)?;
505    Ok(Self(range))
506  }
507}
508
509impl ToCss for AnimationRangeEnd {
510  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
511  where
512    W: std::fmt::Write,
513  {
514    self.0.to_css(dest, 1.0)
515  }
516}
517
518/// A value for the [animation-range](https://drafts.csswg.org/scroll-animations/#animation-range) shorthand property.
519#[derive(Debug, Clone, PartialEq)]
520#[cfg_attr(feature = "visitor", derive(Visit))]
521#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
522#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
523#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
524pub struct AnimationRange {
525  /// The start of the animation's attachment range.
526  pub start: AnimationRangeStart,
527  /// The end of the animation's attachment range.
528  pub end: AnimationRangeEnd,
529}
530
531impl<'i> Parse<'i> for AnimationRange {
532  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
533    let start = AnimationRangeStart::parse(input)?;
534    let end = input
535      .try_parse(AnimationRangeStart::parse)
536      .map(|r| AnimationRangeEnd(r.0))
537      .unwrap_or_else(|_| {
538        // If <'animation-range-end'> is omitted and <'animation-range-start'> includes a <timeline-range-name> component, then
539        // animation-range-end is set to that same <timeline-range-name> and 100%. Otherwise, any omitted longhand is set to its initial value.
540        match &start.0 {
541          AnimationAttachmentRange::TimelineRange { name, .. } => {
542            AnimationRangeEnd(AnimationAttachmentRange::TimelineRange {
543              name: name.clone(),
544              offset: LengthPercentage::Percentage(Percentage(1.0)),
545            })
546          }
547          _ => AnimationRangeEnd(AnimationAttachmentRange::default()),
548        }
549      });
550    Ok(AnimationRange { start, end })
551  }
552}
553
554impl ToCss for AnimationRange {
555  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
556  where
557    W: std::fmt::Write,
558  {
559    self.start.to_css(dest)?;
560
561    let omit_end = match (&self.start.0, &self.end.0) {
562      (
563        AnimationAttachmentRange::TimelineRange { name: start_name, .. },
564        AnimationAttachmentRange::TimelineRange {
565          name: end_name,
566          offset: end_offset,
567        },
568      ) => start_name == end_name && *end_offset == LengthPercentage::Percentage(Percentage(1.0)),
569      (_, end) => *end == AnimationAttachmentRange::default(),
570    };
571
572    if !omit_end {
573      dest.write_char(' ')?;
574      self.end.to_css(dest)?;
575    }
576    Ok(())
577  }
578}
579
580define_list_shorthand! {
581  /// A value for the [animation](https://drafts.csswg.org/css-animations/#animation) shorthand property.
582  pub struct Animation<'i>(VendorPrefix) {
583    /// The animation name.
584    #[cfg_attr(feature = "serde", serde(borrow))]
585    name: AnimationName(AnimationName<'i>, VendorPrefix),
586    /// The animation duration.
587    duration: AnimationDuration(Time, VendorPrefix),
588    /// The easing function for the animation.
589    timing_function: AnimationTimingFunction(EasingFunction, VendorPrefix),
590    /// The number of times the animation will run.
591    iteration_count: AnimationIterationCount(AnimationIterationCount, VendorPrefix),
592    /// The direction of the animation.
593    direction: AnimationDirection(AnimationDirection, VendorPrefix),
594    /// The current play state of the animation.
595    play_state: AnimationPlayState(AnimationPlayState, VendorPrefix),
596    /// The animation delay.
597    delay: AnimationDelay(Time, VendorPrefix),
598    /// The animation fill mode.
599    fill_mode: AnimationFillMode(AnimationFillMode, VendorPrefix),
600    /// The animation timeline.
601    timeline: AnimationTimeline(AnimationTimeline<'i>),
602  }
603}
604
605impl<'i> Parse<'i> for Animation<'i> {
606  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
607    let mut name = None;
608    let mut duration = None;
609    let mut timing_function = None;
610    let mut iteration_count = None;
611    let mut direction = None;
612    let mut play_state = None;
613    let mut delay = None;
614    let mut fill_mode = None;
615    let mut timeline = None;
616
617    macro_rules! parse_prop {
618      ($var: ident, $type: ident) => {
619        if $var.is_none() {
620          if let Ok(value) = input.try_parse($type::parse) {
621            $var = Some(value);
622            continue;
623          }
624        }
625      };
626    }
627
628    loop {
629      parse_prop!(duration, Time);
630      parse_prop!(timing_function, EasingFunction);
631      parse_prop!(delay, Time);
632      parse_prop!(iteration_count, AnimationIterationCount);
633      parse_prop!(direction, AnimationDirection);
634      parse_prop!(fill_mode, AnimationFillMode);
635      parse_prop!(play_state, AnimationPlayState);
636      parse_prop!(name, AnimationName);
637      parse_prop!(timeline, AnimationTimeline);
638      break;
639    }
640
641    Ok(Animation {
642      name: name.unwrap_or(AnimationName::None),
643      duration: duration.unwrap_or(Time::Seconds(0.0)),
644      timing_function: timing_function.unwrap_or(EasingFunction::Ease),
645      iteration_count: iteration_count.unwrap_or(AnimationIterationCount::Number(1.0)),
646      direction: direction.unwrap_or(AnimationDirection::Normal),
647      play_state: play_state.unwrap_or(AnimationPlayState::Running),
648      delay: delay.unwrap_or(Time::Seconds(0.0)),
649      fill_mode: fill_mode.unwrap_or(AnimationFillMode::None),
650      timeline: timeline.unwrap_or(AnimationTimeline::Auto),
651    })
652  }
653}
654
655impl<'i> ToCss for Animation<'i> {
656  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
657  where
658    W: std::fmt::Write,
659  {
660    match &self.name {
661      AnimationName::None => {}
662      AnimationName::Ident(CustomIdent(name)) | AnimationName::String(CSSString(name)) => {
663        if !self.duration.is_zero() || !self.delay.is_zero() {
664          self.duration.to_css(dest)?;
665          dest.write_char(' ')?;
666        }
667
668        if !self.timing_function.is_ease() || EasingFunction::is_ident(&name) {
669          self.timing_function.to_css(dest)?;
670          dest.write_char(' ')?;
671        }
672
673        if !self.delay.is_zero() {
674          self.delay.to_css(dest)?;
675          dest.write_char(' ')?;
676        }
677
678        if self.iteration_count != AnimationIterationCount::default() || name.as_ref() == "infinite" {
679          self.iteration_count.to_css(dest)?;
680          dest.write_char(' ')?;
681        }
682
683        if self.direction != AnimationDirection::default() || AnimationDirection::parse_string(&name).is_ok() {
684          self.direction.to_css(dest)?;
685          dest.write_char(' ')?;
686        }
687
688        if self.fill_mode != AnimationFillMode::default()
689          || (!name.eq_ignore_ascii_case("none") && AnimationFillMode::parse_string(&name).is_ok())
690        {
691          self.fill_mode.to_css(dest)?;
692          dest.write_char(' ')?;
693        }
694
695        if self.play_state != AnimationPlayState::default() || AnimationPlayState::parse_string(&name).is_ok() {
696          self.play_state.to_css(dest)?;
697          dest.write_char(' ')?;
698        }
699      }
700    }
701
702    // Eventually we could output a string here to avoid duplicating some properties above.
703    // Chrome does not yet support strings, however.
704    self.name.to_css(dest)?;
705
706    if self.name != AnimationName::None && self.timeline != AnimationTimeline::default() {
707      dest.write_char(' ')?;
708      self.timeline.to_css(dest)?;
709    }
710
711    Ok(())
712  }
713}
714
715/// A list of animations.
716pub type AnimationList<'i> = SmallVec<[Animation<'i>; 1]>;
717
718#[derive(Default)]
719pub(crate) struct AnimationHandler<'i> {
720  names: Option<(SmallVec<[AnimationName<'i>; 1]>, VendorPrefix)>,
721  durations: Option<(SmallVec<[Time; 1]>, VendorPrefix)>,
722  timing_functions: Option<(SmallVec<[EasingFunction; 1]>, VendorPrefix)>,
723  iteration_counts: Option<(SmallVec<[AnimationIterationCount; 1]>, VendorPrefix)>,
724  directions: Option<(SmallVec<[AnimationDirection; 1]>, VendorPrefix)>,
725  play_states: Option<(SmallVec<[AnimationPlayState; 1]>, VendorPrefix)>,
726  delays: Option<(SmallVec<[Time; 1]>, VendorPrefix)>,
727  fill_modes: Option<(SmallVec<[AnimationFillMode; 1]>, VendorPrefix)>,
728  timelines: Option<SmallVec<[AnimationTimeline<'i>; 1]>>,
729  range_starts: Option<SmallVec<[AnimationRangeStart; 1]>>,
730  range_ends: Option<SmallVec<[AnimationRangeEnd; 1]>>,
731  has_any: bool,
732}
733
734impl<'i> PropertyHandler<'i> for AnimationHandler<'i> {
735  fn handle_property(
736    &mut self,
737    property: &Property<'i>,
738    dest: &mut DeclarationList<'i>,
739    context: &mut PropertyHandlerContext<'i, '_>,
740  ) -> bool {
741    macro_rules! maybe_flush {
742      ($prop: ident, $val: expr, $vp: ident) => {{
743        // If two vendor prefixes for the same property have different
744        // values, we need to flush what we have immediately to preserve order.
745        if let Some((val, prefixes)) = &self.$prop {
746          if val != $val && !prefixes.contains(*$vp) {
747            self.flush(dest, context);
748          }
749        }
750      }};
751    }
752
753    macro_rules! property {
754      ($prop: ident, $val: expr, $vp: ident) => {{
755        maybe_flush!($prop, $val, $vp);
756
757        // Otherwise, update the value and add the prefix.
758        if let Some((val, prefixes)) = &mut self.$prop {
759          *val = $val.clone();
760          *prefixes |= *$vp;
761        } else {
762          self.$prop = Some(($val.clone(), *$vp));
763          self.has_any = true;
764        }
765      }};
766    }
767
768    match property {
769      Property::AnimationName(val, vp) => property!(names, val, vp),
770      Property::AnimationDuration(val, vp) => property!(durations, val, vp),
771      Property::AnimationTimingFunction(val, vp) => property!(timing_functions, val, vp),
772      Property::AnimationIterationCount(val, vp) => property!(iteration_counts, val, vp),
773      Property::AnimationDirection(val, vp) => property!(directions, val, vp),
774      Property::AnimationPlayState(val, vp) => property!(play_states, val, vp),
775      Property::AnimationDelay(val, vp) => property!(delays, val, vp),
776      Property::AnimationFillMode(val, vp) => property!(fill_modes, val, vp),
777      Property::AnimationTimeline(val) => {
778        self.timelines = Some(val.clone());
779        self.has_any = true;
780      }
781      Property::AnimationRangeStart(val) => {
782        self.range_starts = Some(val.clone());
783        self.has_any = true;
784      }
785      Property::AnimationRangeEnd(val) => {
786        self.range_ends = Some(val.clone());
787        self.has_any = true;
788      }
789      Property::AnimationRange(val) => {
790        self.range_starts = Some(val.iter().map(|v| v.start.clone()).collect());
791        self.range_ends = Some(val.iter().map(|v| v.end.clone()).collect());
792        self.has_any = true;
793      }
794      Property::Animation(val, vp) => {
795        let names = val.iter().map(|b| b.name.clone()).collect();
796        maybe_flush!(names, &names, vp);
797
798        let durations = val.iter().map(|b| b.duration.clone()).collect();
799        maybe_flush!(durations, &durations, vp);
800
801        let timing_functions = val.iter().map(|b| b.timing_function.clone()).collect();
802        maybe_flush!(timing_functions, &timing_functions, vp);
803
804        let iteration_counts = val.iter().map(|b| b.iteration_count.clone()).collect();
805        maybe_flush!(iteration_counts, &iteration_counts, vp);
806
807        let directions = val.iter().map(|b| b.direction.clone()).collect();
808        maybe_flush!(directions, &directions, vp);
809
810        let play_states = val.iter().map(|b| b.play_state.clone()).collect();
811        maybe_flush!(play_states, &play_states, vp);
812
813        let delays = val.iter().map(|b| b.delay.clone()).collect();
814        maybe_flush!(delays, &delays, vp);
815
816        let fill_modes = val.iter().map(|b| b.fill_mode.clone()).collect();
817        maybe_flush!(fill_modes, &fill_modes, vp);
818
819        self.timelines = Some(val.iter().map(|b| b.timeline.clone()).collect());
820
821        property!(names, &names, vp);
822        property!(durations, &durations, vp);
823        property!(timing_functions, &timing_functions, vp);
824        property!(iteration_counts, &iteration_counts, vp);
825        property!(directions, &directions, vp);
826        property!(play_states, &play_states, vp);
827        property!(delays, &delays, vp);
828        property!(fill_modes, &fill_modes, vp);
829
830        // The animation shorthand resets animation-range
831        // https://drafts.csswg.org/scroll-animations/#named-range-animation-declaration
832        self.range_starts = None;
833        self.range_ends = None;
834      }
835      Property::Unparsed(val) if is_animation_property(&val.property_id) => {
836        let mut val = Cow::Borrowed(val);
837        if matches!(val.property_id, PropertyId::Animation(_)) {
838          use crate::properties::custom::Token;
839
840          // Find an identifier that isn't a keyword and replace it with an
841          // AnimationName token so it is scoped in CSS modules.
842          for token in &mut val.to_mut().value.0 {
843            match token {
844              TokenOrValue::Token(Token::Ident(id)) => {
845                if AnimationDirection::parse_string(&id).is_err()
846                  && AnimationPlayState::parse_string(&id).is_err()
847                  && AnimationFillMode::parse_string(&id).is_err()
848                  && !EasingFunction::is_ident(&id)
849                  && id.as_ref() != "infinite"
850                  && id.as_ref() != "auto"
851                {
852                  *token = TokenOrValue::AnimationName(AnimationName::Ident(CustomIdent(id.clone())));
853                }
854              }
855              TokenOrValue::Token(Token::String(s)) => {
856                *token = TokenOrValue::AnimationName(AnimationName::String(CSSString(s.clone())));
857              }
858              _ => {}
859            }
860          }
861
862          self.range_starts = None;
863          self.range_ends = None;
864        }
865
866        self.flush(dest, context);
867        dest.push(Property::Unparsed(
868          val.get_prefixed(context.targets, Feature::Animation),
869        ));
870      }
871      _ => return false,
872    }
873
874    true
875  }
876
877  fn finalize(&mut self, dest: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i, '_>) {
878    self.flush(dest, context);
879  }
880}
881
882impl<'i> AnimationHandler<'i> {
883  fn flush(&mut self, dest: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i, '_>) {
884    if !self.has_any {
885      return;
886    }
887
888    self.has_any = false;
889
890    let mut names = std::mem::take(&mut self.names);
891    let mut durations = std::mem::take(&mut self.durations);
892    let mut timing_functions = std::mem::take(&mut self.timing_functions);
893    let mut iteration_counts = std::mem::take(&mut self.iteration_counts);
894    let mut directions = std::mem::take(&mut self.directions);
895    let mut play_states = std::mem::take(&mut self.play_states);
896    let mut delays = std::mem::take(&mut self.delays);
897    let mut fill_modes = std::mem::take(&mut self.fill_modes);
898    let mut timelines_value = std::mem::take(&mut self.timelines);
899    let range_starts = std::mem::take(&mut self.range_starts);
900    let range_ends = std::mem::take(&mut self.range_ends);
901
902    if let (
903      Some((names, names_vp)),
904      Some((durations, durations_vp)),
905      Some((timing_functions, timing_functions_vp)),
906      Some((iteration_counts, iteration_counts_vp)),
907      Some((directions, directions_vp)),
908      Some((play_states, play_states_vp)),
909      Some((delays, delays_vp)),
910      Some((fill_modes, fill_modes_vp)),
911    ) = (
912      &mut names,
913      &mut durations,
914      &mut timing_functions,
915      &mut iteration_counts,
916      &mut directions,
917      &mut play_states,
918      &mut delays,
919      &mut fill_modes,
920    ) {
921      // Only use shorthand syntax if the number of animations matches on all properties.
922      let len = names.len();
923      let intersection = *names_vp
924        & *durations_vp
925        & *timing_functions_vp
926        & *iteration_counts_vp
927        & *directions_vp
928        & *play_states_vp
929        & *delays_vp
930        & *fill_modes_vp;
931      let mut timelines = if let Some(timelines) = &mut timelines_value {
932        Cow::Borrowed(timelines)
933      } else if !intersection.contains(VendorPrefix::None) {
934        // Prefixed animation shorthand does not support animation-timeline
935        Cow::Owned(std::iter::repeat(AnimationTimeline::Auto).take(len).collect())
936      } else {
937        Cow::Owned(SmallVec::new())
938      };
939
940      if !intersection.is_empty()
941        && durations.len() == len
942        && timing_functions.len() == len
943        && iteration_counts.len() == len
944        && directions.len() == len
945        && play_states.len() == len
946        && delays.len() == len
947        && fill_modes.len() == len
948        && timelines.len() == len
949      {
950        let timeline_property = if timelines.iter().any(|t| *t != AnimationTimeline::Auto)
951          && (intersection != VendorPrefix::None
952            || !context
953              .targets
954              .is_compatible(crate::compat::Feature::AnimationTimelineShorthand))
955        {
956          Some(Property::AnimationTimeline(timelines.clone().into_owned()))
957        } else {
958          None
959        };
960
961        let animations = izip!(
962          names.drain(..),
963          durations.drain(..),
964          timing_functions.drain(..),
965          iteration_counts.drain(..),
966          directions.drain(..),
967          play_states.drain(..),
968          delays.drain(..),
969          fill_modes.drain(..),
970          timelines.to_mut().drain(..)
971        )
972        .map(
973          |(
974            name,
975            duration,
976            timing_function,
977            iteration_count,
978            direction,
979            play_state,
980            delay,
981            fill_mode,
982            timeline,
983          )| {
984            Animation {
985              name,
986              duration,
987              timing_function,
988              iteration_count,
989              direction,
990              play_state,
991              delay,
992              fill_mode,
993              timeline: if timeline_property.is_some() {
994                AnimationTimeline::Auto
995              } else {
996                timeline
997              },
998            }
999          },
1000        )
1001        .collect();
1002        let prefix = context.targets.prefixes(intersection, Feature::Animation);
1003        dest.push(Property::Animation(animations, prefix));
1004        names_vp.remove(intersection);
1005        durations_vp.remove(intersection);
1006        timing_functions_vp.remove(intersection);
1007        iteration_counts_vp.remove(intersection);
1008        directions_vp.remove(intersection);
1009        play_states_vp.remove(intersection);
1010        delays_vp.remove(intersection);
1011        fill_modes_vp.remove(intersection);
1012
1013        if let Some(p) = timeline_property {
1014          dest.push(p);
1015        }
1016        timelines_value = None;
1017      }
1018    }
1019
1020    macro_rules! prop {
1021      ($var: ident, $property: ident) => {
1022        if let Some((val, vp)) = $var {
1023          if !vp.is_empty() {
1024            let prefix = context.targets.prefixes(vp, Feature::$property);
1025            dest.push(Property::$property(val, prefix))
1026          }
1027        }
1028      };
1029    }
1030
1031    prop!(names, AnimationName);
1032    prop!(durations, AnimationDuration);
1033    prop!(timing_functions, AnimationTimingFunction);
1034    prop!(iteration_counts, AnimationIterationCount);
1035    prop!(directions, AnimationDirection);
1036    prop!(play_states, AnimationPlayState);
1037    prop!(delays, AnimationDelay);
1038    prop!(fill_modes, AnimationFillMode);
1039
1040    if let Some(val) = timelines_value {
1041      dest.push(Property::AnimationTimeline(val));
1042    }
1043
1044    match (range_starts, range_ends) {
1045      (Some(range_starts), Some(range_ends)) => {
1046        if range_starts.len() == range_ends.len() {
1047          dest.push(Property::AnimationRange(
1048            range_starts
1049              .into_iter()
1050              .zip(range_ends.into_iter())
1051              .map(|(start, end)| AnimationRange { start, end })
1052              .collect(),
1053          ));
1054        } else {
1055          dest.push(Property::AnimationRangeStart(range_starts));
1056          dest.push(Property::AnimationRangeEnd(range_ends));
1057        }
1058      }
1059      (range_starts, range_ends) => {
1060        if let Some(range_starts) = range_starts {
1061          dest.push(Property::AnimationRangeStart(range_starts));
1062        }
1063
1064        if let Some(range_ends) = range_ends {
1065          dest.push(Property::AnimationRangeEnd(range_ends));
1066        }
1067      }
1068    }
1069  }
1070}
1071
1072#[inline]
1073fn is_animation_property(property_id: &PropertyId) -> bool {
1074  match property_id {
1075    PropertyId::AnimationName(_)
1076    | PropertyId::AnimationDuration(_)
1077    | PropertyId::AnimationTimingFunction(_)
1078    | PropertyId::AnimationIterationCount(_)
1079    | PropertyId::AnimationDirection(_)
1080    | PropertyId::AnimationPlayState(_)
1081    | PropertyId::AnimationDelay(_)
1082    | PropertyId::AnimationFillMode(_)
1083    | PropertyId::AnimationComposition
1084    | PropertyId::AnimationTimeline
1085    | PropertyId::AnimationRange
1086    | PropertyId::AnimationRangeStart
1087    | PropertyId::AnimationRangeEnd
1088    | PropertyId::Animation(_) => true,
1089    _ => false,
1090  }
1091}