Skip to main content

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/// none | <string>+
537#[derive(Debug, Clone, PartialEq)]
538#[cfg_attr(feature = "visitor", derive(Visit))]
539#[cfg_attr(
540  feature = "serde",
541  derive(serde::Serialize, serde::Deserialize),
542  serde(tag = "type", rename_all = "kebab-case")
543)]
544#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
545#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
546pub enum GridTemplateAreas {
547  /// No named grid areas.
548  None,
549  /// Defines the list of named grid areas.
550  Areas {
551    /// The number of columns in the grid.
552    columns: u32,
553    /// A flattened list of grid area names.
554    /// Unnamed areas specified by the `.` token are represented as `None`.
555    areas: Vec<Option<String>>,
556  },
557}
558
559impl<'i> Parse<'i> for GridTemplateAreas {
560  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
561    if input.try_parse(|input| input.expect_ident_matching("none")).is_ok() {
562      return Ok(GridTemplateAreas::None);
563    }
564
565    let mut tokens = Vec::new();
566    let mut row = 0;
567    let mut columns = 0;
568    while let Ok(s) = input.try_parse(|input| input.expect_string().map(|s| s.as_ref().to_owned())) {
569      let parsed_columns = Self::parse_string(&s, &mut tokens)
570        .map_err(|()| input.new_error(BasicParseErrorKind::QualifiedRuleInvalid))?;
571
572      if row == 0 {
573        columns = parsed_columns;
574      } else if parsed_columns != columns {
575        return Err(input.new_custom_error(ParserError::InvalidDeclaration));
576      }
577
578      row += 1;
579    }
580
581    Ok(GridTemplateAreas::Areas { columns, areas: tokens })
582  }
583}
584
585impl GridTemplateAreas {
586  fn parse_string(string: &str, tokens: &mut Vec<Option<String>>) -> Result<u32, ()> {
587    let mut string = string;
588    let mut column = 0;
589    loop {
590      let rest = string.trim_start_matches(HTML_SPACE_CHARACTERS);
591      if rest.is_empty() {
592        // Each string must produce a valid token.
593        if column == 0 {
594          return Err(());
595        }
596        break;
597      }
598
599      column += 1;
600
601      if rest.starts_with('.') {
602        string = &rest[rest.find(|c| c != '.').unwrap_or(rest.len())..];
603        tokens.push(None);
604        continue;
605      }
606
607      if !rest.starts_with(is_name_code_point) {
608        return Err(());
609      }
610
611      let token_len = rest.find(|c| !is_name_code_point(c)).unwrap_or(rest.len());
612      let token = &rest[..token_len];
613      tokens.push(Some(token.into()));
614      string = &rest[token_len..];
615    }
616
617    Ok(column)
618  }
619}
620
621static HTML_SPACE_CHARACTERS: &'static [char] = &['\u{0020}', '\u{0009}', '\u{000a}', '\u{000c}', '\u{000d}'];
622
623fn is_name_code_point(c: char) -> bool {
624  c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z' || c >= '\u{80}' || c == '_' || c >= '0' && c <= '9' || c == '-'
625}
626
627impl ToCss for GridTemplateAreas {
628  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
629  where
630    W: std::fmt::Write,
631  {
632    match self {
633      GridTemplateAreas::None => dest.write_str("none"),
634      GridTemplateAreas::Areas { areas, .. } => {
635        let mut iter = areas.iter();
636        let mut next = iter.next();
637        let mut first = true;
638        while next.is_some() {
639          if !first && !dest.minify {
640            dest.newline()?;
641          }
642
643          self.write_string(dest, &mut iter, &mut next)?;
644
645          if first {
646            first = false;
647            if !dest.minify {
648              // Indent by the width of "grid-template-areas: ", so the rows line up.
649              dest.indent_by(21);
650            }
651          }
652        }
653
654        if !dest.minify {
655          dest.dedent_by(21);
656        }
657
658        Ok(())
659      }
660    }
661  }
662}
663
664impl GridTemplateAreas {
665  fn write_string<'a, W>(
666    &self,
667    dest: &mut Printer<W>,
668    iter: &mut std::slice::Iter<'a, Option<String>>,
669    next: &mut Option<&'a Option<String>>,
670  ) -> Result<(), PrinterError>
671  where
672    W: std::fmt::Write,
673  {
674    let columns = match self {
675      GridTemplateAreas::Areas { columns, .. } => *columns,
676      _ => unreachable!(),
677    };
678
679    dest.write_char('"')?;
680
681    let mut last_was_null = false;
682    for i in 0..columns {
683      if let Some(token) = next {
684        if let Some(string) = token {
685          if i > 0 && (!last_was_null || !dest.minify) {
686            dest.write_char(' ')?;
687          }
688          write_ident(string, dest)?;
689          last_was_null = false;
690        } else {
691          if i > 0 && (last_was_null || !dest.minify) {
692            dest.write_char(' ')?;
693          }
694          dest.write_char('.')?;
695          last_was_null = true;
696        }
697      }
698
699      *next = iter.next();
700    }
701
702    dest.write_char('"')
703  }
704}
705
706/// A value for the [grid-template](https://drafts.csswg.org/css-grid-2/#explicit-grid-shorthand) shorthand property.
707///
708/// none | [ <'grid-template-rows'> / <'grid-template-columns'> ] | [ <line-names>? <string> <track-size>? <line-names>? ]+ [ / <explicit-track-list> ]?
709///
710/// If `areas` is not `None`, then `rows` must also not be `None`.
711#[derive(Debug, Clone, PartialEq)]
712#[cfg_attr(feature = "visitor", derive(Visit))]
713#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
714#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
715#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
716pub struct GridTemplate<'i> {
717  /// The grid template rows.
718  #[cfg_attr(feature = "serde", serde(borrow))]
719  pub rows: TrackSizing<'i>,
720  /// The grid template columns.
721  pub columns: TrackSizing<'i>,
722  /// The named grid areas.
723  pub areas: GridTemplateAreas,
724}
725
726impl<'i> Parse<'i> for GridTemplate<'i> {
727  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
728    if input.try_parse(|input| input.expect_ident_matching("none")).is_ok() {
729      input.expect_exhausted()?;
730      return Ok(GridTemplate {
731        rows: TrackSizing::None,
732        columns: TrackSizing::None,
733        areas: GridTemplateAreas::None,
734      });
735    }
736
737    let start = input.state();
738    let mut line_names: Vec<CustomIdentList<'i>> = Vec::new();
739    let mut items = Vec::new();
740    let mut columns = 0;
741    let mut row = 0;
742    let mut tokens = Vec::new();
743
744    loop {
745      if let Ok(first_names) = input.try_parse(parse_line_names) {
746        if let Some(last_names) = line_names.last_mut() {
747          last_names.extend(first_names);
748        } else {
749          line_names.push(first_names);
750        }
751      }
752
753      if let Ok(string) = input.try_parse(|input| input.expect_string().map(|s| s.as_ref().to_owned())) {
754        let parsed_columns = GridTemplateAreas::parse_string(&string, &mut tokens)
755          .map_err(|()| input.new_custom_error(ParserError::InvalidDeclaration))?;
756
757        if row == 0 {
758          columns = parsed_columns;
759        } else if parsed_columns != columns {
760          return Err(input.new_custom_error(ParserError::InvalidDeclaration));
761        }
762
763        row += 1;
764
765        let track_size = input.try_parse(TrackSize::parse).unwrap_or_default();
766        items.push(TrackListItem::TrackSize(track_size));
767
768        let last_names = input.try_parse(parse_line_names).unwrap_or_default();
769        line_names.push(last_names);
770      } else {
771        break;
772      }
773    }
774
775    if !tokens.is_empty() {
776      if line_names.len() == items.len() {
777        line_names.push(Default::default());
778      }
779
780      let areas = GridTemplateAreas::Areas { columns, areas: tokens };
781      let rows = TrackSizing::TrackList(TrackList { line_names, items });
782      let columns = if input.try_parse(|input| input.expect_delim('/')).is_ok() {
783        let list = TrackList::parse(input)?;
784        if !list.is_explicit() {
785          return Err(input.new_custom_error(ParserError::InvalidDeclaration));
786        }
787        TrackSizing::TrackList(list)
788      } else {
789        TrackSizing::None
790      };
791      Ok(GridTemplate { rows, columns, areas })
792    } else {
793      input.reset(&start);
794      let rows = TrackSizing::parse(input)?;
795      input.expect_delim('/')?;
796      let columns = TrackSizing::parse(input)?;
797      Ok(GridTemplate {
798        rows,
799        columns,
800        areas: GridTemplateAreas::None,
801      })
802    }
803  }
804}
805
806impl ToCss for GridTemplate<'_> {
807  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
808  where
809    W: std::fmt::Write,
810  {
811    self.to_css_with_indent(dest, 15)
812  }
813}
814
815impl GridTemplate<'_> {
816  fn to_css_with_indent<W>(&self, dest: &mut Printer<W>, indent: u8) -> Result<(), PrinterError>
817  where
818    W: std::fmt::Write,
819  {
820    match &self.areas {
821      GridTemplateAreas::None => {
822        if self.rows == TrackSizing::None && self.columns == TrackSizing::None {
823          dest.write_str("none")?;
824        } else {
825          self.rows.to_css(dest)?;
826          dest.delim('/', true)?;
827          self.columns.to_css(dest)?;
828        }
829      }
830      GridTemplateAreas::Areas { areas, .. } => {
831        let track_list = match &self.rows {
832          TrackSizing::TrackList(list) => list,
833          _ => unreachable!(),
834        };
835
836        let mut areas_iter = areas.iter();
837        let mut line_names_iter = track_list.line_names.iter();
838        let mut items_iter = track_list.items.iter();
839
840        let mut next = areas_iter.next();
841        let mut first = true;
842        let mut indented = false;
843        while next.is_some() {
844          macro_rules! newline {
845            () => {
846              if !dest.minify {
847                if !indented {
848                  // Indent by the width of "grid-template: ", so the rows line up.
849                  dest.indent_by(indent);
850                  indented = true;
851                }
852                dest.newline()?;
853              }
854            };
855          }
856
857          if let Some(line_names) = line_names_iter.next() {
858            if !line_names.is_empty() {
859              if !dest.minify && line_names.len() == 2 {
860                dest.whitespace()?;
861                serialize_line_names(&line_names[0..1], dest)?;
862                newline!();
863                serialize_line_names(&line_names[1..], dest)?;
864              } else {
865                if !first {
866                  newline!();
867                }
868                serialize_line_names(line_names, dest)?;
869              }
870              dest.whitespace()?;
871            } else if !first {
872              newline!();
873            }
874          } else if !first {
875            newline!();
876          }
877
878          self.areas.write_string(dest, &mut areas_iter, &mut next)?;
879
880          if let Some(item) = items_iter.next() {
881            if *item != TrackListItem::TrackSize(TrackSize::default()) {
882              dest.whitespace()?;
883              match item {
884                TrackListItem::TrackSize(size) => size.to_css(dest)?,
885                _ => unreachable!(),
886              }
887            }
888          }
889
890          first = false;
891        }
892
893        if let Some(line_names) = line_names_iter.next() {
894          if !line_names.is_empty() {
895            dest.whitespace()?;
896            serialize_line_names(line_names, dest)?;
897          }
898        }
899
900        if let TrackSizing::TrackList(track_list) = &self.columns {
901          dest.newline()?;
902          dest.delim('/', false)?;
903          track_list.to_css(dest)?;
904        }
905
906        if indented {
907          dest.dedent_by(indent);
908        }
909      }
910    }
911
912    Ok(())
913  }
914}
915
916impl<'i> GridTemplate<'i> {
917  #[inline]
918  fn is_valid(rows: &TrackSizing, columns: &TrackSizing, areas: &GridTemplateAreas) -> bool {
919    // The `grid-template` shorthand supports only explicit track values (i.e. no `repeat()`)
920    // combined with grid-template-areas. If there are no areas, then any track values are allowed.
921    *areas == GridTemplateAreas::None
922      || (*rows != TrackSizing::None && rows.is_explicit() && columns.is_explicit())
923  }
924}
925
926impl_shorthand! {
927  GridTemplate(GridTemplate<'i>) {
928    rows: [GridTemplateRows],
929    columns: [GridTemplateColumns],
930    areas: [GridTemplateAreas],
931  }
932
933  fn is_valid(shorthand) {
934    GridTemplate::is_valid(&shorthand.rows, &shorthand.columns, &shorthand.areas)
935  }
936}
937
938bitflags! {
939  /// A value for the [grid-auto-flow](https://drafts.csswg.org/css-grid-2/#grid-auto-flow-property) property.
940  ///
941  /// [ row | column ] || dense
942  ///
943  /// The `Row` or `Column` flags may be combined with the `Dense` flag, but the `Row` and `Column` flags may
944  /// not be combined.
945  #[cfg_attr(feature = "visitor", derive(Visit))]
946  #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(from = "SerializedGridAutoFlow", into = "SerializedGridAutoFlow"))]
947  #[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
948  #[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Clone, Copy)]
949  pub struct GridAutoFlow: u8 {
950    /// The auto-placement algorithm places items by filling each row, adding new rows as necessary.
951    const Row    = 0b00;
952    /// The auto-placement algorithm places items by filling each column, adding new columns as necessary.
953    const Column = 0b01;
954    /// If specified, a dense packing algorithm is used, which fills in holes in the grid.
955    const Dense  = 0b10;
956  }
957}
958
959#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
960#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
961struct SerializedGridAutoFlow {
962  /// The direction of the auto flow.
963  direction: AutoFlowDirection,
964  /// If specified, a dense packing algorithm is used, which fills in holes in the grid.
965  dense: bool,
966}
967
968impl From<GridAutoFlow> for SerializedGridAutoFlow {
969  fn from(flow: GridAutoFlow) -> Self {
970    Self {
971      direction: if flow.contains(GridAutoFlow::Column) {
972        AutoFlowDirection::Column
973      } else {
974        AutoFlowDirection::Row
975      },
976      dense: flow.contains(GridAutoFlow::Dense),
977    }
978  }
979}
980
981impl From<SerializedGridAutoFlow> for GridAutoFlow {
982  fn from(s: SerializedGridAutoFlow) -> GridAutoFlow {
983    let mut flow = match s.direction {
984      AutoFlowDirection::Row => GridAutoFlow::Row,
985      AutoFlowDirection::Column => GridAutoFlow::Column,
986    };
987    if s.dense {
988      flow |= GridAutoFlow::Dense
989    }
990
991    flow
992  }
993}
994
995#[cfg_attr(
996  feature = "serde",
997  derive(serde::Serialize, serde::Deserialize),
998  serde(rename_all = "lowercase")
999)]
1000#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
1001enum AutoFlowDirection {
1002  Row,
1003  Column,
1004}
1005
1006#[cfg(feature = "jsonschema")]
1007#[cfg_attr(docsrs, doc(cfg(feature = "jsonschema")))]
1008impl<'a> schemars::JsonSchema for GridAutoFlow {
1009  fn is_referenceable() -> bool {
1010    true
1011  }
1012
1013  fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
1014    SerializedGridAutoFlow::json_schema(gen)
1015  }
1016
1017  fn schema_name() -> String {
1018    "GridAutoFlow".into()
1019  }
1020}
1021
1022impl Default for GridAutoFlow {
1023  fn default() -> GridAutoFlow {
1024    GridAutoFlow::Row
1025  }
1026}
1027
1028impl GridAutoFlow {
1029  fn direction(self) -> GridAutoFlow {
1030    self & GridAutoFlow::Column
1031  }
1032}
1033
1034impl<'i> Parse<'i> for GridAutoFlow {
1035  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
1036    let mut flow = GridAutoFlow::Row;
1037
1038    macro_rules! match_dense {
1039      () => {
1040        if input.try_parse(|input| input.expect_ident_matching("dense")).is_ok() {
1041          flow |= GridAutoFlow::Dense;
1042        }
1043      };
1044    }
1045
1046    let location = input.current_source_location();
1047    let ident = input.expect_ident()?;
1048    match_ignore_ascii_case! { &ident,
1049      "row" => {
1050        match_dense!();
1051      },
1052      "column" => {
1053        flow = GridAutoFlow::Column;
1054        match_dense!();
1055      },
1056      "dense" => {
1057        let location = input.current_source_location();
1058        input.try_parse(|input| {
1059          let ident = input.expect_ident()?;
1060          match_ignore_ascii_case! { &ident,
1061            "row" => {},
1062            "column" => {
1063              flow = GridAutoFlow::Column;
1064            },
1065            _ => return Err(location.new_unexpected_token_error(
1066              cssparser::Token::Ident(ident.clone())
1067            ))
1068          }
1069          Ok(())
1070        })?;
1071        flow |= GridAutoFlow::Dense;
1072      },
1073      _ => return Err(location.new_unexpected_token_error(
1074        cssparser::Token::Ident(ident.clone())
1075      ))
1076    }
1077
1078    Ok(flow)
1079  }
1080}
1081
1082impl ToCss for GridAutoFlow {
1083  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
1084  where
1085    W: std::fmt::Write,
1086  {
1087    let s = if *self == GridAutoFlow::Row {
1088      "row"
1089    } else if *self == GridAutoFlow::Column {
1090      "column"
1091    } else if *self == GridAutoFlow::Row | GridAutoFlow::Dense {
1092      if dest.minify {
1093        "dense"
1094      } else {
1095        "row dense"
1096      }
1097    } else if *self == GridAutoFlow::Column | GridAutoFlow::Dense {
1098      "column dense"
1099    } else {
1100      unreachable!();
1101    };
1102
1103    dest.write_str(s)
1104  }
1105}
1106
1107/// A value for the [grid](https://drafts.csswg.org/css-grid-2/#grid-shorthand) shorthand property.
1108///
1109/// <'grid-template'> | <'grid-template-rows'> / [ auto-flow && dense? ] <'grid-auto-columns'>? | [ auto-flow && dense? ] <'grid-auto-rows'>? / <'grid-template-columns'>
1110///
1111/// Explicit and implicit values may not be combined.
1112#[derive(Debug, Clone, PartialEq)]
1113#[cfg_attr(feature = "visitor", derive(Visit))]
1114#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
1115#[cfg_attr(
1116  feature = "serde",
1117  derive(serde::Serialize, serde::Deserialize),
1118  serde(rename_all = "camelCase")
1119)]
1120#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
1121pub struct Grid<'i> {
1122  /// Explicit grid template rows.
1123  #[cfg_attr(feature = "serde", serde(borrow))]
1124  pub rows: TrackSizing<'i>,
1125  /// Explicit grid template columns.
1126  pub columns: TrackSizing<'i>,
1127  /// Explicit grid template areas.
1128  pub areas: GridTemplateAreas,
1129  /// The grid auto rows.
1130  pub auto_rows: TrackSizeList,
1131  /// The grid auto columns.
1132  pub auto_columns: TrackSizeList,
1133  /// The grid auto flow.
1134  pub auto_flow: GridAutoFlow,
1135}
1136
1137impl<'i> Parse<'i> for Grid<'i> {
1138  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
1139    // <'grid-template'>
1140    if let Ok(template) = input.try_parse(GridTemplate::parse) {
1141      Ok(Grid {
1142        rows: template.rows,
1143        columns: template.columns,
1144        areas: template.areas,
1145        auto_rows: TrackSizeList::default(),
1146        auto_columns: TrackSizeList::default(),
1147        auto_flow: GridAutoFlow::default(),
1148      })
1149
1150    // <'grid-template-rows'> / [ auto-flow && dense? ] <'grid-auto-columns'>?
1151    } else if let Ok(rows) = input.try_parse(TrackSizing::parse) {
1152      input.expect_delim('/')?;
1153      let auto_flow = parse_grid_auto_flow(input, GridAutoFlow::Column)?;
1154      let auto_columns = TrackSizeList::parse(input).unwrap_or_default();
1155      Ok(Grid {
1156        rows,
1157        columns: TrackSizing::None,
1158        areas: GridTemplateAreas::None,
1159        auto_rows: TrackSizeList::default(),
1160        auto_columns,
1161        auto_flow,
1162      })
1163
1164    // [ auto-flow && dense? ] <'grid-auto-rows'>? / <'grid-template-columns'>
1165    } else {
1166      let auto_flow = parse_grid_auto_flow(input, GridAutoFlow::Row)?;
1167      let auto_rows = input.try_parse(TrackSizeList::parse).unwrap_or_default();
1168      input.expect_delim('/')?;
1169      let columns = TrackSizing::parse(input)?;
1170      Ok(Grid {
1171        rows: TrackSizing::None,
1172        columns,
1173        areas: GridTemplateAreas::None,
1174        auto_rows,
1175        auto_columns: TrackSizeList::default(),
1176        auto_flow,
1177      })
1178    }
1179  }
1180}
1181
1182fn parse_grid_auto_flow<'i, 't>(
1183  input: &mut Parser<'i, 't>,
1184  flow: GridAutoFlow,
1185) -> Result<GridAutoFlow, ParseError<'i, ParserError<'i>>> {
1186  if input.try_parse(|input| input.expect_ident_matching("auto-flow")).is_ok() {
1187    if input.try_parse(|input| input.expect_ident_matching("dense")).is_ok() {
1188      Ok(flow | GridAutoFlow::Dense)
1189    } else {
1190      Ok(flow)
1191    }
1192  } else if input.try_parse(|input| input.expect_ident_matching("dense")).is_ok() {
1193    input.expect_ident_matching("auto-flow")?;
1194    Ok(flow | GridAutoFlow::Dense)
1195  } else {
1196    Err(input.new_error_for_next_token())
1197  }
1198}
1199
1200impl ToCss for Grid<'_> {
1201  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
1202  where
1203    W: std::fmt::Write,
1204  {
1205    let is_auto_initial = self.auto_rows == TrackSizeList::default()
1206      && self.auto_columns == TrackSizeList::default()
1207      && self.auto_flow == GridAutoFlow::default();
1208
1209    // Handle the case where areas is set but rows is None (auto-flow syntax).
1210    // In this case, output "auto-flow / columns" format.
1211    if self.areas != GridTemplateAreas::None && self.rows == TrackSizing::None {
1212      dest.write_str("auto-flow")?;
1213      if self.auto_flow.contains(GridAutoFlow::Dense) {
1214        dest.write_str(" dense")?;
1215      }
1216      if self.auto_rows != TrackSizeList::default() {
1217        dest.write_char(' ')?;
1218        self.auto_rows.to_css(dest)?;
1219      }
1220      dest.delim('/', true)?;
1221      self.columns.to_css(dest)?;
1222      return Ok(());
1223    }
1224
1225    // Handle the case where areas is set but columns is None (auto-flow column syntax).
1226    // In this case, output "rows / auto-flow" format.
1227    if self.areas != GridTemplateAreas::None
1228      && self.columns == TrackSizing::None
1229      && self.auto_flow.direction() == GridAutoFlow::Column
1230    {
1231      self.rows.to_css(dest)?;
1232      dest.delim('/', true)?;
1233      dest.write_str("auto-flow")?;
1234      if self.auto_flow.contains(GridAutoFlow::Dense) {
1235        dest.write_str(" dense")?;
1236      }
1237      if self.auto_columns != TrackSizeList::default() {
1238        dest.write_char(' ')?;
1239        self.auto_columns.to_css(dest)?;
1240      }
1241      return Ok(());
1242    }
1243
1244    if self.areas != GridTemplateAreas::None
1245      || (self.rows != TrackSizing::None && self.columns != TrackSizing::None)
1246      || (self.areas == GridTemplateAreas::None && is_auto_initial)
1247    {
1248      if !is_auto_initial {
1249        unreachable!("invalid grid shorthand: mixed implicit and explicit values");
1250      }
1251      let template = GridTemplate {
1252        rows: self.rows.clone(),
1253        columns: self.columns.clone(),
1254        areas: self.areas.clone(),
1255      };
1256      template.to_css_with_indent(dest, 6)?;
1257    } else if self.auto_flow.direction() == GridAutoFlow::Column {
1258      if self.columns != TrackSizing::None || self.auto_rows != TrackSizeList::default() {
1259        unreachable!("invalid grid shorthand: mixed implicit and explicit values");
1260      }
1261      self.rows.to_css(dest)?;
1262      dest.delim('/', true)?;
1263      dest.write_str("auto-flow")?;
1264      if self.auto_flow.contains(GridAutoFlow::Dense) {
1265        dest.write_str(" dense")?;
1266      }
1267      if self.auto_columns != TrackSizeList::default() {
1268        dest.write_char(' ')?;
1269        self.auto_columns.to_css(dest)?;
1270      }
1271    } else {
1272      if self.rows != TrackSizing::None || self.auto_columns != TrackSizeList::default() {
1273        unreachable!("invalid grid shorthand: mixed implicit and explicit values");
1274      }
1275      dest.write_str("auto-flow")?;
1276      if self.auto_flow.contains(GridAutoFlow::Dense) {
1277        dest.write_str(" dense")?;
1278      }
1279      if self.auto_rows != TrackSizeList::default() {
1280        dest.write_char(' ')?;
1281        self.auto_rows.to_css(dest)?;
1282      }
1283      dest.delim('/', true)?;
1284      self.columns.to_css(dest)?;
1285    }
1286
1287    Ok(())
1288  }
1289}
1290
1291impl<'i> Grid<'i> {
1292  #[inline]
1293  fn is_valid(
1294    rows: &TrackSizing,
1295    columns: &TrackSizing,
1296    areas: &GridTemplateAreas,
1297    auto_rows: &TrackSizeList,
1298    auto_columns: &TrackSizeList,
1299    auto_flow: &GridAutoFlow,
1300  ) -> bool {
1301    let default_track_size_list = TrackSizeList::default();
1302
1303    // When areas is set but rows is None (auto-flow syntax like "grid: auto-flow / 1fr"),
1304    // we can output the auto-flow shorthand along with "grid-template-areas" separately.
1305    // ⚠️ The case of `grid: 1fr / auto-flow` does not require such handling.
1306    if *areas != GridTemplateAreas::None && *rows == TrackSizing::None {
1307      return auto_flow.direction() == GridAutoFlow::Row;
1308    }
1309
1310    // The `grid` shorthand can either be fully explicit (e.g. same as `grid-template`),
1311    // or explicit along a single axis. If there are auto rows, then there cannot be explicit rows, for example.
1312    let is_template = GridTemplate::is_valid(rows, columns, areas);
1313    let is_explicit = *auto_rows == default_track_size_list
1314      && *auto_columns == default_track_size_list
1315      && *auto_flow == GridAutoFlow::default();
1316    // grid-auto-flow: row shorthand syntax:
1317    // [ auto-flow && dense? ] <'grid-auto-rows'>? / <'grid-template-columns'>
1318    let is_auto_rows = auto_flow.direction() == GridAutoFlow::Row
1319      && *rows == TrackSizing::None
1320      && *auto_columns == default_track_size_list;
1321    // grid-auto-flow: column shorthand syntax:
1322    // <'grid-template-rows'> / [ auto-flow && dense? ] <'grid-auto-columns'>?
1323    let is_auto_columns = auto_flow.direction() == GridAutoFlow::Column
1324      && *columns == TrackSizing::None
1325      && *auto_rows == default_track_size_list;
1326
1327    (is_template && is_explicit) || is_auto_rows || is_auto_columns
1328  }
1329}
1330
1331// TODO: shorthand `grid: auto-flow 1fr / 100px` https://drafts.csswg.org/css-grid/#example-dec34e0f
1332impl_shorthand! {
1333  Grid(Grid<'i>) {
1334    rows: [GridTemplateRows],
1335    columns: [GridTemplateColumns],
1336    areas: [GridTemplateAreas],
1337    auto_rows: [GridAutoRows],
1338    auto_columns: [GridAutoColumns],
1339    auto_flow: [GridAutoFlow],
1340  }
1341
1342  fn is_valid(grid) {
1343    Grid::is_valid(&grid.rows, &grid.columns, &grid.areas, &grid.auto_rows, &grid.auto_columns, &grid.auto_flow)
1344  }
1345}
1346
1347/// A [`<grid-line>`](https://drafts.csswg.org/css-grid-2/#typedef-grid-row-start-grid-line) value,
1348/// used in the `grid-row-start`, `grid-row-end`, `grid-column-start`, and `grid-column-end` properties.
1349#[derive(Debug, Clone, PartialEq)]
1350#[cfg_attr(feature = "visitor", derive(Visit))]
1351#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
1352#[cfg_attr(
1353  feature = "serde",
1354  derive(serde::Serialize, serde::Deserialize),
1355  serde(tag = "type", rename_all = "kebab-case")
1356)]
1357#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
1358pub enum GridLine<'i> {
1359  /// Automatic placement.
1360  Auto,
1361  /// A named grid area name (automatically postfixed by `-start` or `-end`), or and explicit grid line name.
1362  Area {
1363    /// A grid area name.
1364    name: CustomIdent<'i>,
1365  },
1366  /// The Nth grid line, optionally filtered by line name. Negative numbers count backwards from the end.
1367  Line {
1368    /// A line number.
1369    index: CSSInteger,
1370    /// A line name to filter by.
1371    #[cfg_attr(feature = "serde", serde(borrow))]
1372    name: Option<CustomIdent<'i>>,
1373  },
1374  /// A grid span based on the Nth grid line from the opposite edge, optionally filtered by line name.
1375  Span {
1376    /// A line number.
1377    index: CSSInteger,
1378    /// A line name to filter by.
1379    name: Option<CustomIdent<'i>>,
1380  },
1381}
1382
1383impl<'i> Parse<'i> for GridLine<'i> {
1384  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
1385    if input.try_parse(|input| input.expect_ident_matching("auto")).is_ok() {
1386      return Ok(GridLine::Auto);
1387    }
1388
1389    if input.try_parse(|input| input.expect_ident_matching("span")).is_ok() {
1390      // TODO: is calc() supported here??
1391      let (index, name) = if let Ok(line_number) = input.try_parse(CSSInteger::parse) {
1392        let ident = input.try_parse(CustomIdent::parse).ok();
1393        (line_number, ident)
1394      } else if let Ok(ident) = input.try_parse(CustomIdent::parse) {
1395        let line_number = input.try_parse(CSSInteger::parse).unwrap_or(1);
1396        (line_number, Some(ident))
1397      } else {
1398        return Err(input.new_custom_error(ParserError::InvalidDeclaration));
1399      };
1400
1401      if index == 0 {
1402        return Err(input.new_custom_error(ParserError::InvalidDeclaration));
1403      }
1404
1405      return Ok(GridLine::Span { index, name });
1406    }
1407
1408    if let Ok(index) = input.try_parse(CSSInteger::parse) {
1409      if index == 0 {
1410        return Err(input.new_custom_error(ParserError::InvalidDeclaration));
1411      }
1412      let name = input.try_parse(CustomIdent::parse).ok();
1413      return Ok(GridLine::Line { index, name });
1414    }
1415
1416    let name = CustomIdent::parse(input)?;
1417    if let Ok(index) = input.try_parse(CSSInteger::parse) {
1418      if index == 0 {
1419        return Err(input.new_custom_error(ParserError::InvalidDeclaration));
1420      }
1421      return Ok(GridLine::Line {
1422        index,
1423        name: Some(name),
1424      });
1425    }
1426
1427    Ok(GridLine::Area { name })
1428  }
1429}
1430
1431impl ToCss for GridLine<'_> {
1432  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
1433  where
1434    W: std::fmt::Write,
1435  {
1436    match self {
1437      GridLine::Auto => dest.write_str("auto"),
1438      GridLine::Area { name } => write_ident(&name.0, dest),
1439      GridLine::Line { index, name } => {
1440        index.to_css(dest)?;
1441        if let Some(id) = name {
1442          dest.write_char(' ')?;
1443          write_ident(&id.0, dest)?;
1444        }
1445        Ok(())
1446      }
1447      GridLine::Span { index, name } => {
1448        dest.write_str("span ")?;
1449        if *index != 1 || name.is_none() {
1450          index.to_css(dest)?;
1451          if name.is_some() {
1452            dest.write_char(' ')?;
1453          }
1454        }
1455
1456        if let Some(id) = name {
1457          write_ident(&id.0, dest)?;
1458        }
1459        Ok(())
1460      }
1461    }
1462  }
1463}
1464
1465impl<'i> GridLine<'i> {
1466  fn default_end_value(&self) -> GridLine<'i> {
1467    if matches!(self, GridLine::Area { .. }) {
1468      self.clone()
1469    } else {
1470      GridLine::Auto
1471    }
1472  }
1473
1474  fn can_omit_end(&self, end: &GridLine) -> bool {
1475    if let GridLine::Area { name: start_id } = &self {
1476      matches!(end, GridLine::Area { name: end_id } if end_id == start_id)
1477    } else if matches!(end, GridLine::Auto) {
1478      true
1479    } else {
1480      false
1481    }
1482  }
1483}
1484
1485macro_rules! impl_grid_placement {
1486  ($name: ident) => {
1487    impl<'i> Parse<'i> for $name<'i> {
1488      fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
1489        let start = GridLine::parse(input)?;
1490        let end = if input.try_parse(|input| input.expect_delim('/')).is_ok() {
1491          GridLine::parse(input)?
1492        } else {
1493          start.default_end_value()
1494        };
1495
1496        Ok($name { start, end })
1497      }
1498    }
1499
1500    impl ToCss for $name<'_> {
1501      fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
1502      where
1503        W: std::fmt::Write,
1504      {
1505        self.start.to_css(dest)?;
1506
1507        if !self.start.can_omit_end(&self.end) {
1508          dest.delim('/', true)?;
1509          self.end.to_css(dest)?;
1510        }
1511        Ok(())
1512      }
1513    }
1514  };
1515}
1516
1517define_shorthand! {
1518  /// A value for the [grid-row](https://drafts.csswg.org/css-grid-2/#propdef-grid-row) shorthand property.
1519  /// <grid-line> [ / <grid-line> ]?
1520  pub struct GridRow<'i> {
1521    /// The starting line.
1522    #[cfg_attr(feature = "serde", serde(borrow))]
1523    start: GridRowStart(GridLine<'i>),
1524    /// The ending line.
1525    end: GridRowEnd(GridLine<'i>),
1526  }
1527}
1528
1529define_shorthand! {
1530  /// A value for the [grid-column](https://drafts.csswg.org/css-grid-2/#propdef-grid-column) shorthand property.
1531  /// <grid-line> [ / <grid-line> ]?
1532  pub struct GridColumn<'i> {
1533    /// The starting line.
1534    #[cfg_attr(feature = "serde", serde(borrow))]
1535    start: GridColumnStart(GridLine<'i>),
1536    /// The ending line.
1537    end: GridColumnEnd(GridLine<'i>),
1538  }
1539}
1540
1541impl_grid_placement!(GridRow);
1542impl_grid_placement!(GridColumn);
1543
1544define_shorthand! {
1545  /// A value for the [grid-area](https://drafts.csswg.org/css-grid-2/#propdef-grid-area) shorthand property.
1546  /// <grid-line> [ / <grid-line> ]{0,3}
1547  pub struct GridArea<'i> {
1548    /// The grid row start placement.
1549    #[cfg_attr(feature = "serde", serde(borrow))]
1550    row_start: GridRowStart(GridLine<'i>),
1551    /// The grid column start placement.
1552    column_start: GridColumnStart(GridLine<'i>),
1553    /// The grid row end placement.
1554    row_end: GridRowEnd(GridLine<'i>),
1555    /// The grid column end placement.
1556    column_end: GridColumnEnd(GridLine<'i>),
1557  }
1558}
1559
1560impl<'i> Parse<'i> for GridArea<'i> {
1561  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
1562    let row_start = GridLine::parse(input)?;
1563    let column_start = if input.try_parse(|input| input.expect_delim('/')).is_ok() {
1564      GridLine::parse(input)?
1565    } else {
1566      let opposite = row_start.default_end_value();
1567      return Ok(GridArea {
1568        row_start,
1569        column_start: opposite.clone(),
1570        row_end: opposite.clone(),
1571        column_end: opposite,
1572      });
1573    };
1574
1575    let row_end = if input.try_parse(|input| input.expect_delim('/')).is_ok() {
1576      GridLine::parse(input)?
1577    } else {
1578      let row_end = row_start.default_end_value();
1579      let column_end = column_start.default_end_value();
1580      return Ok(GridArea {
1581        row_start,
1582        column_start,
1583        row_end,
1584        column_end,
1585      });
1586    };
1587
1588    let column_end = if input.try_parse(|input| input.expect_delim('/')).is_ok() {
1589      GridLine::parse(input)?
1590    } else {
1591      let column_end = column_start.default_end_value();
1592      return Ok(GridArea {
1593        row_start,
1594        column_start,
1595        row_end,
1596        column_end,
1597      });
1598    };
1599
1600    Ok(GridArea {
1601      row_start,
1602      column_start,
1603      row_end,
1604      column_end,
1605    })
1606  }
1607}
1608
1609impl ToCss for GridArea<'_> {
1610  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
1611  where
1612    W: std::fmt::Write,
1613  {
1614    self.row_start.to_css(dest)?;
1615
1616    let can_omit_column_end = self.column_start.can_omit_end(&self.column_end);
1617    let can_omit_row_end = can_omit_column_end && self.row_start.can_omit_end(&self.row_end);
1618    let can_omit_column_start = can_omit_row_end && self.row_start.can_omit_end(&self.column_start);
1619
1620    if !can_omit_column_start {
1621      dest.delim('/', true)?;
1622      self.column_start.to_css(dest)?;
1623    }
1624
1625    if !can_omit_row_end {
1626      dest.delim('/', true)?;
1627      self.row_end.to_css(dest)?;
1628    }
1629
1630    if !can_omit_column_end {
1631      dest.delim('/', true)?;
1632      self.column_end.to_css(dest)?;
1633    }
1634
1635    Ok(())
1636  }
1637}
1638
1639#[derive(Default, Debug)]
1640pub(crate) struct GridHandler<'i> {
1641  rows: Option<TrackSizing<'i>>,
1642  columns: Option<TrackSizing<'i>>,
1643  areas: Option<GridTemplateAreas>,
1644  auto_rows: Option<TrackSizeList>,
1645  auto_columns: Option<TrackSizeList>,
1646  auto_flow: Option<GridAutoFlow>,
1647  row_start: Option<GridLine<'i>>,
1648  column_start: Option<GridLine<'i>>,
1649  row_end: Option<GridLine<'i>>,
1650  column_end: Option<GridLine<'i>>,
1651  has_any: bool,
1652}
1653
1654impl<'i> PropertyHandler<'i> for GridHandler<'i> {
1655  fn handle_property(
1656    &mut self,
1657    property: &Property<'i>,
1658    dest: &mut DeclarationList<'i>,
1659    context: &mut PropertyHandlerContext<'i, '_>,
1660  ) -> bool {
1661    use Property::*;
1662
1663    match property {
1664      GridTemplateColumns(columns) => self.columns = Some(columns.clone()),
1665      GridTemplateRows(rows) => self.rows = Some(rows.clone()),
1666      GridTemplateAreas(areas) => self.areas = Some(areas.clone()),
1667      GridAutoColumns(auto_columns) => self.auto_columns = Some(auto_columns.clone()),
1668      GridAutoRows(auto_rows) => self.auto_rows = Some(auto_rows.clone()),
1669      GridAutoFlow(auto_flow) => self.auto_flow = Some(auto_flow.clone()),
1670      GridTemplate(template) => {
1671        self.rows = Some(template.rows.clone());
1672        self.columns = Some(template.columns.clone());
1673        self.areas = Some(template.areas.clone());
1674      }
1675      Grid(grid) => {
1676        self.rows = Some(grid.rows.clone());
1677        self.columns = Some(grid.columns.clone());
1678        self.areas = Some(grid.areas.clone());
1679        self.auto_rows = Some(grid.auto_rows.clone());
1680        self.auto_columns = Some(grid.auto_columns.clone());
1681        self.auto_flow = Some(grid.auto_flow.clone());
1682      }
1683      GridRowStart(row_start) => self.row_start = Some(row_start.clone()),
1684      GridRowEnd(row_end) => self.row_end = Some(row_end.clone()),
1685      GridColumnStart(column_start) => self.column_start = Some(column_start.clone()),
1686      GridColumnEnd(column_end) => self.column_end = Some(column_end.clone()),
1687      GridRow(row) => {
1688        self.row_start = Some(row.start.clone());
1689        self.row_end = Some(row.end.clone());
1690      }
1691      GridColumn(column) => {
1692        self.column_start = Some(column.start.clone());
1693        self.column_end = Some(column.end.clone());
1694      }
1695      GridArea(area) => {
1696        self.row_start = Some(area.row_start.clone());
1697        self.row_end = Some(area.row_end.clone());
1698        self.column_start = Some(area.column_start.clone());
1699        self.column_end = Some(area.column_end.clone());
1700      }
1701      Unparsed(val) if is_grid_property(&val.property_id) => {
1702        self.finalize(dest, context);
1703        dest.push(property.clone());
1704      }
1705      _ => return false,
1706    }
1707
1708    self.has_any = true;
1709    true
1710  }
1711
1712  fn finalize(&mut self, dest: &mut DeclarationList<'i>, _: &mut PropertyHandlerContext<'i, '_>) {
1713    if !self.has_any {
1714      return;
1715    }
1716
1717    self.has_any = false;
1718
1719    let mut rows = std::mem::take(&mut self.rows);
1720    let mut columns = std::mem::take(&mut self.columns);
1721    let mut areas = std::mem::take(&mut self.areas);
1722    let mut auto_rows = std::mem::take(&mut self.auto_rows);
1723    let mut auto_columns = std::mem::take(&mut self.auto_columns);
1724    let mut auto_flow = std::mem::take(&mut self.auto_flow);
1725    let mut row_start = std::mem::take(&mut self.row_start);
1726    let mut row_end = std::mem::take(&mut self.row_end);
1727    let mut column_start = std::mem::take(&mut self.column_start);
1728    let mut column_end = std::mem::take(&mut self.column_end);
1729
1730    if let (Some(rows_val), Some(columns_val), Some(areas_val)) = (&rows, &columns, &areas) {
1731      let mut has_template = true;
1732      if let (Some(auto_rows_val), Some(auto_columns_val), Some(auto_flow_val)) =
1733        (&auto_rows, &auto_columns, &auto_flow)
1734      {
1735        // The `grid` shorthand can either be fully explicit (e.g. same as `grid-template`),
1736        // or explicit along a single axis. If there are auto rows, then there cannot be explicit rows, for example.
1737        if Grid::is_valid(
1738          rows_val,
1739          columns_val,
1740          areas_val,
1741          auto_rows_val,
1742          auto_columns_val,
1743          auto_flow_val,
1744        ) {
1745          let needs_separate_areas = *areas_val != GridTemplateAreas::None
1746            && ((*rows_val == TrackSizing::None && auto_flow_val.direction() == GridAutoFlow::Row)
1747              || (*columns_val == TrackSizing::None && auto_flow_val.direction() == GridAutoFlow::Column));
1748
1749          // Pad areas with "." for missing rows. But don't pad if we're using auto-flow syntax,
1750          // because grid-template-areas should remain as-is in that case.
1751          // Use tuple to avoid double cloning when needs_separate_areas is true.
1752          let (areas_for_grid, areas_for_output) = if needs_separate_areas {
1753            // Take the original areas directly to avoid cloning when needs_separate_areas is true
1754            (areas_val.clone(), Some(areas_val.clone()))
1755          } else {
1756            (GridHandler::pad_grid_template_areas(rows_val, areas_val.clone()), None)
1757          };
1758
1759          dest.push(Property::Grid(Grid {
1760            rows: rows_val.clone(),
1761            columns: columns_val.clone(),
1762            areas: areas_for_grid,
1763            auto_rows: auto_rows_val.clone(),
1764            auto_columns: auto_columns_val.clone(),
1765            auto_flow: auto_flow_val.clone(),
1766          }));
1767
1768          has_template = false;
1769          auto_rows = None;
1770          auto_columns = None;
1771          auto_flow = None;
1772
1773          // When areas is set but rows/columns is None (auto-flow syntax), also output
1774          // grid-template-areas separately since grid shorthand can't represent this combination.
1775          if let Some(areas) = areas_for_output {
1776            dest.push(Property::GridTemplateAreas(areas));
1777          }
1778        }
1779      }
1780
1781      // The `grid-template` shorthand supports only explicit track values (i.e. no `repeat()`)
1782      // combined with grid-template-areas. If there are no areas, then any track values are allowed.
1783      if has_template && GridTemplate::is_valid(rows_val, columns_val, areas_val) {
1784        // Pad areas with "." for missing rows
1785        let padded_areas = GridHandler::pad_grid_template_areas(rows_val, areas_val.clone());
1786
1787        dest.push(Property::GridTemplate(GridTemplate {
1788          rows: rows_val.clone(),
1789          columns: columns_val.clone(),
1790          areas: padded_areas,
1791        }));
1792
1793        has_template = false;
1794      }
1795
1796      if !has_template {
1797        rows = None;
1798        columns = None;
1799        areas = None;
1800      }
1801    }
1802
1803    if row_start.is_some() && row_end.is_some() && column_start.is_some() && column_end.is_some() {
1804      dest.push(Property::GridArea(GridArea {
1805        row_start: std::mem::take(&mut row_start).unwrap(),
1806        row_end: std::mem::take(&mut row_end).unwrap(),
1807        column_start: std::mem::take(&mut column_start).unwrap(),
1808        column_end: std::mem::take(&mut column_end).unwrap(),
1809      }))
1810    } else {
1811      if row_start.is_some() && row_end.is_some() {
1812        dest.push(Property::GridRow(GridRow {
1813          start: std::mem::take(&mut row_start).unwrap(),
1814          end: std::mem::take(&mut row_end).unwrap(),
1815        }))
1816      }
1817
1818      if column_start.is_some() && column_end.is_some() {
1819        dest.push(Property::GridColumn(GridColumn {
1820          start: std::mem::take(&mut column_start).unwrap(),
1821          end: std::mem::take(&mut column_end).unwrap(),
1822        }))
1823      }
1824    }
1825
1826    macro_rules! single_property {
1827      ($prop: ident, $key: ident) => {
1828        if let Some(val) = $key {
1829          dest.push(Property::$prop(val))
1830        }
1831      };
1832    }
1833
1834    single_property!(GridTemplateRows, rows);
1835    single_property!(GridTemplateColumns, columns);
1836    single_property!(GridTemplateAreas, areas);
1837    single_property!(GridAutoRows, auto_rows);
1838    single_property!(GridAutoColumns, auto_columns);
1839    single_property!(GridAutoFlow, auto_flow);
1840    single_property!(GridRowStart, row_start);
1841    single_property!(GridRowEnd, row_end);
1842    single_property!(GridColumnStart, column_start);
1843    single_property!(GridColumnEnd, column_end);
1844  }
1845}
1846
1847/// Pads grid template areas with "." (None) for missing rows.
1848/// All the remaining unnamed areas in a grid can be referred using null cell
1849/// tokens. A null cell token is a sequence of one or more . (U+002E FULL STOP)
1850/// characters, e.g., ., ..., or ..... etc. A null cell token can be used to
1851/// create empty spaces in the grid.
1852/// Spec: https://drafts.csswg.org/css-grid/#ref-for-string-value①
1853impl GridHandler<'_> {
1854  fn pad_grid_template_areas(rows: &TrackSizing, areas: GridTemplateAreas) -> GridTemplateAreas {
1855    match (rows, areas) {
1856      (TrackSizing::TrackList(rows_list), GridTemplateAreas::Areas { columns, areas }) => {
1857        let rows_count = rows_list.items.len();
1858        let areas_rows_count = areas.len() / columns as usize;
1859        if areas_rows_count < rows_count {
1860          let mut padded_areas = areas;
1861          // Fill each missing row with "." (represented as None)
1862          for _ in areas_rows_count..rows_count {
1863            for _ in 0..columns {
1864              padded_areas.push(None);
1865            }
1866          }
1867          GridTemplateAreas::Areas {
1868            columns,
1869            areas: padded_areas,
1870          }
1871        } else {
1872          GridTemplateAreas::Areas { columns, areas }
1873        }
1874      }
1875      (_, areas) => areas,
1876    }
1877  }
1878}
1879
1880#[inline]
1881fn is_grid_property(property_id: &PropertyId) -> bool {
1882  match property_id {
1883    PropertyId::GridTemplateColumns
1884    | PropertyId::GridTemplateRows
1885    | PropertyId::GridTemplateAreas
1886    | PropertyId::GridAutoColumns
1887    | PropertyId::GridAutoRows
1888    | PropertyId::GridAutoFlow
1889    | PropertyId::GridTemplate
1890    | PropertyId::Grid
1891    | PropertyId::GridRowStart
1892    | PropertyId::GridRowEnd
1893    | PropertyId::GridColumnStart
1894    | PropertyId::GridColumnEnd
1895    | PropertyId::GridRow
1896    | PropertyId::GridColumn
1897    | PropertyId::GridArea => true,
1898    _ => false,
1899  }
1900}