Skip to main content

style/values/specified/
animation.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5//! Specified types for properties related to animations and transitions.
6
7use crate::derives::*;
8use crate::parser::{Parse, ParserContext};
9use crate::properties::{NonCustomPropertyId, PropertyId, ShorthandId};
10use crate::typed_om::ToTyped;
11use crate::values::generics::animation as generics;
12use crate::values::generics::position::{IsTreeScoped, TreeScoped};
13use crate::values::specified::{LengthPercentage, NonNegativeNumber, Time};
14use crate::values::{AtomIdent, CustomIdent, DashedIdent, KeyframesName};
15use crate::Atom;
16use cssparser::{match_ignore_ascii_case, Parser};
17use std::fmt::{self, Write};
18use style_traits::{
19    CssWriter, KeywordsCollectFn, ParseError, SpecifiedValueInfo, StyleParseErrorKind, ToCss,
20};
21
22/// A given transition property, that is either `All`, a longhand or shorthand
23/// property, or an unsupported or custom property.
24#[derive(
25    Clone, Debug, Eq, Hash, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToShmem,
26)]
27#[repr(u8)]
28pub enum TransitionProperty {
29    /// A non-custom property.
30    NonCustom(NonCustomPropertyId),
31    /// A custom property.
32    Custom(Atom),
33    /// Unrecognized property which could be any non-transitionable, custom property, or
34    /// unknown property.
35    Unsupported(CustomIdent),
36}
37
38impl ToCss for TransitionProperty {
39    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
40    where
41        W: Write,
42    {
43        match *self {
44            TransitionProperty::NonCustom(ref id) => id.to_css(dest),
45            TransitionProperty::Custom(ref name) => {
46                dest.write_str("--")?;
47                crate::values::serialize_atom_name(name, dest)
48            },
49            TransitionProperty::Unsupported(ref i) => i.to_css(dest),
50        }
51    }
52}
53
54impl ToTyped for TransitionProperty {}
55
56impl Parse for TransitionProperty {
57    fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
58        let ident = input.expect_ident()?;
59
60        let id = match PropertyId::parse_ignoring_rule_type(ident, context) {
61            Ok(id) => id,
62            Err(..) => {
63                // None is not acceptable as a single transition-property.
64                return Ok(TransitionProperty::Unsupported(CustomIdent::from_ident(
65                    ident,
66                    &["none"],
67                )?));
68            },
69        };
70
71        Ok(match id {
72            PropertyId::NonCustom(id) => TransitionProperty::NonCustom(id.unaliased()),
73            PropertyId::Custom(name) => TransitionProperty::Custom(name),
74        })
75    }
76}
77
78impl SpecifiedValueInfo for TransitionProperty {
79    fn collect_completion_keywords(f: KeywordsCollectFn) {
80        // `transition-property` can actually accept all properties and
81        // arbitrary identifiers, but `all` is a special one we'd like
82        // to list.
83        f(&["all"]);
84    }
85}
86
87impl TransitionProperty {
88    /// Returns the `none` value.
89    #[inline]
90    pub fn none() -> Self {
91        TransitionProperty::Unsupported(CustomIdent(atom!("none")))
92    }
93
94    /// Returns whether we're the `none` value.
95    #[inline]
96    pub fn is_none(&self) -> bool {
97        matches!(*self, TransitionProperty::Unsupported(ref ident) if ident.0 == atom!("none"))
98    }
99
100    /// Returns `all`.
101    #[inline]
102    pub fn all() -> Self {
103        TransitionProperty::NonCustom(NonCustomPropertyId::from_shorthand(ShorthandId::All))
104    }
105
106    /// Returns true if it is `all`.
107    #[inline]
108    pub fn is_all(&self) -> bool {
109        self == &TransitionProperty::NonCustom(NonCustomPropertyId::from_shorthand(
110            ShorthandId::All,
111        ))
112    }
113}
114
115/// A specified value for <transition-behavior-value>.
116///
117/// https://drafts.csswg.org/css-transitions-2/#transition-behavior-property
118#[derive(
119    Clone,
120    Copy,
121    Debug,
122    MallocSizeOf,
123    Parse,
124    PartialEq,
125    SpecifiedValueInfo,
126    ToComputedValue,
127    ToCss,
128    ToResolvedValue,
129    ToShmem,
130    ToTyped,
131)]
132#[repr(u8)]
133pub enum TransitionBehavior {
134    /// Transitions will not be started for discrete properties, only for interpolable properties.
135    Normal,
136    /// Transitions will be started for discrete properties as well as interpolable properties.
137    AllowDiscrete,
138}
139
140impl TransitionBehavior {
141    /// Return normal, the initial value.
142    #[inline]
143    pub fn normal() -> Self {
144        Self::Normal
145    }
146
147    /// Return true if it is normal.
148    #[inline]
149    pub fn is_normal(&self) -> bool {
150        matches!(*self, Self::Normal)
151    }
152}
153
154/// A specified value for the `animation-duration` property.
155pub type AnimationDuration = generics::GenericAnimationDuration<Time>;
156
157impl Parse for AnimationDuration {
158    fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
159        if crate::pref!("layout.css.scroll-driven-animations.enabled")
160            && input.try_parse(|i| i.expect_ident_matching("auto")).is_ok()
161        {
162            return Ok(Self::auto());
163        }
164
165        Time::parse_non_negative(context, input).map(AnimationDuration::Time)
166    }
167}
168
169/// https://drafts.csswg.org/css-animations/#animation-iteration-count
170#[derive(
171    Clone, Debug, MallocSizeOf, PartialEq, Parse, SpecifiedValueInfo, ToCss, ToShmem, ToTyped,
172)]
173pub enum AnimationIterationCount {
174    /// A `<number>` value.
175    Number(NonNegativeNumber),
176    /// The `infinite` keyword.
177    Infinite,
178}
179
180impl AnimationIterationCount {
181    /// Returns the value `1.0`.
182    #[inline]
183    pub fn one() -> Self {
184        Self::Number(NonNegativeNumber::new(1.0))
185    }
186
187    /// Returns true if it's `1.0`.
188    #[inline]
189    pub fn is_one(&self) -> bool {
190        *self == Self::one()
191    }
192}
193
194/// A value for the `animation-name` property.
195#[derive(
196    Clone,
197    Debug,
198    Eq,
199    Hash,
200    MallocSizeOf,
201    PartialEq,
202    SpecifiedValueInfo,
203    ToComputedValue,
204    ToCss,
205    ToResolvedValue,
206    ToShmem,
207    ToTyped,
208)]
209#[value_info(other_values = "none")]
210#[repr(C)]
211pub struct AnimationName(pub KeyframesName);
212
213impl AnimationName {
214    /// Get the name of the animation as an `Atom`.
215    pub fn as_atom(&self) -> Option<&Atom> {
216        if self.is_none() {
217            return None;
218        }
219        Some(self.0.as_atom())
220    }
221
222    /// Returns the `none` value.
223    pub fn none() -> Self {
224        AnimationName(KeyframesName::none())
225    }
226
227    /// Returns whether this is the none value.
228    pub fn is_none(&self) -> bool {
229        self.0.is_none()
230    }
231}
232
233impl Parse for AnimationName {
234    fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
235        if let Ok(name) = input.try_parse(|input| KeyframesName::parse(context, input)) {
236            return Ok(AnimationName(name));
237        }
238
239        input.expect_ident_matching("none")?;
240        Ok(AnimationName(KeyframesName::none()))
241    }
242}
243
244/// https://drafts.csswg.org/css-animations/#propdef-animation-direction
245#[derive(
246    Copy,
247    Clone,
248    Debug,
249    MallocSizeOf,
250    Parse,
251    PartialEq,
252    SpecifiedValueInfo,
253    ToComputedValue,
254    ToCss,
255    ToResolvedValue,
256    ToShmem,
257    ToTyped,
258)]
259#[repr(u8)]
260#[allow(missing_docs)]
261pub enum AnimationDirection {
262    Normal,
263    Reverse,
264    Alternate,
265    AlternateReverse,
266}
267
268impl AnimationDirection {
269    /// Returns true if the name matches any animation-direction keyword.
270    #[inline]
271    pub fn match_keywords(name: &AnimationName) -> bool {
272        if let Some(name) = name.as_atom() {
273            #[cfg(feature = "gecko")]
274            return name.with_str(|n| Self::from_ident(n).is_ok());
275            #[cfg(feature = "servo")]
276            return Self::from_ident(name).is_ok();
277        }
278        false
279    }
280}
281
282/// https://drafts.csswg.org/css-animations/#animation-play-state
283#[derive(
284    Copy,
285    Clone,
286    Debug,
287    MallocSizeOf,
288    Parse,
289    PartialEq,
290    SpecifiedValueInfo,
291    ToComputedValue,
292    ToCss,
293    ToResolvedValue,
294    ToShmem,
295    ToTyped,
296)]
297#[repr(u8)]
298#[allow(missing_docs)]
299pub enum AnimationPlayState {
300    Running,
301    Paused,
302}
303
304impl AnimationPlayState {
305    /// Returns true if the name matches any animation-play-state keyword.
306    #[inline]
307    pub fn match_keywords(name: &AnimationName) -> bool {
308        if let Some(name) = name.as_atom() {
309            #[cfg(feature = "gecko")]
310            return name.with_str(|n| Self::from_ident(n).is_ok());
311            #[cfg(feature = "servo")]
312            return Self::from_ident(name).is_ok();
313        }
314        false
315    }
316}
317
318/// https://drafts.csswg.org/css-animations/#propdef-animation-fill-mode
319#[derive(
320    Copy,
321    Clone,
322    Debug,
323    MallocSizeOf,
324    Parse,
325    PartialEq,
326    SpecifiedValueInfo,
327    ToComputedValue,
328    ToCss,
329    ToResolvedValue,
330    ToShmem,
331    ToTyped,
332)]
333#[repr(u8)]
334#[allow(missing_docs)]
335pub enum AnimationFillMode {
336    None,
337    Forwards,
338    Backwards,
339    Both,
340}
341
342impl AnimationFillMode {
343    /// Returns true if the name matches any animation-fill-mode keyword.
344    /// Note: animation-name:none is its initial value, so we don't have to match none here.
345    #[inline]
346    pub fn match_keywords(name: &AnimationName) -> bool {
347        if let Some(name) = name.as_atom() {
348            #[cfg(feature = "gecko")]
349            return name.with_str(|n| Self::from_ident(n).is_ok());
350            #[cfg(feature = "servo")]
351            return Self::from_ident(name).is_ok();
352        }
353        false
354    }
355}
356
357/// https://drafts.csswg.org/css-animations-2/#animation-composition
358#[derive(
359    Copy,
360    Clone,
361    Debug,
362    MallocSizeOf,
363    Parse,
364    PartialEq,
365    SpecifiedValueInfo,
366    ToComputedValue,
367    ToCss,
368    ToResolvedValue,
369    ToShmem,
370    ToTyped,
371)]
372#[repr(u8)]
373#[allow(missing_docs)]
374pub enum AnimationComposition {
375    Replace,
376    Add,
377    Accumulate,
378}
379
380/// A value for the <Scroller> used in scroll().
381///
382/// https://drafts.csswg.org/scroll-animations-1/rewrite#typedef-scroller
383#[derive(
384    Copy,
385    Clone,
386    Debug,
387    Eq,
388    Hash,
389    MallocSizeOf,
390    Parse,
391    PartialEq,
392    SpecifiedValueInfo,
393    ToComputedValue,
394    ToCss,
395    ToResolvedValue,
396    ToShmem,
397)]
398#[repr(u8)]
399pub enum Scroller {
400    /// The nearest ancestor scroll container. (Default.)
401    Nearest,
402    /// The document viewport as the scroll container.
403    Root,
404    /// Specifies to use the element’s own principal box as the scroll container.
405    #[css(keyword = "self")]
406    SelfElement,
407}
408
409impl Scroller {
410    /// Returns true if it is default.
411    #[inline]
412    fn is_default(&self) -> bool {
413        matches!(*self, Self::Nearest)
414    }
415}
416
417impl Default for Scroller {
418    fn default() -> Self {
419        Self::Nearest
420    }
421}
422
423/// A value for the <Axis> used in scroll(), or a value for {scroll|view}-timeline-axis.
424///
425/// https://drafts.csswg.org/scroll-animations-1/#typedef-axis
426/// https://drafts.csswg.org/scroll-animations-1/#scroll-timeline-axis
427/// https://drafts.csswg.org/scroll-animations-1/#view-timeline-axis
428#[derive(
429    Copy,
430    Clone,
431    Debug,
432    Eq,
433    Hash,
434    MallocSizeOf,
435    Parse,
436    PartialEq,
437    SpecifiedValueInfo,
438    ToComputedValue,
439    ToCss,
440    ToResolvedValue,
441    ToShmem,
442    ToTyped,
443)]
444#[repr(u8)]
445pub enum ScrollAxis {
446    /// The block axis of the scroll container. (Default.)
447    Block = 0,
448    /// The inline axis of the scroll container.
449    Inline = 1,
450    /// The horizontal axis of the scroll container.
451    X = 2,
452    /// The vertical axis of the scroll container.
453    Y = 3,
454}
455
456impl ScrollAxis {
457    /// Returns true if it is default.
458    #[inline]
459    pub fn is_default(&self) -> bool {
460        matches!(*self, Self::Block)
461    }
462}
463
464impl Default for ScrollAxis {
465    fn default() -> Self {
466        Self::Block
467    }
468}
469
470/// The scroll() notation.
471/// https://drafts.csswg.org/scroll-animations-1/#scroll-notation
472#[derive(
473    Copy,
474    Clone,
475    Debug,
476    MallocSizeOf,
477    PartialEq,
478    SpecifiedValueInfo,
479    ToComputedValue,
480    ToCss,
481    ToResolvedValue,
482    ToShmem,
483)]
484#[css(function = "scroll")]
485#[repr(C)]
486pub struct ScrollFunction {
487    /// The scroll container element whose scroll position drives the progress of the timeline.
488    #[css(skip_if = "Scroller::is_default")]
489    pub scroller: Scroller,
490    /// The axis of scrolling that drives the progress of the timeline.
491    #[css(skip_if = "ScrollAxis::is_default")]
492    pub axis: ScrollAxis,
493}
494
495impl ScrollFunction {
496    /// Parse the inner function arguments of `scroll()`.
497    fn parse_arguments(input: &mut Parser) -> Result<Self, ParseError> {
498        // <scroll()> = scroll( [ <scroller> || <axis> ]? )
499        // https://drafts.csswg.org/scroll-animations-1/#funcdef-scroll
500        let mut scroller = None;
501        let mut axis = None;
502        loop {
503            if scroller.is_none() {
504                scroller = input.try_parse(Scroller::parse).ok();
505            }
506
507            if axis.is_none() {
508                axis = input.try_parse(ScrollAxis::parse).ok();
509                if axis.is_some() {
510                    continue;
511                }
512            }
513            break;
514        }
515
516        Ok(Self {
517            scroller: scroller.unwrap_or_default(),
518            axis: axis.unwrap_or_default(),
519        })
520    }
521}
522
523impl generics::ViewFunction<LengthPercentage> {
524    /// Parse the inner function arguments of `view()`.
525    fn parse_arguments(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
526        // <view()> = view( [ <axis> || <'view-timeline-inset'> ]? )
527        // https://drafts.csswg.org/scroll-animations-1/#funcdef-view
528        let mut axis = None;
529        let mut inset = None;
530        loop {
531            if axis.is_none() {
532                axis = input.try_parse(ScrollAxis::parse).ok();
533            }
534
535            if inset.is_none() {
536                inset = input
537                    .try_parse(|i| ViewTimelineInset::parse(context, i))
538                    .ok();
539                if inset.is_some() {
540                    continue;
541                }
542            }
543            break;
544        }
545
546        Ok(Self {
547            inset: inset.unwrap_or_default(),
548            axis: axis.unwrap_or_default(),
549        })
550    }
551}
552
553/// The typedef of scroll-timeline-name or view-timeline-name.
554///
555/// https://drafts.csswg.org/scroll-animations-1/#scroll-timeline-name
556/// https://drafts.csswg.org/scroll-animations-1/#view-timeline-name
557pub type TimelineName = TreeScoped<TimelineIdent>;
558
559impl TimelineName {
560    /// Return the `none` value.
561    pub fn none() -> Self {
562        Self::with_default_level(TimelineIdent::none())
563    }
564}
565
566/// The identifier for a timeline name.
567#[derive(
568    Clone,
569    Debug,
570    Eq,
571    Hash,
572    MallocSizeOf,
573    PartialEq,
574    SpecifiedValueInfo,
575    ToComputedValue,
576    ToResolvedValue,
577    ToShmem,
578)]
579#[repr(C)]
580pub struct TimelineIdent(DashedIdent);
581
582impl TimelineIdent {
583    /// Returns the `none` value.
584    pub fn none() -> Self {
585        Self(DashedIdent::empty())
586    }
587
588    /// Check if this is `none` value.
589    pub fn is_none(&self) -> bool {
590        self.0.is_empty()
591    }
592}
593
594impl IsTreeScoped for TimelineIdent {
595    fn is_tree_scoped(&self) -> bool {
596        !self.is_none()
597    }
598}
599
600impl Parse for TimelineIdent {
601    fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
602        if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() {
603            return Ok(Self::none());
604        }
605
606        DashedIdent::parse(context, input).map(TimelineIdent)
607    }
608}
609
610impl ToCss for TimelineIdent {
611    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
612    where
613        W: Write,
614    {
615        if self.is_none() {
616            return dest.write_str("none");
617        }
618
619        self.0.to_css(dest)
620    }
621}
622
623impl ToTyped for TimelineName {}
624
625/// A specified value for the `animation-timeline` property.
626pub type AnimationTimeline = generics::GenericAnimationTimeline<LengthPercentage>;
627
628impl Parse for AnimationTimeline {
629    fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
630        use crate::values::generics::animation::ViewFunction;
631
632        // <single-animation-timeline> = auto | none | <dashed-ident> | <scroll()> | <view()>
633        // https://drafts.csswg.org/css-animations-2/#typedef-single-animation-timeline
634
635        if input.try_parse(|i| i.expect_ident_matching("auto")).is_ok() {
636            return Ok(Self::Auto);
637        }
638
639        // This parses none or <dashed-indent>.
640        if let Ok(name) = input.try_parse(|i| TimelineName::parse(context, i)) {
641            return Ok(AnimationTimeline::Timeline(name));
642        }
643
644        // Parse <scroll()> or <view()>.
645        let function = input.expect_function()?.clone();
646        input.parse_nested_block(move |i| {
647            match_ignore_ascii_case! { &function,
648                "scroll" => ScrollFunction::parse_arguments(i).map(Self::Scroll),
649                "view" => ViewFunction::parse_arguments(context, i).map(Self::View),
650                _ => {
651                    Err(ParseError::custom(
652                        StyleParseErrorKind::UnexpectedFunction
653                    ))
654                },
655            }
656        })
657    }
658}
659
660/// A specified value for the `view-timeline-inset` property.
661pub type ViewTimelineInset = generics::GenericViewTimelineInset<LengthPercentage>;
662
663impl Parse for ViewTimelineInset {
664    fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
665        use crate::values::specified::LengthPercentageOrAuto;
666
667        let start = LengthPercentageOrAuto::parse(context, input)?;
668        let end = match input.try_parse(|input| LengthPercentageOrAuto::parse(context, input)) {
669            Ok(end) => end,
670            Err(_) => start.clone(),
671        };
672
673        Ok(Self { start, end })
674    }
675}
676
677/// The view-transition-name: `none | <custom-ident> | match-element`.
678///
679/// https://drafts.csswg.org/css-view-transitions-1/#view-transition-name-prop
680/// https://drafts.csswg.org/css-view-transitions-2/#auto-vt-name
681// TODO: auto keyword.
682#[derive(
683    Clone,
684    Debug,
685    Eq,
686    Hash,
687    PartialEq,
688    MallocSizeOf,
689    SpecifiedValueInfo,
690    ToCss,
691    ToComputedValue,
692    ToResolvedValue,
693    ToShmem,
694    ToTyped,
695)]
696#[repr(transparent)]
697#[typed(todo_derive_fields)]
698#[value_info(other_values = "none, match-element")]
699pub struct ViewTransitionNameKeyword(AtomIdent);
700
701impl ViewTransitionNameKeyword {
702    /// Returns the `none` value.
703    pub fn none() -> Self {
704        Self(AtomIdent::new(atom!("none")))
705    }
706}
707
708impl IsTreeScoped for ViewTransitionNameKeyword {
709    fn is_tree_scoped(&self) -> bool {
710        self.0 .0 != atom!("none")
711    }
712}
713
714impl Parse for ViewTransitionNameKeyword {
715    fn parse(_: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
716        let ident = input.expect_ident()?;
717        if ident.eq_ignore_ascii_case("none") {
718            return Ok(Self::none());
719        }
720
721        if ident.eq_ignore_ascii_case("match-element") {
722            return Ok(Self(AtomIdent::new(atom!("match-element"))));
723        }
724
725        // We check none already, so don't need to exclude none here.
726        // Note: "auto" is not supported yet so we exclude it.
727        CustomIdent::from_ident(ident, &["auto"]).map(|i| Self(AtomIdent::new(i.0)))
728    }
729}
730
731/// https://drafts.csswg.org/css-view-transitions-1/#view-transition-name-prop
732pub type ViewTransitionName = TreeScoped<ViewTransitionNameKeyword>;
733
734impl ViewTransitionName {
735    /// Return the `none` value.
736    pub fn none() -> Self {
737        Self::with_default_level(ViewTransitionNameKeyword::none())
738    }
739}
740
741/// The view-transition-class: `none | <custom-ident>+`.
742///
743/// https://drafts.csswg.org/css-view-transitions-2/#view-transition-class-prop
744///
745/// Empty slice represents `none`.
746#[derive(
747    Clone,
748    Debug,
749    Default,
750    Eq,
751    Hash,
752    PartialEq,
753    MallocSizeOf,
754    SpecifiedValueInfo,
755    ToComputedValue,
756    ToCss,
757    ToResolvedValue,
758    ToShmem,
759    ToTyped,
760)]
761#[repr(C)]
762#[value_info(other_values = "none")]
763pub struct ViewTransitionClassList(
764    #[css(iterable, if_empty = "none")]
765    #[ignore_malloc_size_of = "Arc"]
766    crate::ArcSlice<CustomIdent>,
767);
768
769impl IsTreeScoped for ViewTransitionClassList {
770    fn is_tree_scoped(&self) -> bool {
771        !self.is_none()
772    }
773}
774
775impl ViewTransitionClassList {
776    /// Returns the default value, `none`. We use the default slice (i.e. empty) to represent it.
777    pub fn none() -> Self {
778        Self(Default::default())
779    }
780
781    /// Returns whether this is the `none` value.
782    pub fn is_none(&self) -> bool {
783        self.0.is_empty()
784    }
785
786    /// Iterates over the contained custom idents.
787    pub fn iter(&self) -> impl Iterator<Item = &CustomIdent> {
788        self.0.iter()
789    }
790}
791
792impl Parse for ViewTransitionClassList {
793    fn parse(_: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
794        use style_traits::{Separator, Space};
795
796        if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() {
797            return Ok(Self::none());
798        }
799
800        Ok(Self(crate::ArcSlice::from_iter(
801            Space::parse(input, |i| CustomIdent::parse(i, &["none"]))?.into_iter(),
802        )))
803    }
804}
805
806/// https://drafts.csswg.org/css-view-transitions-2/#view-transition-class-prop
807pub type ViewTransitionClass = TreeScoped<ViewTransitionClassList>;
808
809impl ViewTransitionClass {
810    /// Returns the default value, `none`.
811    pub fn none() -> Self {
812        Self::with_default_level(ViewTransitionClassList::none())
813    }
814}
815
816/// The <timeline-range-name> value type, which indicates a CSS identifier representing one of the
817/// predefined named timeline ranges.
818/// https://drafts.csswg.org/scroll-animations-1/#named-ranges
819///
820/// For now, only view timeline ranges use this type.
821/// https://drafts.csswg.org/scroll-animations-1/#view-timelines-ranges
822#[derive(
823    Copy,
824    Clone,
825    Debug,
826    Eq,
827    MallocSizeOf,
828    Parse,
829    PartialEq,
830    SpecifiedValueInfo,
831    ToComputedValue,
832    ToCss,
833    ToResolvedValue,
834    ToShmem,
835    ToTyped,
836)]
837#[repr(u8)]
838pub enum TimelineRangeName {
839    /// The default normal value.
840    #[css(skip)]
841    Normal,
842    /// No timeline range name specified.
843    #[css(skip)]
844    None,
845    /// Represents the full range of the view progress timeline
846    Cover,
847    /// Represents the range during which the principal box is either fully contained by, or fully
848    /// covers, its view progress visibility range within the scrollport.
849    Contain,
850    /// Represents the range during which the principal box is entering the view progress
851    /// visibility range.
852    Entry,
853    /// Represents the range during which the principal box is exiting the view progress visibility
854    /// range.
855    Exit,
856    /// Represents the range during which the principal box crosses the end border edge.
857    EntryCrossing,
858    /// Represents the range during which the principal box crosses the start border edge.
859    ExitCrossing,
860    /// Represents the full range of the scroll container on which the view progress timeline is
861    /// defined.
862    Scroll,
863}
864
865impl TimelineRangeName {
866    /// Returns true if it is `normal`.
867    #[inline]
868    pub fn is_normal(&self) -> bool {
869        matches!(*self, Self::Normal)
870    }
871
872    /// Returns true if it is `none`.
873    #[inline]
874    pub fn is_none(&self) -> bool {
875        matches!(*self, Self::None)
876    }
877}
878
879/// The internal value for `animation-range-start` and `animation-range-end`.
880pub type AnimationRangeValue = generics::GenericAnimationRangeValue<LengthPercentage>;
881
882fn parse_animation_range(
883    context: &ParserContext,
884    input: &mut Parser,
885    default: LengthPercentage,
886) -> Result<AnimationRangeValue, ParseError> {
887    if input
888        .try_parse(|i| i.expect_ident_matching("normal"))
889        .is_ok()
890    {
891        return Ok(AnimationRangeValue::normal(default));
892    }
893
894    if let Ok(lp) = input.try_parse(|i| LengthPercentage::parse(context, i)) {
895        return Ok(AnimationRangeValue::length_percentage(lp));
896    }
897
898    let name = TimelineRangeName::parse(input)?;
899    let lp = input
900        .try_parse(|i| LengthPercentage::parse(context, i))
901        .unwrap_or(default);
902    Ok(AnimationRangeValue::new(name, lp))
903}
904
905/// A specified value for the `animation-range-start`.
906pub type AnimationRangeStart = generics::GenericAnimationRangeStart<LengthPercentage>;
907
908impl Parse for AnimationRangeStart {
909    fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
910        parse_animation_range(context, input, LengthPercentage::zero_percent()).map(Self)
911    }
912}
913
914/// A specified value for the `animation-range-end`.
915pub type AnimationRangeEnd = generics::GenericAnimationRangeEnd<LengthPercentage>;
916
917impl Parse for AnimationRangeEnd {
918    fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
919        parse_animation_range(context, input, LengthPercentage::hundred_percent()).map(Self)
920    }
921}