lightningcss/properties/
list.rs

1//! CSS properties related to lists and counters.
2
3use super::{Property, PropertyId};
4use crate::context::PropertyHandlerContext;
5use crate::declaration::{DeclarationBlock, DeclarationList};
6use crate::error::{ParserError, PrinterError};
7use crate::macros::{define_shorthand, enum_property, shorthand_handler, shorthand_property};
8use crate::printer::Printer;
9use crate::targets::{Browsers, Targets};
10use crate::traits::{FallbackValues, IsCompatible, Parse, PropertyHandler, Shorthand, ToCss};
11use crate::values::string::CSSString;
12use crate::values::{ident::CustomIdent, image::Image};
13#[cfg(feature = "visitor")]
14use crate::visitor::Visit;
15use cssparser::*;
16
17/// A value for the [list-style-type](https://www.w3.org/TR/2020/WD-css-lists-3-20201117/#text-markers) property.
18#[derive(Debug, Clone, PartialEq, Parse, ToCss)]
19#[cfg_attr(feature = "visitor", derive(Visit))]
20#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
21#[cfg_attr(
22  feature = "serde",
23  derive(serde::Serialize, serde::Deserialize),
24  serde(tag = "type", content = "value", rename_all = "kebab-case")
25)]
26#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
27pub enum ListStyleType<'i> {
28  /// No marker.
29  None,
30  /// An explicit marker string.
31  #[cfg_attr(feature = "serde", serde(borrow))]
32  String(CSSString<'i>),
33  /// A named counter style.
34  CounterStyle(CounterStyle<'i>),
35}
36
37impl Default for ListStyleType<'_> {
38  fn default() -> Self {
39    ListStyleType::CounterStyle(CounterStyle::Predefined(PredefinedCounterStyle::Disc))
40  }
41}
42
43impl IsCompatible for ListStyleType<'_> {
44  fn is_compatible(&self, browsers: Browsers) -> bool {
45    match self {
46      ListStyleType::CounterStyle(c) => c.is_compatible(browsers),
47      ListStyleType::String(..) => crate::compat::Feature::StringListStyleType.is_compatible(browsers),
48      ListStyleType::None => true,
49    }
50  }
51}
52
53/// A [counter-style](https://www.w3.org/TR/css-counter-styles-3/#typedef-counter-style) name.
54#[derive(Debug, Clone, PartialEq)]
55#[cfg_attr(feature = "visitor", derive(Visit))]
56#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
57#[cfg_attr(
58  feature = "serde",
59  derive(serde::Serialize, serde::Deserialize),
60  serde(tag = "type", rename_all = "kebab-case")
61)]
62#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
63pub enum CounterStyle<'i> {
64  /// A predefined counter style name.
65  #[cfg_attr(
66    feature = "serde",
67    serde(with = "crate::serialization::ValueWrapper::<PredefinedCounterStyle>")
68  )]
69  Predefined(PredefinedCounterStyle),
70  /// A custom counter style name.
71  #[cfg_attr(
72    feature = "serde",
73    serde(borrow, with = "crate::serialization::ValueWrapper::<CustomIdent>")
74  )]
75  Name(CustomIdent<'i>),
76  /// An inline [`symbols()`](https://www.w3.org/TR/css-counter-styles-3/#symbols-function) definition.
77  Symbols {
78    /// The counter system.
79    #[cfg_attr(feature = "serde", serde(default))]
80    system: SymbolsType,
81    /// The symbols.
82    symbols: Vec<Symbol<'i>>,
83  },
84}
85
86macro_rules! counter_styles {
87  (
88    $(#[$outer:meta])*
89    $vis:vis enum $name:ident {
90      $(
91        $(#[$meta: meta])*
92        $id: ident,
93      )+
94    }
95  ) => {
96    enum_property! {
97      /// A [predefined counter](https://www.w3.org/TR/css-counter-styles-3/#predefined-counters) style.
98      #[allow(missing_docs)]
99      pub enum PredefinedCounterStyle {
100        $(
101           $(#[$meta])*
102           $id,
103        )+
104      }
105    }
106
107    impl IsCompatible for PredefinedCounterStyle {
108      fn is_compatible(&self, browsers: Browsers) -> bool {
109        match self {
110          $(
111            PredefinedCounterStyle::$id => paste::paste! {
112              crate::compat::Feature::[<$id ListStyleType>].is_compatible(browsers)
113            },
114          )+
115        }
116      }
117    }
118  };
119}
120
121counter_styles! {
122  /// A [predefined counter](https://www.w3.org/TR/css-counter-styles-3/#predefined-counters) style.
123  #[allow(missing_docs)]
124  pub enum PredefinedCounterStyle {
125    // https://www.w3.org/TR/css-counter-styles-3/#simple-numeric
126    Decimal,
127    DecimalLeadingZero,
128    ArabicIndic,
129    Armenian,
130    UpperArmenian,
131    LowerArmenian,
132    Bengali,
133    Cambodian,
134    Khmer,
135    CjkDecimal,
136    Devanagari,
137    Georgian,
138    Gujarati,
139    Gurmukhi,
140    Hebrew,
141    Kannada,
142    Lao,
143    Malayalam,
144    Mongolian,
145    Myanmar,
146    Oriya,
147    Persian,
148    LowerRoman,
149    UpperRoman,
150    Tamil,
151    Telugu,
152    Thai,
153    Tibetan,
154
155    // https://www.w3.org/TR/css-counter-styles-3/#simple-alphabetic
156    LowerAlpha,
157    LowerLatin,
158    UpperAlpha,
159    UpperLatin,
160    LowerGreek,
161    Hiragana,
162    HiraganaIroha,
163    Katakana,
164    KatakanaIroha,
165
166    // https://www.w3.org/TR/css-counter-styles-3/#simple-symbolic
167    Disc,
168    Circle,
169    Square,
170    DisclosureOpen,
171    DisclosureClosed,
172
173    // https://www.w3.org/TR/css-counter-styles-3/#simple-fixed
174    CjkEarthlyBranch,
175    CjkHeavenlyStem,
176
177    // https://www.w3.org/TR/css-counter-styles-3/#complex-cjk
178    JapaneseInformal,
179    JapaneseFormal,
180    KoreanHangulFormal,
181    KoreanHanjaInformal,
182    KoreanHanjaFormal,
183    SimpChineseInformal,
184    SimpChineseFormal,
185    TradChineseInformal,
186    TradChineseFormal,
187    EthiopicNumeric,
188  }
189}
190
191impl<'i> Parse<'i> for CounterStyle<'i> {
192  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
193    if let Ok(predefined) = input.try_parse(PredefinedCounterStyle::parse) {
194      return Ok(CounterStyle::Predefined(predefined));
195    }
196
197    if input.try_parse(|input| input.expect_function_matching("symbols")).is_ok() {
198      return input.parse_nested_block(|input| {
199        let t = input.try_parse(SymbolsType::parse).unwrap_or_default();
200
201        let mut symbols = Vec::new();
202        while let Ok(s) = input.try_parse(Symbol::parse) {
203          symbols.push(s);
204        }
205
206        Ok(CounterStyle::Symbols { system: t, symbols })
207      });
208    }
209
210    let name = CustomIdent::parse(input)?;
211    Ok(CounterStyle::Name(name))
212  }
213}
214
215impl ToCss for CounterStyle<'_> {
216  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
217  where
218    W: std::fmt::Write,
219  {
220    match self {
221      CounterStyle::Predefined(style) => style.to_css(dest),
222      CounterStyle::Name(name) => {
223        if let Some(css_module) = &mut dest.css_module {
224          css_module.reference(&name.0, dest.loc.source_index)
225        }
226        name.to_css(dest)
227      }
228      CounterStyle::Symbols { system: t, symbols } => {
229        dest.write_str("symbols(")?;
230        let mut needs_space = false;
231        if *t != SymbolsType::Symbolic {
232          t.to_css(dest)?;
233          needs_space = true;
234        }
235
236        for symbol in symbols {
237          if needs_space {
238            dest.write_char(' ')?;
239          }
240          symbol.to_css(dest)?;
241          needs_space = true;
242        }
243        dest.write_char(')')
244      }
245    }
246  }
247}
248
249impl IsCompatible for CounterStyle<'_> {
250  fn is_compatible(&self, browsers: Browsers) -> bool {
251    match self {
252      CounterStyle::Name(..) => true,
253      CounterStyle::Predefined(p) => p.is_compatible(browsers),
254      CounterStyle::Symbols { .. } => crate::compat::Feature::SymbolsListStyleType.is_compatible(browsers),
255    }
256  }
257}
258
259enum_property! {
260  /// A [`<symbols-type>`](https://www.w3.org/TR/css-counter-styles-3/#typedef-symbols-type) value,
261  /// as used in the `symbols()` function.
262  ///
263  /// See [CounterStyle](CounterStyle).
264  #[allow(missing_docs)]
265  pub enum SymbolsType {
266    Cyclic,
267    Numeric,
268    Alphabetic,
269    Symbolic,
270    Fixed,
271  }
272}
273
274impl Default for SymbolsType {
275  fn default() -> Self {
276    SymbolsType::Symbolic
277  }
278}
279
280/// A single [symbol](https://www.w3.org/TR/css-counter-styles-3/#funcdef-symbols) as used in the
281/// `symbols()` function.
282///
283/// See [CounterStyle](CounterStyle).
284#[derive(Debug, Clone, PartialEq, Parse, ToCss)]
285#[cfg_attr(feature = "visitor", derive(Visit))]
286#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
287#[cfg_attr(
288  feature = "serde",
289  derive(serde::Serialize, serde::Deserialize),
290  serde(tag = "type", content = "value", rename_all = "kebab-case")
291)]
292#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
293pub enum Symbol<'i> {
294  /// A string.
295  #[cfg_attr(feature = "serde", serde(borrow))]
296  String(CSSString<'i>),
297  /// An image.
298  Image(Image<'i>),
299}
300
301enum_property! {
302  /// A value for the [list-style-position](https://www.w3.org/TR/2020/WD-css-lists-3-20201117/#list-style-position-property) property.
303  pub enum ListStylePosition {
304    /// The list marker is placed inside the element.
305    Inside,
306    /// The list marker is placed outside the element.
307    Outside,
308  }
309}
310
311impl Default for ListStylePosition {
312  fn default() -> ListStylePosition {
313    ListStylePosition::Outside
314  }
315}
316
317impl IsCompatible for ListStylePosition {
318  fn is_compatible(&self, _browsers: Browsers) -> bool {
319    true
320  }
321}
322
323enum_property! {
324  /// A value for the [marker-side](https://www.w3.org/TR/2020/WD-css-lists-3-20201117/#marker-side) property.
325  #[allow(missing_docs)]
326  pub enum MarkerSide {
327    MatchSelf,
328    MatchParent,
329  }
330}
331
332shorthand_property! {
333  /// A value for the [list-style](https://www.w3.org/TR/2020/WD-css-lists-3-20201117/#list-style-property) shorthand property.
334  pub struct ListStyle<'i> {
335    /// The list style type.
336    #[cfg_attr(feature = "serde", serde(borrow))]
337    list_style_type: ListStyleType(ListStyleType<'i>),
338    /// The list marker image.
339    image: ListStyleImage(Image<'i>),
340    /// The position of the list marker.
341    position: ListStylePosition(ListStylePosition),
342  }
343}
344
345impl<'i> FallbackValues for ListStyle<'i> {
346  fn get_fallbacks(&mut self, targets: Targets) -> Vec<Self> {
347    self
348      .image
349      .get_fallbacks(targets)
350      .into_iter()
351      .map(|image| ListStyle { image, ..self.clone() })
352      .collect()
353  }
354}
355
356shorthand_handler!(ListStyleHandler -> ListStyle<'i> fallbacks: true {
357  image: ListStyleImage(Image<'i>, fallback: true, image: true),
358  list_style_type: ListStyleType(ListStyleType<'i>),
359  position: ListStylePosition(ListStylePosition),
360});