1use 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#[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 None,
40 #[cfg_attr(feature = "serde", serde(borrow))]
42 Ident(CustomIdent<'i>),
43 #[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 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
88pub type AnimationNameList<'i> = SmallVec<[AnimationName<'i>; 1]>;
90
91#[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 Number(CSSNumber),
104 Infinite,
106}
107
108impl Default for AnimationIterationCount {
109 fn default() -> Self {
110 AnimationIterationCount::Number(1.0)
111 }
112}
113
114enum_property! {
115 pub enum AnimationDirection {
117 Normal,
119 Reverse,
121 Alternate,
123 AlternateReverse,
125 }
126}
127
128impl Default for AnimationDirection {
129 fn default() -> Self {
130 AnimationDirection::Normal
131 }
132}
133
134enum_property! {
135 pub enum AnimationPlayState {
137 Running,
139 Paused,
141 }
142}
143
144impl Default for AnimationPlayState {
145 fn default() -> Self {
146 AnimationPlayState::Running
147 }
148}
149
150enum_property! {
151 pub enum AnimationFillMode {
153 None,
155 Forwards,
157 Backwards,
159 Both,
161 }
162}
163
164impl Default for AnimationFillMode {
165 fn default() -> Self {
166 AnimationFillMode::None
167 }
168}
169
170enum_property! {
171 pub enum AnimationComposition {
173 Replace,
175 Add,
177 Accumulate,
179 }
180}
181
182#[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 Auto,
195 None,
197 #[cfg_attr(feature = "serde", serde(borrow))]
199 DashedIdent(DashedIdent<'i>),
200 Scroll(ScrollTimeline),
202 View(ViewTimeline),
204}
205
206impl<'i> Default for AnimationTimeline<'i> {
207 fn default() -> Self {
208 AnimationTimeline::Auto
209 }
210}
211
212#[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 pub scroller: Scroller,
221 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 pub enum Scroller {
280 "root": Root,
282 "nearest": Nearest,
284 "self": SelfElement,
286 }
287}
288
289impl Default for Scroller {
290 fn default() -> Self {
291 Scroller::Nearest
292 }
293}
294
295enum_property! {
296 pub enum ScrollAxis {
298 Block,
300 Inline,
302 X,
304 Y,
306 }
307}
308
309impl Default for ScrollAxis {
310 fn default() -> Self {
311 ScrollAxis::Block
312 }
313}
314
315#[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 pub axis: ScrollAxis,
324 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#[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 Cover,
392 Contain,
395 Entry,
397 Exit,
399 EntryCrossing,
401 ExitCrossing,
403}
404
405#[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 Normal,
415 #[cfg_attr(feature = "serde", serde(untagged))]
417 LengthPercentage(LengthPercentage),
418 #[cfg_attr(feature = "serde", serde(untagged))]
420 TimelineRange {
421 name: TimelineRangeName,
423 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#[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#[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#[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 pub start: AnimationRangeStart,
527 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 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 pub struct Animation<'i>(VendorPrefix) {
583 #[cfg_attr(feature = "serde", serde(borrow))]
585 name: AnimationName(AnimationName<'i>, VendorPrefix),
586 duration: AnimationDuration(Time, VendorPrefix),
588 timing_function: AnimationTimingFunction(EasingFunction, VendorPrefix),
590 iteration_count: AnimationIterationCount(AnimationIterationCount, VendorPrefix),
592 direction: AnimationDirection(AnimationDirection, VendorPrefix),
594 play_state: AnimationPlayState(AnimationPlayState, VendorPrefix),
596 delay: AnimationDelay(Time, VendorPrefix),
598 fill_mode: AnimationFillMode(AnimationFillMode, VendorPrefix),
600 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 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
715pub 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 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 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 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 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 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 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}