lightningcss/properties/
grid.rs

1//! CSS properties related to grid layout.
2
3#![allow(non_upper_case_globals)]
4
5use crate::context::PropertyHandlerContext;
6use crate::declaration::{DeclarationBlock, DeclarationList};
7use crate::error::{Error, ErrorLocation, ParserError, PrinterError, PrinterErrorKind};
8use crate::macros::{define_shorthand, impl_shorthand};
9use crate::printer::Printer;
10use crate::properties::{Property, PropertyId};
11use crate::traits::{Parse, PropertyHandler, Shorthand, ToCss};
12use crate::values::ident::CustomIdent;
13use crate::values::length::serialize_dimension;
14use crate::values::number::{CSSInteger, CSSNumber};
15use crate::values::{ident::CustomIdentList, length::LengthPercentage};
16#[cfg(feature = "visitor")]
17use crate::visitor::Visit;
18use bitflags::bitflags;
19use cssparser::*;
20use smallvec::SmallVec;
21
22#[cfg(feature = "serde")]
23use crate::serialization::ValueWrapper;
24
25/// A [track sizing](https://drafts.csswg.org/css-grid-2/#track-sizing) value
26/// for the `grid-template-rows` and `grid-template-columns` properties.
27#[derive(Debug, Clone, PartialEq, Parse, ToCss)]
28#[cfg_attr(feature = "visitor", derive(Visit))]
29#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
30#[cfg_attr(
31  feature = "serde",
32  derive(serde::Serialize, serde::Deserialize),
33  serde(tag = "type", rename_all = "kebab-case")
34)]
35#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
36pub enum TrackSizing<'i> {
37  /// No explicit grid tracks.
38  None,
39  /// A list of grid tracks.
40  #[cfg_attr(feature = "serde", serde(borrow))]
41  TrackList(TrackList<'i>),
42}
43
44/// A [`<track-list>`](https://drafts.csswg.org/css-grid-2/#typedef-track-list) value,
45/// as used in the `grid-template-rows` and `grid-template-columns` properties.
46///
47/// See [TrackSizing](TrackSizing).
48#[derive(Debug, Clone, PartialEq)]
49#[cfg_attr(feature = "visitor", derive(Visit))]
50#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
51#[cfg_attr(
52  feature = "serde",
53  derive(serde::Serialize, serde::Deserialize),
54  serde(rename_all = "camelCase")
55)]
56#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
57pub struct TrackList<'i> {
58  /// A list of line names.
59  #[cfg_attr(feature = "serde", serde(borrow))]
60  pub line_names: Vec<CustomIdentList<'i>>,
61  /// A list of grid track items.
62  pub items: Vec<TrackListItem<'i>>,
63}
64
65/// Either a track size or `repeat()` function.
66///
67/// See [TrackList](TrackList).
68#[derive(Debug, Clone, PartialEq)]
69#[cfg_attr(feature = "visitor", derive(Visit))]
70#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
71#[cfg_attr(
72  feature = "serde",
73  derive(serde::Serialize, serde::Deserialize),
74  serde(tag = "type", content = "value", rename_all = "kebab-case")
75)]
76#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
77pub enum TrackListItem<'i> {
78  /// A track size.
79  TrackSize(TrackSize),
80  /// A `repeat()` function.
81  #[cfg_attr(feature = "serde", serde(borrow))]
82  TrackRepeat(TrackRepeat<'i>),
83}
84
85/// A [`<track-size>`](https://drafts.csswg.org/css-grid-2/#typedef-track-size) value,
86/// as used in the `grid-template-rows` and `grid-template-columns` properties.
87///
88/// See [TrackListItem](TrackListItem).
89#[derive(Debug, Clone, PartialEq)]
90#[cfg_attr(feature = "visitor", derive(Visit))]
91#[cfg_attr(
92  feature = "serde",
93  derive(serde::Serialize, serde::Deserialize),
94  serde(tag = "type", rename_all = "kebab-case")
95)]
96#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
97#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
98pub enum TrackSize {
99  /// An explicit track breadth.
100  #[cfg_attr(feature = "serde", serde(with = "ValueWrapper::<TrackBreadth>"))]
101  TrackBreadth(TrackBreadth),
102  /// The `minmax()` function.
103  MinMax {
104    /// The minimum value.
105    min: TrackBreadth,
106    /// The maximum value.
107    max: TrackBreadth,
108  },
109  /// The `fit-content()` function.
110  #[cfg_attr(feature = "serde", serde(with = "ValueWrapper::<LengthPercentage>"))]
111  FitContent(LengthPercentage),
112}
113
114impl Default for TrackSize {
115  fn default() -> TrackSize {
116    TrackSize::TrackBreadth(TrackBreadth::Auto)
117  }
118}
119
120/// A [track size list](https://drafts.csswg.org/css-grid-2/#auto-tracks), as used
121/// in the `grid-auto-rows` and `grid-auto-columns` properties.
122#[derive(Debug, Clone, PartialEq, Default)]
123#[cfg_attr(feature = "visitor", derive(Visit))]
124#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(transparent))]
125#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
126#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
127pub struct TrackSizeList(pub SmallVec<[TrackSize; 1]>);
128
129/// A [`<track-breadth>`](https://drafts.csswg.org/css-grid-2/#typedef-track-breadth) value.
130///
131/// See [TrackSize](TrackSize).
132#[derive(Debug, Clone, PartialEq)]
133#[cfg_attr(feature = "visitor", derive(Visit))]
134#[cfg_attr(
135  feature = "serde",
136  derive(serde::Serialize, serde::Deserialize),
137  serde(tag = "type", content = "value", rename_all = "kebab-case")
138)]
139#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
140#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
141pub enum TrackBreadth {
142  /// An explicit length.
143  Length(LengthPercentage),
144  /// A flex factor.
145  Flex(CSSNumber),
146  /// The `min-content` keyword.
147  MinContent,
148  /// The `max-content` keyword.
149  MaxContent,
150  /// The `auto` keyword.
151  Auto,
152}
153
154/// A [`<track-repeat>`](https://drafts.csswg.org/css-grid-2/#typedef-track-repeat) value,
155/// representing the `repeat()` function in a track list.
156///
157/// See [TrackListItem](TrackListItem).
158#[derive(Debug, Clone, PartialEq)]
159#[cfg_attr(feature = "visitor", derive(Visit))]
160#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
161#[cfg_attr(
162  feature = "serde",
163  derive(serde::Serialize, serde::Deserialize),
164  serde(rename_all = "camelCase")
165)]
166#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
167pub struct TrackRepeat<'i> {
168  /// The repeat count.
169  pub count: RepeatCount,
170  /// The line names to repeat.
171  #[cfg_attr(feature = "serde", serde(borrow))]
172  pub line_names: Vec<CustomIdentList<'i>>,
173  /// The track sizes to repeat.
174  pub track_sizes: Vec<TrackSize>,
175}
176
177/// A [`<repeat-count>`](https://drafts.csswg.org/css-grid-2/#typedef-track-repeat) value,
178/// used in the `repeat()` function.
179///
180/// See [TrackRepeat](TrackRepeat).
181#[derive(Debug, Clone, PartialEq, Parse, ToCss)]
182#[cfg_attr(feature = "visitor", derive(Visit))]
183#[cfg_attr(
184  feature = "serde",
185  derive(serde::Serialize, serde::Deserialize),
186  serde(tag = "type", content = "value", rename_all = "kebab-case")
187)]
188#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
189#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
190pub enum RepeatCount {
191  /// The number of times to repeat.
192  Number(CSSInteger),
193  /// The `auto-fill` keyword.
194  AutoFill,
195  /// The `auto-fit` keyword.
196  AutoFit,
197}
198
199impl<'i> Parse<'i> for TrackSize {
200  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
201    if let Ok(breadth) = input.try_parse(TrackBreadth::parse) {
202      return Ok(TrackSize::TrackBreadth(breadth));
203    }
204
205    if input.try_parse(|input| input.expect_function_matching("minmax")).is_ok() {
206      return input.parse_nested_block(|input| {
207        let min = TrackBreadth::parse_internal(input, false)?;
208        input.expect_comma()?;
209        Ok(TrackSize::MinMax {
210          min,
211          max: TrackBreadth::parse(input)?,
212        })
213      });
214    }
215
216    input.expect_function_matching("fit-content")?;
217    let len = input.parse_nested_block(LengthPercentage::parse)?;
218    Ok(TrackSize::FitContent(len))
219  }
220}
221
222impl ToCss for TrackSize {
223  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
224  where
225    W: std::fmt::Write,
226  {
227    match self {
228      TrackSize::TrackBreadth(breadth) => breadth.to_css(dest),
229      TrackSize::MinMax { min, max } => {
230        dest.write_str("minmax(")?;
231        min.to_css(dest)?;
232        dest.delim(',', false)?;
233        max.to_css(dest)?;
234        dest.write_char(')')
235      }
236      TrackSize::FitContent(len) => {
237        dest.write_str("fit-content(")?;
238        len.to_css(dest)?;
239        dest.write_char(')')
240      }
241    }
242  }
243}
244
245impl<'i> Parse<'i> for TrackBreadth {
246  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
247    Self::parse_internal(input, true)
248  }
249}
250
251impl TrackBreadth {
252  fn parse_internal<'i, 't>(
253    input: &mut Parser<'i, 't>,
254    allow_flex: bool,
255  ) -> Result<Self, ParseError<'i, ParserError<'i>>> {
256    if let Ok(len) = input.try_parse(LengthPercentage::parse) {
257      return Ok(TrackBreadth::Length(len));
258    }
259
260    if allow_flex {
261      if let Ok(flex) = input.try_parse(Self::parse_flex) {
262        return Ok(TrackBreadth::Flex(flex));
263      }
264    }
265
266    let location = input.current_source_location();
267    let ident = input.expect_ident()?;
268    match_ignore_ascii_case! { &*ident,
269      "auto" => Ok(TrackBreadth::Auto),
270      "min-content" => Ok(TrackBreadth::MinContent),
271      "max-content" => Ok(TrackBreadth::MaxContent),
272      _ => Err(location.new_unexpected_token_error(
273        cssparser::Token::Ident(ident.clone())
274      ))
275    }
276  }
277
278  fn parse_flex<'i, 't>(input: &mut Parser<'i, 't>) -> Result<CSSNumber, ParseError<'i, ParserError<'i>>> {
279    let location = input.current_source_location();
280    match *input.next()? {
281      Token::Dimension { value, ref unit, .. } if unit.eq_ignore_ascii_case("fr") && value.is_sign_positive() => {
282        Ok(value)
283      }
284      ref t => Err(location.new_unexpected_token_error(t.clone())),
285    }
286  }
287}
288
289impl ToCss for TrackBreadth {
290  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
291  where
292    W: std::fmt::Write,
293  {
294    match self {
295      TrackBreadth::Auto => dest.write_str("auto"),
296      TrackBreadth::MinContent => dest.write_str("min-content"),
297      TrackBreadth::MaxContent => dest.write_str("max-content"),
298      TrackBreadth::Length(len) => len.to_css(dest),
299      TrackBreadth::Flex(flex) => serialize_dimension(*flex, "fr", dest),
300    }
301  }
302}
303
304impl<'i> Parse<'i> for TrackRepeat<'i> {
305  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
306    input.expect_function_matching("repeat")?;
307    input.parse_nested_block(|input| {
308      let count = RepeatCount::parse(input)?;
309      input.expect_comma()?;
310
311      let mut line_names = Vec::new();
312      let mut track_sizes = Vec::new();
313
314      loop {
315        let line_name = input.try_parse(parse_line_names).unwrap_or_default();
316        line_names.push(line_name);
317
318        if let Ok(track_size) = input.try_parse(TrackSize::parse) {
319          // TODO: error handling
320          track_sizes.push(track_size)
321        } else {
322          break;
323        }
324      }
325
326      Ok(TrackRepeat {
327        count,
328        line_names,
329        track_sizes,
330      })
331    })
332  }
333}
334
335impl<'i> ToCss for TrackRepeat<'i> {
336  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
337  where
338    W: std::fmt::Write,
339  {
340    dest.write_str("repeat(")?;
341    self.count.to_css(dest)?;
342    dest.delim(',', false)?;
343
344    let mut track_sizes_iter = self.track_sizes.iter();
345    let mut first = true;
346    for names in self.line_names.iter() {
347      if !names.is_empty() {
348        serialize_line_names(names, dest)?;
349      }
350
351      if let Some(size) = track_sizes_iter.next() {
352        // Whitespace is required if there are no line names.
353        if !names.is_empty() {
354          dest.whitespace()?;
355        } else if !first {
356          dest.write_char(' ')?;
357        }
358        size.to_css(dest)?;
359      }
360
361      first = false;
362    }
363
364    dest.write_char(')')
365  }
366}
367
368fn parse_line_names<'i, 't>(
369  input: &mut Parser<'i, 't>,
370) -> Result<CustomIdentList<'i>, ParseError<'i, ParserError<'i>>> {
371  input.expect_square_bracket_block()?;
372  input.parse_nested_block(|input| {
373    let mut values = SmallVec::new();
374    while let Ok(ident) = input.try_parse(CustomIdent::parse) {
375      values.push(ident)
376    }
377    Ok(values)
378  })
379}
380
381fn serialize_line_names<W>(names: &[CustomIdent], dest: &mut Printer<W>) -> Result<(), PrinterError>
382where
383  W: std::fmt::Write,
384{
385  dest.write_char('[')?;
386  let mut first = true;
387  for name in names {
388    if first {
389      first = false;
390    } else {
391      dest.write_char(' ')?;
392    }
393    write_ident(&name.0, dest)?;
394  }
395  dest.write_char(']')
396}
397
398fn write_ident<W>(name: &str, dest: &mut Printer<W>) -> Result<(), PrinterError>
399where
400  W: std::fmt::Write,
401{
402  let css_module_grid_enabled = dest.css_module.as_ref().map_or(false, |css_module| css_module.config.grid);
403  if css_module_grid_enabled {
404    if let Some(css_module) = &mut dest.css_module {
405      if let Some(last) = css_module.config.pattern.segments.last() {
406        if !matches!(last, crate::css_modules::Segment::Local) {
407          return Err(Error {
408            kind: PrinterErrorKind::InvalidCssModulesPatternInGrid,
409            loc: Some(ErrorLocation {
410              filename: dest.filename().into(),
411              line: dest.loc.line,
412              column: dest.loc.column,
413            }),
414          });
415        }
416      }
417    }
418  }
419  dest.write_ident(name, css_module_grid_enabled)?;
420  Ok(())
421}
422
423impl<'i> Parse<'i> for TrackList<'i> {
424  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
425    let mut line_names = Vec::new();
426    let mut items = Vec::new();
427
428    loop {
429      let line_name = input.try_parse(parse_line_names).unwrap_or_default();
430      line_names.push(line_name);
431
432      if let Ok(track_size) = input.try_parse(TrackSize::parse) {
433        // TODO: error handling
434        items.push(TrackListItem::TrackSize(track_size));
435      } else if let Ok(repeat) = input.try_parse(TrackRepeat::parse) {
436        // TODO: error handling
437        items.push(TrackListItem::TrackRepeat(repeat))
438      } else {
439        break;
440      }
441    }
442
443    if items.is_empty() {
444      return Err(input.new_custom_error(ParserError::InvalidDeclaration));
445    }
446
447    Ok(TrackList { line_names, items })
448  }
449}
450
451impl<'i> ToCss for TrackList<'i> {
452  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
453  where
454    W: std::fmt::Write,
455  {
456    let mut items_iter = self.items.iter();
457    let line_names_iter = self.line_names.iter();
458    let mut first = true;
459
460    for names in line_names_iter {
461      if !names.is_empty() {
462        serialize_line_names(names, dest)?;
463      }
464
465      if let Some(item) = items_iter.next() {
466        // Whitespace is required if there are no line names.
467        if !names.is_empty() {
468          dest.whitespace()?;
469        } else if !first {
470          dest.write_char(' ')?;
471        }
472        match item {
473          TrackListItem::TrackRepeat(repeat) => repeat.to_css(dest)?,
474          TrackListItem::TrackSize(size) => size.to_css(dest)?,
475        };
476      }
477
478      first = false;
479    }
480
481    Ok(())
482  }
483}
484
485impl<'i> TrackList<'i> {
486  fn is_explicit(&self) -> bool {
487    self.items.iter().all(|item| matches!(item, TrackListItem::TrackSize(_)))
488  }
489}
490
491impl<'i> TrackSizing<'i> {
492  fn is_explicit(&self) -> bool {
493    match self {
494      TrackSizing::None => true,
495      TrackSizing::TrackList(list) => list.is_explicit(),
496    }
497  }
498}
499
500impl<'i> Parse<'i> for TrackSizeList {
501  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
502    let mut res = SmallVec::new();
503    while let Ok(size) = input.try_parse(TrackSize::parse) {
504      res.push(size)
505    }
506    if res.len() == 1 && res[0] == TrackSize::default() {
507      res.clear();
508    }
509    Ok(TrackSizeList(res))
510  }
511}
512
513impl ToCss for TrackSizeList {
514  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
515  where
516    W: std::fmt::Write,
517  {
518    if self.0.len() == 0 {
519      return dest.write_str("auto");
520    }
521
522    let mut first = true;
523    for item in &self.0 {
524      if first {
525        first = false;
526      } else {
527        dest.write_char(' ')?;
528      }
529      item.to_css(dest)?;
530    }
531    Ok(())
532  }
533}
534
535/// A value for the [grid-template-areas](https://drafts.csswg.org/css-grid-2/#grid-template-areas-property) property.
536#[derive(Debug, Clone, PartialEq)]
537#[cfg_attr(feature = "visitor", derive(Visit))]
538#[cfg_attr(
539  feature = "serde",
540  derive(serde::Serialize, serde::Deserialize),
541  serde(tag = "type", rename_all = "kebab-case")
542)]
543#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
544#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
545pub enum GridTemplateAreas {
546  /// No named grid areas.
547  None,
548  /// Defines the list of named grid areas.
549  Areas {
550    /// The number of columns in the grid.
551    columns: u32,
552    /// A flattened list of grid area names.
553    /// Unnamed areas specified by the `.` token are represented as `None`.
554    areas: Vec<Option<String>>,
555  },
556}
557
558impl<'i> Parse<'i> for GridTemplateAreas {
559  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
560    if input.try_parse(|input| input.expect_ident_matching("none")).is_ok() {
561      return Ok(GridTemplateAreas::None);
562    }
563
564    let mut tokens = Vec::new();
565    let mut row = 0;
566    let mut columns = 0;
567    while let Ok(s) = input.try_parse(|input| input.expect_string().map(|s| s.as_ref().to_owned())) {
568      let parsed_columns = Self::parse_string(&s, &mut tokens)
569        .map_err(|()| input.new_error(BasicParseErrorKind::QualifiedRuleInvalid))?;
570
571      if row == 0 {
572        columns = parsed_columns;
573      } else if parsed_columns != columns {
574        return Err(input.new_custom_error(ParserError::InvalidDeclaration));
575      }
576
577      row += 1;
578    }
579
580    Ok(GridTemplateAreas::Areas { columns, areas: tokens })
581  }
582}
583
584impl GridTemplateAreas {
585  fn parse_string(string: &str, tokens: &mut Vec<Option<String>>) -> Result<u32, ()> {
586    let mut string = string;
587    let mut column = 0;
588    loop {
589      let rest = string.trim_start_matches(HTML_SPACE_CHARACTERS);
590      if rest.is_empty() {
591        // Each string must produce a valid token.
592        if column == 0 {
593          return Err(());
594        }
595        break;
596      }
597
598      column += 1;
599
600      if rest.starts_with('.') {
601        string = &rest[rest.find(|c| c != '.').unwrap_or(rest.len())..];
602        tokens.push(None);
603        continue;
604      }
605
606      if !rest.starts_with(is_name_code_point) {
607        return Err(());
608      }
609
610      let token_len = rest.find(|c| !is_name_code_point(c)).unwrap_or(rest.len());
611      let token = &rest[..token_len];
612      tokens.push(Some(token.into()));
613      string = &rest[token_len..];
614    }
615
616    Ok(column)
617  }
618}
619
620static HTML_SPACE_CHARACTERS: &'static [char] = &['\u{0020}', '\u{0009}', '\u{000a}', '\u{000c}', '\u{000d}'];
621
622fn is_name_code_point(c: char) -> bool {
623  c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z' || c >= '\u{80}' || c == '_' || c >= '0' && c <= '9' || c == '-'
624}
625
626impl ToCss for GridTemplateAreas {
627  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
628  where
629    W: std::fmt::Write,
630  {
631    match self {
632      GridTemplateAreas::None => dest.write_str("none"),
633      GridTemplateAreas::Areas { areas, .. } => {
634        let mut iter = areas.iter();
635        let mut next = iter.next();
636        let mut first = true;
637        while next.is_some() {
638          if !first && !dest.minify {
639            dest.newline()?;
640          }
641
642          self.write_string(dest, &mut iter, &mut next)?;
643
644          if first {
645            first = false;
646            if !dest.minify {
647              // Indent by the width of "grid-template-areas: ", so the rows line up.
648              dest.indent_by(21);
649            }
650          }
651        }
652
653        if !dest.minify {
654          dest.dedent_by(21);
655        }
656
657        Ok(())
658      }
659    }
660  }
661}
662
663impl GridTemplateAreas {
664  fn write_string<'a, W>(
665    &self,
666    dest: &mut Printer<W>,
667    iter: &mut std::slice::Iter<'a, Option<String>>,
668    next: &mut Option<&'a Option<String>>,
669  ) -> Result<(), PrinterError>
670  where
671    W: std::fmt::Write,
672  {
673    let columns = match self {
674      GridTemplateAreas::Areas { columns, .. } => *columns,
675      _ => unreachable!(),
676    };
677
678    dest.write_char('"')?;
679
680    let mut last_was_null = false;
681    for i in 0..columns {
682      if let Some(token) = next {
683        if let Some(string) = token {
684          if i > 0 && (!last_was_null || !dest.minify) {
685            dest.write_char(' ')?;
686          }
687          write_ident(string, dest)?;
688          last_was_null = false;
689        } else {
690          if i > 0 && (last_was_null || !dest.minify) {
691            dest.write_char(' ')?;
692          }
693          dest.write_char('.')?;
694          last_was_null = true;
695        }
696      }
697
698      *next = iter.next();
699    }
700
701    dest.write_char('"')
702  }
703}
704
705/// A value for the [grid-template](https://drafts.csswg.org/css-grid-2/#explicit-grid-shorthand) shorthand property.
706///
707/// If `areas` is not `None`, then `rows` must also not be `None`.
708#[derive(Debug, Clone, PartialEq)]
709#[cfg_attr(feature = "visitor", derive(Visit))]
710#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
711#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
712#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
713pub struct GridTemplate<'i> {
714  /// The grid template rows.
715  #[cfg_attr(feature = "serde", serde(borrow))]
716  pub rows: TrackSizing<'i>,
717  /// The grid template columns.
718  pub columns: TrackSizing<'i>,
719  /// The named grid areas.
720  pub areas: GridTemplateAreas,
721}
722
723impl<'i> Parse<'i> for GridTemplate<'i> {
724  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
725    if input.try_parse(|input| input.expect_ident_matching("none")).is_ok() {
726      input.expect_exhausted()?;
727      return Ok(GridTemplate {
728        rows: TrackSizing::None,
729        columns: TrackSizing::None,
730        areas: GridTemplateAreas::None,
731      });
732    }
733
734    let start = input.state();
735    let mut line_names: Vec<CustomIdentList<'i>> = Vec::new();
736    let mut items = Vec::new();
737    let mut columns = 0;
738    let mut row = 0;
739    let mut tokens = Vec::new();
740
741    loop {
742      if let Ok(first_names) = input.try_parse(parse_line_names) {
743        if let Some(last_names) = line_names.last_mut() {
744          last_names.extend(first_names);
745        } else {
746          line_names.push(first_names);
747        }
748      }
749
750      if let Ok(string) = input.try_parse(|input| input.expect_string().map(|s| s.as_ref().to_owned())) {
751        let parsed_columns = GridTemplateAreas::parse_string(&string, &mut tokens)
752          .map_err(|()| input.new_custom_error(ParserError::InvalidDeclaration))?;
753
754        if row == 0 {
755          columns = parsed_columns;
756        } else if parsed_columns != columns {
757          return Err(input.new_custom_error(ParserError::InvalidDeclaration));
758        }
759
760        row += 1;
761
762        let track_size = input.try_parse(TrackSize::parse).unwrap_or_default();
763        items.push(TrackListItem::TrackSize(track_size));
764
765        let last_names = input.try_parse(parse_line_names).unwrap_or_default();
766        line_names.push(last_names);
767      } else {
768        break;
769      }
770    }
771
772    if !tokens.is_empty() {
773      if line_names.len() == items.len() {
774        line_names.push(Default::default());
775      }
776
777      let areas = GridTemplateAreas::Areas { columns, areas: tokens };
778      let rows = TrackSizing::TrackList(TrackList { line_names, items });
779      let columns = if input.try_parse(|input| input.expect_delim('/')).is_ok() {
780        let list = TrackList::parse(input)?;
781        if !list.is_explicit() {
782          return Err(input.new_custom_error(ParserError::InvalidDeclaration));
783        }
784        TrackSizing::TrackList(list)
785      } else {
786        TrackSizing::None
787      };
788      Ok(GridTemplate { rows, columns, areas })
789    } else {
790      input.reset(&start);
791      let rows = TrackSizing::parse(input)?;
792      input.expect_delim('/')?;
793      let columns = TrackSizing::parse(input)?;
794      Ok(GridTemplate {
795        rows,
796        columns,
797        areas: GridTemplateAreas::None,
798      })
799    }
800  }
801}
802
803impl ToCss for GridTemplate<'_> {
804  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
805  where
806    W: std::fmt::Write,
807  {
808    self.to_css_with_indent(dest, 15)
809  }
810}
811
812impl GridTemplate<'_> {
813  fn to_css_with_indent<W>(&self, dest: &mut Printer<W>, indent: u8) -> Result<(), PrinterError>
814  where
815    W: std::fmt::Write,
816  {
817    match &self.areas {
818      GridTemplateAreas::None => {
819        if self.rows == TrackSizing::None && self.columns == TrackSizing::None {
820          dest.write_str("none")?;
821        } else {
822          self.rows.to_css(dest)?;
823          dest.delim('/', true)?;
824          self.columns.to_css(dest)?;
825        }
826      }
827      GridTemplateAreas::Areas { areas, .. } => {
828        let track_list = match &self.rows {
829          TrackSizing::TrackList(list) => list,
830          _ => unreachable!(),
831        };
832
833        let mut areas_iter = areas.iter();
834        let mut line_names_iter = track_list.line_names.iter();
835        let mut items_iter = track_list.items.iter();
836
837        let mut next = areas_iter.next();
838        let mut first = true;
839        let mut indented = false;
840        while next.is_some() {
841          macro_rules! newline {
842            () => {
843              if !dest.minify {
844                if !indented {
845                  // Indent by the width of "grid-template: ", so the rows line up.
846                  dest.indent_by(indent);
847                  indented = true;
848                }
849                dest.newline()?;
850              }
851            };
852          }
853
854          if let Some(line_names) = line_names_iter.next() {
855            if !line_names.is_empty() {
856              if !dest.minify && line_names.len() == 2 {
857                dest.whitespace()?;
858                serialize_line_names(&line_names[0..1], dest)?;
859                newline!();
860                serialize_line_names(&line_names[1..], dest)?;
861              } else {
862                if !first {
863                  newline!();
864                }
865                serialize_line_names(line_names, dest)?;
866              }
867              dest.whitespace()?;
868            } else if !first {
869              newline!();
870            }
871          } else if !first {
872            newline!();
873          }
874
875          self.areas.write_string(dest, &mut areas_iter, &mut next)?;
876
877          if let Some(item) = items_iter.next() {
878            if *item != TrackListItem::TrackSize(TrackSize::default()) {
879              dest.whitespace()?;
880              match item {
881                TrackListItem::TrackSize(size) => size.to_css(dest)?,
882                _ => unreachable!(),
883              }
884            }
885          }
886
887          first = false;
888        }
889
890        if let Some(line_names) = line_names_iter.next() {
891          if !line_names.is_empty() {
892            dest.whitespace()?;
893            serialize_line_names(line_names, dest)?;
894          }
895        }
896
897        if let TrackSizing::TrackList(track_list) = &self.columns {
898          dest.newline()?;
899          dest.delim('/', false)?;
900          track_list.to_css(dest)?;
901        }
902
903        if indented {
904          dest.dedent_by(indent);
905        }
906      }
907    }
908
909    Ok(())
910  }
911}
912
913impl<'i> GridTemplate<'i> {
914  #[inline]
915  fn is_valid(rows: &TrackSizing, columns: &TrackSizing, areas: &GridTemplateAreas) -> bool {
916    // The `grid-template` shorthand supports only explicit track values (i.e. no `repeat()`)
917    // combined with grid-template-areas. If there are no areas, then any track values are allowed.
918    *areas == GridTemplateAreas::None
919      || (*rows != TrackSizing::None && rows.is_explicit() && columns.is_explicit())
920  }
921}
922
923impl_shorthand! {
924  GridTemplate(GridTemplate<'i>) {
925    rows: [GridTemplateRows],
926    columns: [GridTemplateColumns],
927    areas: [GridTemplateAreas],
928  }
929
930  fn is_valid(shorthand) {
931    GridTemplate::is_valid(&shorthand.rows, &shorthand.columns, &shorthand.areas)
932  }
933}
934
935bitflags! {
936  /// A value for the [grid-auto-flow](https://drafts.csswg.org/css-grid-2/#grid-auto-flow-property) property.
937  ///
938  /// The `Row` or `Column` flags may be combined with the `Dense` flag, but the `Row` and `Column` flags may
939  /// not be combined.
940  #[cfg_attr(feature = "visitor", derive(Visit))]
941  #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(from = "SerializedGridAutoFlow", into = "SerializedGridAutoFlow"))]
942  #[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
943  #[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Clone, Copy)]
944  pub struct GridAutoFlow: u8 {
945    /// The auto-placement algorithm places items by filling each row, adding new rows as necessary.
946    const Row    = 0b00;
947    /// The auto-placement algorithm places items by filling each column, adding new columns as necessary.
948    const Column = 0b01;
949    /// If specified, a dense packing algorithm is used, which fills in holes in the grid.
950    const Dense  = 0b10;
951  }
952}
953
954#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
955#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
956struct SerializedGridAutoFlow {
957  /// The direction of the auto flow.
958  direction: AutoFlowDirection,
959  /// If specified, a dense packing algorithm is used, which fills in holes in the grid.
960  dense: bool,
961}
962
963impl From<GridAutoFlow> for SerializedGridAutoFlow {
964  fn from(flow: GridAutoFlow) -> Self {
965    Self {
966      direction: if flow.contains(GridAutoFlow::Column) {
967        AutoFlowDirection::Column
968      } else {
969        AutoFlowDirection::Row
970      },
971      dense: flow.contains(GridAutoFlow::Dense),
972    }
973  }
974}
975
976impl From<SerializedGridAutoFlow> for GridAutoFlow {
977  fn from(s: SerializedGridAutoFlow) -> GridAutoFlow {
978    let mut flow = match s.direction {
979      AutoFlowDirection::Row => GridAutoFlow::Row,
980      AutoFlowDirection::Column => GridAutoFlow::Column,
981    };
982    if s.dense {
983      flow |= GridAutoFlow::Dense
984    }
985
986    flow
987  }
988}
989
990#[cfg_attr(
991  feature = "serde",
992  derive(serde::Serialize, serde::Deserialize),
993  serde(rename_all = "lowercase")
994)]
995#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
996enum AutoFlowDirection {
997  Row,
998  Column,
999}
1000
1001#[cfg(feature = "jsonschema")]
1002#[cfg_attr(docsrs, doc(cfg(feature = "jsonschema")))]
1003impl<'a> schemars::JsonSchema for GridAutoFlow {
1004  fn is_referenceable() -> bool {
1005    true
1006  }
1007
1008  fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
1009    SerializedGridAutoFlow::json_schema(gen)
1010  }
1011
1012  fn schema_name() -> String {
1013    "GridAutoFlow".into()
1014  }
1015}
1016
1017impl Default for GridAutoFlow {
1018  fn default() -> GridAutoFlow {
1019    GridAutoFlow::Row
1020  }
1021}
1022
1023impl GridAutoFlow {
1024  fn direction(self) -> GridAutoFlow {
1025    self & GridAutoFlow::Column
1026  }
1027}
1028
1029impl<'i> Parse<'i> for GridAutoFlow {
1030  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
1031    let mut flow = GridAutoFlow::Row;
1032
1033    macro_rules! match_dense {
1034      () => {
1035        if input.try_parse(|input| input.expect_ident_matching("dense")).is_ok() {
1036          flow |= GridAutoFlow::Dense;
1037        }
1038      };
1039    }
1040
1041    let location = input.current_source_location();
1042    let ident = input.expect_ident()?;
1043    match_ignore_ascii_case! { &ident,
1044      "row" => {
1045        match_dense!();
1046      },
1047      "column" => {
1048        flow = GridAutoFlow::Column;
1049        match_dense!();
1050      },
1051      "dense" => {
1052        let location = input.current_source_location();
1053        input.try_parse(|input| {
1054          let ident = input.expect_ident()?;
1055          match_ignore_ascii_case! { &ident,
1056            "row" => {},
1057            "column" => {
1058              flow = GridAutoFlow::Column;
1059            },
1060            _ => return Err(location.new_unexpected_token_error(
1061              cssparser::Token::Ident(ident.clone())
1062            ))
1063          }
1064          Ok(())
1065        })?;
1066        flow |= GridAutoFlow::Dense;
1067      },
1068      _ => return Err(location.new_unexpected_token_error(
1069        cssparser::Token::Ident(ident.clone())
1070      ))
1071    }
1072
1073    Ok(flow)
1074  }
1075}
1076
1077impl ToCss for GridAutoFlow {
1078  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
1079  where
1080    W: std::fmt::Write,
1081  {
1082    let s = if *self == GridAutoFlow::Row {
1083      "row"
1084    } else if *self == GridAutoFlow::Column {
1085      "column"
1086    } else if *self == GridAutoFlow::Row | GridAutoFlow::Dense {
1087      if dest.minify {
1088        "dense"
1089      } else {
1090        "row dense"
1091      }
1092    } else if *self == GridAutoFlow::Column | GridAutoFlow::Dense {
1093      "column dense"
1094    } else {
1095      unreachable!();
1096    };
1097
1098    dest.write_str(s)
1099  }
1100}
1101
1102/// A value for the [grid](https://drafts.csswg.org/css-grid-2/#grid-shorthand) shorthand property.
1103///
1104/// Explicit and implicit values may not be combined.
1105#[derive(Debug, Clone, PartialEq)]
1106#[cfg_attr(feature = "visitor", derive(Visit))]
1107#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
1108#[cfg_attr(
1109  feature = "serde",
1110  derive(serde::Serialize, serde::Deserialize),
1111  serde(rename_all = "camelCase")
1112)]
1113#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
1114pub struct Grid<'i> {
1115  /// Explicit grid template rows.
1116  #[cfg_attr(feature = "serde", serde(borrow))]
1117  pub rows: TrackSizing<'i>,
1118  /// Explicit grid template columns.
1119  pub columns: TrackSizing<'i>,
1120  /// Explicit grid template areas.
1121  pub areas: GridTemplateAreas,
1122  /// The grid auto rows.
1123  pub auto_rows: TrackSizeList,
1124  /// The grid auto columns.
1125  pub auto_columns: TrackSizeList,
1126  /// The grid auto flow.
1127  pub auto_flow: GridAutoFlow,
1128}
1129
1130impl<'i> Parse<'i> for Grid<'i> {
1131  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
1132    // <'grid-template'>
1133    if let Ok(template) = input.try_parse(GridTemplate::parse) {
1134      Ok(Grid {
1135        rows: template.rows,
1136        columns: template.columns,
1137        areas: template.areas,
1138        auto_rows: TrackSizeList::default(),
1139        auto_columns: TrackSizeList::default(),
1140        auto_flow: GridAutoFlow::default(),
1141      })
1142
1143    // <'grid-template-rows'> / [ auto-flow && dense? ] <'grid-auto-columns'>?
1144    } else if let Ok(rows) = input.try_parse(TrackSizing::parse) {
1145      input.expect_delim('/')?;
1146      let auto_flow = parse_grid_auto_flow(input, GridAutoFlow::Column)?;
1147      let auto_columns = TrackSizeList::parse(input).unwrap_or_default();
1148      Ok(Grid {
1149        rows,
1150        columns: TrackSizing::None,
1151        areas: GridTemplateAreas::None,
1152        auto_rows: TrackSizeList::default(),
1153        auto_columns,
1154        auto_flow,
1155      })
1156
1157    // [ auto-flow && dense? ] <'grid-auto-rows'>? / <'grid-template-columns'>
1158    } else {
1159      let auto_flow = parse_grid_auto_flow(input, GridAutoFlow::Row)?;
1160      let auto_rows = input.try_parse(TrackSizeList::parse).unwrap_or_default();
1161      input.expect_delim('/')?;
1162      let columns = TrackSizing::parse(input)?;
1163      Ok(Grid {
1164        rows: TrackSizing::None,
1165        columns,
1166        areas: GridTemplateAreas::None,
1167        auto_rows,
1168        auto_columns: TrackSizeList::default(),
1169        auto_flow,
1170      })
1171    }
1172  }
1173}
1174
1175fn parse_grid_auto_flow<'i, 't>(
1176  input: &mut Parser<'i, 't>,
1177  flow: GridAutoFlow,
1178) -> Result<GridAutoFlow, ParseError<'i, ParserError<'i>>> {
1179  if input.try_parse(|input| input.expect_ident_matching("auto-flow")).is_ok() {
1180    if input.try_parse(|input| input.expect_ident_matching("dense")).is_ok() {
1181      Ok(flow | GridAutoFlow::Dense)
1182    } else {
1183      Ok(flow)
1184    }
1185  } else if input.try_parse(|input| input.expect_ident_matching("dense")).is_ok() {
1186    input.expect_ident_matching("auto-flow")?;
1187    Ok(flow | GridAutoFlow::Dense)
1188  } else {
1189    Err(input.new_error_for_next_token())
1190  }
1191}
1192
1193impl ToCss for Grid<'_> {
1194  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
1195  where
1196    W: std::fmt::Write,
1197  {
1198    let is_auto_initial = self.auto_rows == TrackSizeList::default()
1199      && self.auto_columns == TrackSizeList::default()
1200      && self.auto_flow == GridAutoFlow::default();
1201
1202    if self.areas != GridTemplateAreas::None
1203      || (self.rows != TrackSizing::None && self.columns != TrackSizing::None)
1204      || (self.areas == GridTemplateAreas::None && is_auto_initial)
1205    {
1206      if !is_auto_initial {
1207        unreachable!("invalid grid shorthand: mixed implicit and explicit values");
1208      }
1209      let template = GridTemplate {
1210        rows: self.rows.clone(),
1211        columns: self.columns.clone(),
1212        areas: self.areas.clone(),
1213      };
1214      template.to_css_with_indent(dest, 6)?;
1215    } else if self.auto_flow.direction() == GridAutoFlow::Column {
1216      if self.columns != TrackSizing::None || self.auto_rows != TrackSizeList::default() {
1217        unreachable!("invalid grid shorthand: mixed implicit and explicit values");
1218      }
1219      self.rows.to_css(dest)?;
1220      dest.delim('/', true)?;
1221      dest.write_str("auto-flow")?;
1222      if self.auto_flow.contains(GridAutoFlow::Dense) {
1223        dest.write_str(" dense")?;
1224      }
1225      if self.auto_columns != TrackSizeList::default() {
1226        dest.write_char(' ')?;
1227        self.auto_columns.to_css(dest)?;
1228      }
1229    } else {
1230      if self.rows != TrackSizing::None || self.auto_columns != TrackSizeList::default() {
1231        unreachable!("invalid grid shorthand: mixed implicit and explicit values");
1232      }
1233      dest.write_str("auto-flow")?;
1234      if self.auto_flow.contains(GridAutoFlow::Dense) {
1235        dest.write_str(" dense")?;
1236      }
1237      if self.auto_rows != TrackSizeList::default() {
1238        dest.write_char(' ')?;
1239        self.auto_rows.to_css(dest)?;
1240      }
1241      dest.delim('/', true)?;
1242      self.columns.to_css(dest)?;
1243    }
1244
1245    Ok(())
1246  }
1247}
1248
1249impl<'i> Grid<'i> {
1250  #[inline]
1251  fn is_valid(
1252    rows: &TrackSizing,
1253    columns: &TrackSizing,
1254    areas: &GridTemplateAreas,
1255    auto_rows: &TrackSizeList,
1256    auto_columns: &TrackSizeList,
1257    auto_flow: &GridAutoFlow,
1258  ) -> bool {
1259    // The `grid` shorthand can either be fully explicit (e.g. same as `grid-template`),
1260    // or explicit along a single axis. If there are auto rows, then there cannot be explicit rows, for example.
1261    let is_template = GridTemplate::is_valid(rows, columns, areas);
1262    let default_track_size_list = TrackSizeList::default();
1263    let is_explicit = *auto_rows == default_track_size_list
1264      && *auto_columns == default_track_size_list
1265      && *auto_flow == GridAutoFlow::default();
1266    let is_auto_rows = auto_flow.direction() == GridAutoFlow::Row
1267      && *rows == TrackSizing::None
1268      && *auto_columns == default_track_size_list;
1269    let is_auto_columns = auto_flow.direction() == GridAutoFlow::Column
1270      && *columns == TrackSizing::None
1271      && *auto_rows == default_track_size_list;
1272
1273    (is_template && is_explicit) || is_auto_rows || is_auto_columns
1274  }
1275}
1276
1277impl_shorthand! {
1278  Grid(Grid<'i>) {
1279    rows: [GridTemplateRows],
1280    columns: [GridTemplateColumns],
1281    areas: [GridTemplateAreas],
1282    auto_rows: [GridAutoRows],
1283    auto_columns: [GridAutoColumns],
1284    auto_flow: [GridAutoFlow],
1285  }
1286
1287  fn is_valid(grid) {
1288    Grid::is_valid(&grid.rows, &grid.columns, &grid.areas, &grid.auto_rows, &grid.auto_columns, &grid.auto_flow)
1289  }
1290}
1291
1292/// A [`<grid-line>`](https://drafts.csswg.org/css-grid-2/#typedef-grid-row-start-grid-line) value,
1293/// used in the `grid-row-start`, `grid-row-end`, `grid-column-start`, and `grid-column-end` properties.
1294#[derive(Debug, Clone, PartialEq)]
1295#[cfg_attr(feature = "visitor", derive(Visit))]
1296#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
1297#[cfg_attr(
1298  feature = "serde",
1299  derive(serde::Serialize, serde::Deserialize),
1300  serde(tag = "type", rename_all = "kebab-case")
1301)]
1302#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
1303pub enum GridLine<'i> {
1304  /// Automatic placement.
1305  Auto,
1306  /// A named grid area name (automatically postfixed by `-start` or `-end`), or and explicit grid line name.
1307  Area {
1308    /// A grid area name.
1309    name: CustomIdent<'i>,
1310  },
1311  /// The Nth grid line, optionally filtered by line name. Negative numbers count backwards from the end.
1312  Line {
1313    /// A line number.
1314    index: CSSInteger,
1315    /// A line name to filter by.
1316    #[cfg_attr(feature = "serde", serde(borrow))]
1317    name: Option<CustomIdent<'i>>,
1318  },
1319  /// A grid span based on the Nth grid line from the opposite edge, optionally filtered by line name.
1320  Span {
1321    /// A line number.
1322    index: CSSInteger,
1323    /// A line name to filter by.
1324    name: Option<CustomIdent<'i>>,
1325  },
1326}
1327
1328impl<'i> Parse<'i> for GridLine<'i> {
1329  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
1330    if input.try_parse(|input| input.expect_ident_matching("auto")).is_ok() {
1331      return Ok(GridLine::Auto);
1332    }
1333
1334    if input.try_parse(|input| input.expect_ident_matching("span")).is_ok() {
1335      // TODO: is calc() supported here??
1336      let (index, name) = if let Ok(line_number) = input.try_parse(CSSInteger::parse) {
1337        let ident = input.try_parse(CustomIdent::parse).ok();
1338        (line_number, ident)
1339      } else if let Ok(ident) = input.try_parse(CustomIdent::parse) {
1340        let line_number = input.try_parse(CSSInteger::parse).unwrap_or(1);
1341        (line_number, Some(ident))
1342      } else {
1343        return Err(input.new_custom_error(ParserError::InvalidDeclaration));
1344      };
1345
1346      if index == 0 {
1347        return Err(input.new_custom_error(ParserError::InvalidDeclaration));
1348      }
1349
1350      return Ok(GridLine::Span { index, name });
1351    }
1352
1353    if let Ok(index) = input.try_parse(CSSInteger::parse) {
1354      if index == 0 {
1355        return Err(input.new_custom_error(ParserError::InvalidDeclaration));
1356      }
1357      let name = input.try_parse(CustomIdent::parse).ok();
1358      return Ok(GridLine::Line { index, name });
1359    }
1360
1361    let name = CustomIdent::parse(input)?;
1362    if let Ok(index) = input.try_parse(CSSInteger::parse) {
1363      if index == 0 {
1364        return Err(input.new_custom_error(ParserError::InvalidDeclaration));
1365      }
1366      return Ok(GridLine::Line {
1367        index,
1368        name: Some(name),
1369      });
1370    }
1371
1372    Ok(GridLine::Area { name })
1373  }
1374}
1375
1376impl ToCss for GridLine<'_> {
1377  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
1378  where
1379    W: std::fmt::Write,
1380  {
1381    match self {
1382      GridLine::Auto => dest.write_str("auto"),
1383      GridLine::Area { name } => write_ident(&name.0, dest),
1384      GridLine::Line { index, name } => {
1385        index.to_css(dest)?;
1386        if let Some(id) = name {
1387          dest.write_char(' ')?;
1388          write_ident(&id.0, dest)?;
1389        }
1390        Ok(())
1391      }
1392      GridLine::Span { index, name } => {
1393        dest.write_str("span ")?;
1394        if *index != 1 || name.is_none() {
1395          index.to_css(dest)?;
1396          if name.is_some() {
1397            dest.write_char(' ')?;
1398          }
1399        }
1400
1401        if let Some(id) = name {
1402          write_ident(&id.0, dest)?;
1403        }
1404        Ok(())
1405      }
1406    }
1407  }
1408}
1409
1410impl<'i> GridLine<'i> {
1411  fn default_end_value(&self) -> GridLine<'i> {
1412    if matches!(self, GridLine::Area { .. }) {
1413      self.clone()
1414    } else {
1415      GridLine::Auto
1416    }
1417  }
1418
1419  fn can_omit_end(&self, end: &GridLine) -> bool {
1420    if let GridLine::Area { name: start_id } = &self {
1421      matches!(end, GridLine::Area { name: end_id } if end_id == start_id)
1422    } else if matches!(end, GridLine::Auto) {
1423      true
1424    } else {
1425      false
1426    }
1427  }
1428}
1429
1430macro_rules! impl_grid_placement {
1431  ($name: ident) => {
1432    impl<'i> Parse<'i> for $name<'i> {
1433      fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
1434        let start = GridLine::parse(input)?;
1435        let end = if input.try_parse(|input| input.expect_delim('/')).is_ok() {
1436          GridLine::parse(input)?
1437        } else {
1438          start.default_end_value()
1439        };
1440
1441        Ok($name { start, end })
1442      }
1443    }
1444
1445    impl ToCss for $name<'_> {
1446      fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
1447      where
1448        W: std::fmt::Write,
1449      {
1450        self.start.to_css(dest)?;
1451
1452        if !self.start.can_omit_end(&self.end) {
1453          dest.delim('/', true)?;
1454          self.end.to_css(dest)?;
1455        }
1456        Ok(())
1457      }
1458    }
1459  };
1460}
1461
1462define_shorthand! {
1463  /// A value for the [grid-row](https://drafts.csswg.org/css-grid-2/#propdef-grid-row) shorthand property.
1464  pub struct GridRow<'i> {
1465    /// The starting line.
1466    #[cfg_attr(feature = "serde", serde(borrow))]
1467    start: GridRowStart(GridLine<'i>),
1468    /// The ending line.
1469    end: GridRowEnd(GridLine<'i>),
1470  }
1471}
1472
1473define_shorthand! {
1474  /// A value for the [grid-row](https://drafts.csswg.org/css-grid-2/#propdef-grid-column) shorthand property.
1475  pub struct GridColumn<'i> {
1476    /// The starting line.
1477    #[cfg_attr(feature = "serde", serde(borrow))]
1478    start: GridColumnStart(GridLine<'i>),
1479    /// The ending line.
1480    end: GridColumnEnd(GridLine<'i>),
1481  }
1482}
1483
1484impl_grid_placement!(GridRow);
1485impl_grid_placement!(GridColumn);
1486
1487define_shorthand! {
1488  /// A value for the [grid-area](https://drafts.csswg.org/css-grid-2/#propdef-grid-area) shorthand property.
1489  pub struct GridArea<'i> {
1490    /// The grid row start placement.
1491    #[cfg_attr(feature = "serde", serde(borrow))]
1492    row_start: GridRowStart(GridLine<'i>),
1493    /// The grid column start placement.
1494    column_start: GridColumnStart(GridLine<'i>),
1495    /// The grid row end placement.
1496    row_end: GridRowEnd(GridLine<'i>),
1497    /// The grid column end placement.
1498    column_end: GridColumnEnd(GridLine<'i>),
1499  }
1500}
1501
1502impl<'i> Parse<'i> for GridArea<'i> {
1503  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
1504    let row_start = GridLine::parse(input)?;
1505    let column_start = if input.try_parse(|input| input.expect_delim('/')).is_ok() {
1506      GridLine::parse(input)?
1507    } else {
1508      let opposite = row_start.default_end_value();
1509      return Ok(GridArea {
1510        row_start,
1511        column_start: opposite.clone(),
1512        row_end: opposite.clone(),
1513        column_end: opposite,
1514      });
1515    };
1516
1517    let row_end = if input.try_parse(|input| input.expect_delim('/')).is_ok() {
1518      GridLine::parse(input)?
1519    } else {
1520      let row_end = row_start.default_end_value();
1521      let column_end = column_start.default_end_value();
1522      return Ok(GridArea {
1523        row_start,
1524        column_start,
1525        row_end,
1526        column_end,
1527      });
1528    };
1529
1530    let column_end = if input.try_parse(|input| input.expect_delim('/')).is_ok() {
1531      GridLine::parse(input)?
1532    } else {
1533      let column_end = column_start.default_end_value();
1534      return Ok(GridArea {
1535        row_start,
1536        column_start,
1537        row_end,
1538        column_end,
1539      });
1540    };
1541
1542    Ok(GridArea {
1543      row_start,
1544      column_start,
1545      row_end,
1546      column_end,
1547    })
1548  }
1549}
1550
1551impl ToCss for GridArea<'_> {
1552  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
1553  where
1554    W: std::fmt::Write,
1555  {
1556    self.row_start.to_css(dest)?;
1557
1558    let can_omit_column_end = self.column_start.can_omit_end(&self.column_end);
1559    let can_omit_row_end = can_omit_column_end && self.row_start.can_omit_end(&self.row_end);
1560    let can_omit_column_start = can_omit_row_end && self.row_start.can_omit_end(&self.column_start);
1561
1562    if !can_omit_column_start {
1563      dest.delim('/', true)?;
1564      self.column_start.to_css(dest)?;
1565    }
1566
1567    if !can_omit_row_end {
1568      dest.delim('/', true)?;
1569      self.row_end.to_css(dest)?;
1570    }
1571
1572    if !can_omit_column_end {
1573      dest.delim('/', true)?;
1574      self.column_end.to_css(dest)?;
1575    }
1576
1577    Ok(())
1578  }
1579}
1580
1581#[derive(Default, Debug)]
1582pub(crate) struct GridHandler<'i> {
1583  rows: Option<TrackSizing<'i>>,
1584  columns: Option<TrackSizing<'i>>,
1585  areas: Option<GridTemplateAreas>,
1586  auto_rows: Option<TrackSizeList>,
1587  auto_columns: Option<TrackSizeList>,
1588  auto_flow: Option<GridAutoFlow>,
1589  row_start: Option<GridLine<'i>>,
1590  column_start: Option<GridLine<'i>>,
1591  row_end: Option<GridLine<'i>>,
1592  column_end: Option<GridLine<'i>>,
1593  has_any: bool,
1594}
1595
1596impl<'i> PropertyHandler<'i> for GridHandler<'i> {
1597  fn handle_property(
1598    &mut self,
1599    property: &Property<'i>,
1600    dest: &mut DeclarationList<'i>,
1601    context: &mut PropertyHandlerContext<'i, '_>,
1602  ) -> bool {
1603    use Property::*;
1604
1605    match property {
1606      GridTemplateColumns(columns) => self.columns = Some(columns.clone()),
1607      GridTemplateRows(rows) => self.rows = Some(rows.clone()),
1608      GridTemplateAreas(areas) => self.areas = Some(areas.clone()),
1609      GridAutoColumns(auto_columns) => self.auto_columns = Some(auto_columns.clone()),
1610      GridAutoRows(auto_rows) => self.auto_rows = Some(auto_rows.clone()),
1611      GridAutoFlow(auto_flow) => self.auto_flow = Some(auto_flow.clone()),
1612      GridTemplate(template) => {
1613        self.rows = Some(template.rows.clone());
1614        self.columns = Some(template.columns.clone());
1615        self.areas = Some(template.areas.clone());
1616      }
1617      Grid(grid) => {
1618        self.rows = Some(grid.rows.clone());
1619        self.columns = Some(grid.columns.clone());
1620        self.areas = Some(grid.areas.clone());
1621        self.auto_rows = Some(grid.auto_rows.clone());
1622        self.auto_columns = Some(grid.auto_columns.clone());
1623        self.auto_flow = Some(grid.auto_flow.clone());
1624      }
1625      GridRowStart(row_start) => self.row_start = Some(row_start.clone()),
1626      GridRowEnd(row_end) => self.row_end = Some(row_end.clone()),
1627      GridColumnStart(column_start) => self.column_start = Some(column_start.clone()),
1628      GridColumnEnd(column_end) => self.column_end = Some(column_end.clone()),
1629      GridRow(row) => {
1630        self.row_start = Some(row.start.clone());
1631        self.row_end = Some(row.end.clone());
1632      }
1633      GridColumn(column) => {
1634        self.column_start = Some(column.start.clone());
1635        self.column_end = Some(column.end.clone());
1636      }
1637      GridArea(area) => {
1638        self.row_start = Some(area.row_start.clone());
1639        self.row_end = Some(area.row_end.clone());
1640        self.column_start = Some(area.column_start.clone());
1641        self.column_end = Some(area.column_end.clone());
1642      }
1643      Unparsed(val) if is_grid_property(&val.property_id) => {
1644        self.finalize(dest, context);
1645        dest.push(property.clone());
1646      }
1647      _ => return false,
1648    }
1649
1650    self.has_any = true;
1651    true
1652  }
1653
1654  fn finalize(&mut self, dest: &mut DeclarationList<'i>, _: &mut PropertyHandlerContext<'i, '_>) {
1655    if !self.has_any {
1656      return;
1657    }
1658
1659    self.has_any = false;
1660
1661    let mut rows = std::mem::take(&mut self.rows);
1662    let mut columns = std::mem::take(&mut self.columns);
1663    let mut areas = std::mem::take(&mut self.areas);
1664    let mut auto_rows = std::mem::take(&mut self.auto_rows);
1665    let mut auto_columns = std::mem::take(&mut self.auto_columns);
1666    let mut auto_flow = std::mem::take(&mut self.auto_flow);
1667    let mut row_start = std::mem::take(&mut self.row_start);
1668    let mut row_end = std::mem::take(&mut self.row_end);
1669    let mut column_start = std::mem::take(&mut self.column_start);
1670    let mut column_end = std::mem::take(&mut self.column_end);
1671
1672    if let (Some(rows_val), Some(columns_val), Some(areas_val)) = (&rows, &columns, &areas) {
1673      let mut has_template = true;
1674      if let (Some(auto_rows_val), Some(auto_columns_val), Some(auto_flow_val)) =
1675        (&auto_rows, &auto_columns, &auto_flow)
1676      {
1677        // The `grid` shorthand can either be fully explicit (e.g. same as `grid-template`),
1678        // or explicit along a single axis. If there are auto rows, then there cannot be explicit rows, for example.
1679        if Grid::is_valid(
1680          rows_val,
1681          columns_val,
1682          areas_val,
1683          auto_rows_val,
1684          auto_columns_val,
1685          auto_flow_val,
1686        ) {
1687          dest.push(Property::Grid(Grid {
1688            rows: rows_val.clone(),
1689            columns: columns_val.clone(),
1690            areas: areas_val.clone(),
1691            auto_rows: auto_rows_val.clone(),
1692            auto_columns: auto_columns_val.clone(),
1693            auto_flow: auto_flow_val.clone(),
1694          }));
1695
1696          has_template = false;
1697          auto_rows = None;
1698          auto_columns = None;
1699          auto_flow = None;
1700        }
1701      }
1702
1703      // The `grid-template` shorthand supports only explicit track values (i.e. no `repeat()`)
1704      // combined with grid-template-areas. If there are no areas, then any track values are allowed.
1705      if has_template && GridTemplate::is_valid(rows_val, columns_val, areas_val) {
1706        dest.push(Property::GridTemplate(GridTemplate {
1707          rows: rows_val.clone(),
1708          columns: columns_val.clone(),
1709          areas: areas_val.clone(),
1710        }));
1711
1712        has_template = false;
1713      }
1714
1715      if !has_template {
1716        rows = None;
1717        columns = None;
1718        areas = None;
1719      }
1720    }
1721
1722    if row_start.is_some() && row_end.is_some() && column_start.is_some() && column_end.is_some() {
1723      dest.push(Property::GridArea(GridArea {
1724        row_start: std::mem::take(&mut row_start).unwrap(),
1725        row_end: std::mem::take(&mut row_end).unwrap(),
1726        column_start: std::mem::take(&mut column_start).unwrap(),
1727        column_end: std::mem::take(&mut column_end).unwrap(),
1728      }))
1729    } else {
1730      if row_start.is_some() && row_end.is_some() {
1731        dest.push(Property::GridRow(GridRow {
1732          start: std::mem::take(&mut row_start).unwrap(),
1733          end: std::mem::take(&mut row_end).unwrap(),
1734        }))
1735      }
1736
1737      if column_start.is_some() && column_end.is_some() {
1738        dest.push(Property::GridColumn(GridColumn {
1739          start: std::mem::take(&mut column_start).unwrap(),
1740          end: std::mem::take(&mut column_end).unwrap(),
1741        }))
1742      }
1743    }
1744
1745    macro_rules! single_property {
1746      ($prop: ident, $key: ident) => {
1747        if let Some(val) = $key {
1748          dest.push(Property::$prop(val))
1749        }
1750      };
1751    }
1752
1753    single_property!(GridTemplateRows, rows);
1754    single_property!(GridTemplateColumns, columns);
1755    single_property!(GridTemplateAreas, areas);
1756    single_property!(GridAutoRows, auto_rows);
1757    single_property!(GridAutoColumns, auto_columns);
1758    single_property!(GridAutoFlow, auto_flow);
1759    single_property!(GridRowStart, row_start);
1760    single_property!(GridRowEnd, row_end);
1761    single_property!(GridColumnStart, column_start);
1762    single_property!(GridColumnEnd, column_end);
1763  }
1764}
1765
1766#[inline]
1767fn is_grid_property(property_id: &PropertyId) -> bool {
1768  match property_id {
1769    PropertyId::GridTemplateColumns
1770    | PropertyId::GridTemplateRows
1771    | PropertyId::GridTemplateAreas
1772    | PropertyId::GridAutoColumns
1773    | PropertyId::GridAutoRows
1774    | PropertyId::GridAutoFlow
1775    | PropertyId::GridTemplate
1776    | PropertyId::Grid
1777    | PropertyId::GridRowStart
1778    | PropertyId::GridRowEnd
1779    | PropertyId::GridColumnStart
1780    | PropertyId::GridColumnEnd
1781    | PropertyId::GridRow
1782    | PropertyId::GridColumn
1783    | PropertyId::GridArea => true,
1784    _ => false,
1785  }
1786}