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