Skip to main content

lightningcss/rules/
view_transition.rs

1//! The `@view-transition` rule.
2
3use super::Location;
4use crate::error::{ParserError, PrinterError};
5use crate::printer::Printer;
6use crate::properties::custom::CustomProperty;
7use crate::stylesheet::ParserOptions;
8use crate::traits::{Parse, ToCss};
9use crate::values::ident::NoneOrCustomIdentList;
10#[cfg(feature = "visitor")]
11use crate::visitor::Visit;
12use cssparser::*;
13
14/// A [@view-transition](https://drafts.csswg.org/css-view-transitions-2/#view-transition-rule) rule.
15#[derive(Debug, PartialEq, Clone)]
16#[cfg_attr(feature = "visitor", derive(Visit))]
17#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
18#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
19#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
20pub struct ViewTransitionRule<'i> {
21  /// Declarations in the `@view-transition` rule.
22  #[cfg_attr(feature = "serde", serde(borrow))]
23  pub properties: Vec<ViewTransitionProperty<'i>>,
24  /// The location of the rule in the source file.
25  #[cfg_attr(feature = "visitor", skip_visit)]
26  pub loc: Location,
27}
28
29/// A property within a `@view-transition` rule.
30///
31///  See [ViewTransitionRule](ViewTransitionRule).
32#[derive(Debug, Clone, PartialEq)]
33#[cfg_attr(feature = "visitor", derive(Visit))]
34#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
35#[cfg_attr(
36  feature = "serde",
37  derive(serde::Serialize, serde::Deserialize),
38  serde(tag = "property", content = "value", rename_all = "kebab-case")
39)]
40#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
41pub enum ViewTransitionProperty<'i> {
42  /// The `navigation` property.
43  Navigation(Navigation),
44  /// The `types` property.
45  #[cfg_attr(feature = "serde", serde(borrow))]
46  Types(NoneOrCustomIdentList<'i>),
47  /// An unknown or unsupported property.
48  Custom(CustomProperty<'i>),
49}
50
51/// A value for the [navigation](https://drafts.csswg.org/css-view-transitions-2/#view-transition-navigation-descriptor)
52/// property in a `@view-transition` rule.
53#[derive(Debug, Clone, PartialEq, Default, Parse, ToCss)]
54#[cfg_attr(feature = "visitor", derive(Visit))]
55#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
56#[cfg_attr(
57  feature = "serde",
58  derive(serde::Serialize, serde::Deserialize),
59  serde(rename_all = "kebab-case")
60)]
61#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
62pub enum Navigation {
63  /// There will be no transition.
64  #[default]
65  None,
66  /// The transition will be enabled if the navigation is same-origin.
67  Auto,
68}
69
70pub(crate) struct ViewTransitionDeclarationParser;
71
72impl<'i> cssparser::DeclarationParser<'i> for ViewTransitionDeclarationParser {
73  type Declaration = ViewTransitionProperty<'i>;
74  type Error = ParserError<'i>;
75
76  fn parse_value<'t>(
77    &mut self,
78    name: CowRcStr<'i>,
79    input: &mut Parser<'i, 't>,
80    _declaration_start: &ParserState,
81  ) -> Result<Self::Declaration, ParseError<'i, Self::Error>> {
82    let state = input.state();
83    match_ignore_ascii_case! { &name,
84      "navigation" => {
85        // https://drafts.csswg.org/css-view-transitions-2/#view-transition-navigation-descriptor
86        if let Ok(navigation) = Navigation::parse(input) {
87          return Ok(ViewTransitionProperty::Navigation(navigation));
88        }
89      },
90      "types" => {
91        // https://drafts.csswg.org/css-view-transitions-2/#types-cross-doc
92        if let Ok(types) = NoneOrCustomIdentList::parse(input) {
93          return Ok(ViewTransitionProperty::Types(types));
94        }
95      },
96      _ => return Err(input.new_custom_error(ParserError::InvalidDeclaration))
97    }
98
99    input.reset(&state);
100    return Ok(ViewTransitionProperty::Custom(CustomProperty::parse(
101      name.into(),
102      input,
103      &ParserOptions::default(),
104    )?));
105  }
106}
107
108/// Default methods reject all at rules.
109impl<'i> AtRuleParser<'i> for ViewTransitionDeclarationParser {
110  type Prelude = ();
111  type AtRule = ViewTransitionProperty<'i>;
112  type Error = ParserError<'i>;
113}
114
115impl<'i> QualifiedRuleParser<'i> for ViewTransitionDeclarationParser {
116  type Prelude = ();
117  type QualifiedRule = ViewTransitionProperty<'i>;
118  type Error = ParserError<'i>;
119}
120
121impl<'i> RuleBodyItemParser<'i, ViewTransitionProperty<'i>, ParserError<'i>> for ViewTransitionDeclarationParser {
122  fn parse_qualified(&self) -> bool {
123    false
124  }
125
126  fn parse_declarations(&self) -> bool {
127    true
128  }
129}
130
131impl<'i> ViewTransitionRule<'i> {
132  pub(crate) fn parse<'t>(
133    input: &mut Parser<'i, 't>,
134    loc: Location,
135  ) -> Result<Self, ParseError<'i, ParserError<'i>>> {
136    let mut decl_parser = ViewTransitionDeclarationParser;
137    let mut parser = RuleBodyParser::new(input, &mut decl_parser);
138    let mut properties = vec![];
139    while let Some(decl) = parser.next() {
140      if let Ok(decl) = decl {
141        properties.push(decl);
142      }
143    }
144
145    Ok(ViewTransitionRule { properties, loc })
146  }
147}
148
149impl<'i> ToCss for ViewTransitionRule<'i> {
150  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
151  where
152    W: std::fmt::Write,
153  {
154    #[cfg(feature = "sourcemap")]
155    dest.add_mapping(self.loc);
156    dest.write_str("@view-transition")?;
157    dest.whitespace()?;
158    dest.write_char('{')?;
159    dest.indent();
160    let len = self.properties.len();
161    for (i, prop) in self.properties.iter().enumerate() {
162      dest.newline()?;
163      prop.to_css(dest)?;
164      if i != len - 1 || !dest.minify {
165        dest.write_char(';')?;
166      }
167    }
168    dest.dedent();
169    dest.newline()?;
170    dest.write_char('}')
171  }
172}
173
174impl<'i> ToCss for ViewTransitionProperty<'i> {
175  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
176  where
177    W: std::fmt::Write,
178  {
179    macro_rules! property {
180      ($prop: literal, $value: expr) => {{
181        dest.write_str($prop)?;
182        dest.delim(':', false)?;
183        $value.to_css(dest)
184      }};
185    }
186
187    match self {
188      ViewTransitionProperty::Navigation(f) => property!("navigation", f),
189      ViewTransitionProperty::Types(t) => property!("types", t),
190      ViewTransitionProperty::Custom(custom) => {
191        dest.write_str(custom.name.as_ref())?;
192        dest.delim(':', false)?;
193        custom.value.to_css(dest, true)
194      }
195    }
196  }
197}