Skip to main content

taffy/style/
grid.rs

1//! Style types for CSS Grid layout
2use super::{
3    AlignContent, AlignItems, AlignSelf, CheapCloneStr, CompactLength, CoreStyle, Dimension, JustifyContent,
4    LengthPercentage, LengthPercentageAuto, Style,
5};
6use crate::compute::grid::{GridCoordinate, GridLine, OriginZeroLine, MAX_GRID_TRACKS};
7use crate::geometry::{AbsoluteAxis, AbstractAxis, Line, MinMax, Size};
8use crate::style_helpers::*;
9use crate::sys::{DefaultCheapStr, Vec};
10use core::cmp::{max, min};
11use core::fmt::Debug;
12
13#[cfg(feature = "parse")]
14use crate::util::parse::{
15    from_str_from_css, parse_css_str_entirely, CssParseResult, FromCss, ParseError, Parser, Token,
16};
17
18/// Defines the value of the `grid-template-areas` property: the named areas plus the overall
19/// size (in tracks) of the area template.
20///
21/// The template may be larger than the extents of the named areas due to unnamed (`.`) cells,
22/// so the size is stored explicitly.
23#[derive(Debug, Clone, PartialEq)]
24#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
25pub struct GridTemplateAreas<CustomIdent: CheapCloneStr> {
26    /// The named grid areas
27    pub areas: crate::util::sys::GridTrackVec<GridTemplateArea<CustomIdent>>,
28    /// The number of rows in the area template
29    pub row_count: u16,
30    /// The number of columns in the area template
31    pub column_count: u16,
32}
33
34/// Defines a grid area
35#[derive(Debug, Clone, PartialEq)]
36#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
37pub struct GridTemplateArea<CustomIdent: CheapCloneStr> {
38    /// The name of the grid area which
39    #[cfg_attr(feature = "serde", serde(deserialize_with = "crate::util::deserialize_from_str"))]
40    pub name: CustomIdent,
41    /// The index of the row at which the grid area starts in grid coordinates.
42    pub row_start: u16,
43    /// The index of the row at which the grid area ends in grid coordinates.
44    pub row_end: u16,
45    /// The index of the column at which the grid area starts in grid coordinates.
46    pub column_start: u16,
47    /// The index of the column at which the grid area end in grid coordinates.
48    pub column_end: u16,
49}
50
51/// Defines a named grid line
52#[derive(Debug, Clone, PartialEq)]
53#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
54pub struct NamedGridLine<CustomIdent: CheapCloneStr> {
55    /// The name of the grid area which
56    #[cfg_attr(feature = "serde", serde(deserialize_with = "crate::util::deserialize_from_str"))]
57    pub name: CustomIdent,
58    /// The index of the row at which the grid area starts in grid coordinates.
59    pub index: u16,
60}
61
62/// Axis as `Row` or `Column`
63#[derive(Debug, Clone, Copy, PartialEq)]
64pub(crate) enum GridAreaAxis {
65    /// The `Row` axis
66    Row,
67    /// The `Column` axis
68    Column,
69}
70
71/// Logical end (`Start` or `End`)
72#[derive(Debug, Clone, Copy, PartialEq)]
73pub(crate) enum GridAreaEnd {
74    /// The `Start` end
75    Start,
76    /// The `End` end
77    End,
78}
79
80/// A trait to represent a `repeat()` clause in a `grid-template-*` definition
81pub trait GenericRepetition {
82    /// The type that represents `<custom-ident>`s (for named lines)
83    type CustomIdent: CheapCloneStr;
84    /// The type which represents an iterator over the list of repeated tracks
85    type RepetitionTrackList<'a>: Iterator<Item = TrackSizingFunction> + ExactSizeIterator + Clone
86    where
87        Self: 'a;
88
89    /// A nested iterator of line names (nested because each line may have multiple associated names)
90    type TemplateLineNames<'a>: TemplateLineNames<'a, Self::CustomIdent>
91    where
92        Self: 'a;
93    /// The repetition count (integer, auto-fill, or auto-fit)
94    fn count(&self) -> RepetitionCount;
95    /// Get an iterator over the repeated tracks
96    fn tracks(&self) -> Self::RepetitionTrackList<'_>;
97    /// Returns the number of repeated tracks
98    fn track_count(&self) -> u16 {
99        self.tracks().len().min(u16::MAX as usize) as u16
100    }
101    /// Returns an iterator over the lines names
102    fn lines_names(&self) -> Self::TemplateLineNames<'_>;
103}
104
105/// A nested list of line names. This is effectively a generic representation of `Vec<Vec<String>>` that allows
106/// both the collection and string type to be customised.
107#[rustfmt::skip]
108pub trait TemplateLineNames<'a, S: CheapCloneStr> : Iterator<Item = Self::LineNameSet<'a>> + ExactSizeIterator + Clone where Self: 'a {
109    /// A simple list line names. This is effectively a generic representation of `VecString>` that allows
110    /// both the collection and string type to be customised.
111    type LineNameSet<'b>: Iterator<Item = &'b S> + ExactSizeIterator + Clone where Self: 'b;
112}
113
114impl<'a, S: CheapCloneStr> TemplateLineNames<'a, S>
115    for core::iter::Map<core::slice::Iter<'a, Vec<S>>, fn(&Vec<S>) -> core::slice::Iter<'_, S>>
116{
117    type LineNameSet<'b>
118        = core::slice::Iter<'b, S>
119    where
120        Self: 'b;
121}
122
123#[derive(Copy, Clone)]
124/// A type representing a component in a `grid-template-*` defintion where the type
125/// representing `repeat()`s is generic
126pub enum GenericGridTemplateComponent<S, Repetition>
127where
128    S: CheapCloneStr,
129    Repetition: GenericRepetition<CustomIdent = S>,
130{
131    /// A single track sizing function
132    Single(TrackSizingFunction),
133    /// A `repeat()`
134    Repeat(Repetition),
135}
136
137impl<S, Repetition> GenericGridTemplateComponent<S, Repetition>
138where
139    S: CheapCloneStr,
140    Repetition: GenericRepetition<CustomIdent = S>,
141{
142    /// Whether the track definition is a auto-repeated fragment
143    pub fn is_auto_repetition(&self) -> bool {
144        match self {
145            Self::Single(_) => false,
146            Self::Repeat(repeat) => matches!(repeat.count(), RepetitionCount::AutoFit | RepetitionCount::AutoFill),
147        }
148    }
149}
150
151/// The set of styles required for a CSS Grid container
152pub trait GridContainerStyle: CoreStyle {
153    /// The type for a `repeat()` within a grid_template_rows or grid_template_columns
154    type Repetition<'a>: GenericRepetition<CustomIdent = Self::CustomIdent>
155    where
156        Self: 'a;
157
158    /// The type returned by grid_template_rows and grid_template_columns
159    type TemplateTrackList<'a>: Iterator<Item = GenericGridTemplateComponent<Self::CustomIdent, Self::Repetition<'a>>>
160        + ExactSizeIterator
161        + Clone
162    where
163        Self: 'a;
164
165    /// The type returned by grid_auto_rows and grid_auto_columns
166    type AutoTrackList<'a>: Iterator<Item = TrackSizingFunction> + ExactSizeIterator + Clone
167    where
168        Self: 'a;
169
170    /// The type returned by grid_template_row_names and grid_template_column_names
171    //IntoIterator<Item = &'a Self::LineNameSet<'a>>
172    type TemplateLineNames<'a>: TemplateLineNames<'a, Self::CustomIdent>
173    where
174        Self: 'a;
175
176    /// The type of custom identifiers used to identify named grid lines and areas
177    type GridTemplateAreas<'a>: IntoIterator<Item = GridTemplateArea<Self::CustomIdent>>
178    where
179        Self: 'a;
180
181    // FIXME: re-add default implemenations for grid_{template,auto}_{rows,columns} once the
182    // associated_type_defaults feature (https://github.com/rust-lang/rust/issues/29661) is stabilised.
183
184    /// Defines the track sizing functions (heights) of the grid rows
185    fn grid_template_rows(&self) -> Option<Self::TemplateTrackList<'_>>;
186    /// Defines the track sizing functions (widths) of the grid columns
187    fn grid_template_columns(&self) -> Option<Self::TemplateTrackList<'_>>;
188    /// Defines the size of implicitly created rows
189    fn grid_auto_rows(&self) -> Self::AutoTrackList<'_>;
190    /// Defined the size of implicitly created columns
191    fn grid_auto_columns(&self) -> Self::AutoTrackList<'_>;
192
193    /// Named grid areas
194    fn grid_template_areas(&self) -> Option<Self::GridTemplateAreas<'_>>;
195    /// The number of rows in the `grid-template-areas` template (0 if there is no template).
196    /// May be greater than the extent of the named areas due to unnamed (`.`) cells.
197    fn grid_template_area_row_count(&self) -> u16 {
198        self.grid_template_areas()
199            .map(|areas| areas.into_iter().map(|area| area.row_end.max(1) - 1).max().unwrap_or(0))
200            .unwrap_or(0)
201    }
202    /// The number of columns in the `grid-template-areas` template (0 if there is no template).
203    /// May be greater than the extent of the named areas due to unnamed (`.`) cells.
204    fn grid_template_area_column_count(&self) -> u16 {
205        self.grid_template_areas()
206            .map(|areas| areas.into_iter().map(|area| area.column_end.max(1) - 1).max().unwrap_or(0))
207            .unwrap_or(0)
208    }
209    /// Defines the line names for row lines
210    fn grid_template_column_names(&self) -> Option<Self::TemplateLineNames<'_>>;
211    /// Defines the size of implicitly created rows
212    fn grid_template_row_names(&self) -> Option<Self::TemplateLineNames<'_>>;
213
214    /// Controls how items get placed into the grid for auto-placed items
215    #[inline(always)]
216    fn grid_auto_flow(&self) -> GridAutoFlow {
217        Style::<Self::CustomIdent>::DEFAULT.grid_auto_flow
218    }
219
220    /// How large should the gaps between items in a grid or flex container be?
221    #[inline(always)]
222    fn gap(&self) -> Size<LengthPercentage> {
223        Style::<Self::CustomIdent>::DEFAULT.gap
224    }
225
226    // Alignment properties
227
228    /// How should content contained within this item be aligned in the cross/block axis
229    #[inline(always)]
230    fn align_content(&self) -> Option<AlignContent> {
231        Style::<Self::CustomIdent>::DEFAULT.align_content
232    }
233    /// How should contained within this item be aligned in the main/inline axis
234    #[inline(always)]
235    fn justify_content(&self) -> Option<JustifyContent> {
236        Style::<Self::CustomIdent>::DEFAULT.justify_content
237    }
238    /// How this node's children aligned in the cross/block axis?
239    #[inline(always)]
240    fn align_items(&self) -> Option<AlignItems> {
241        Style::<Self::CustomIdent>::DEFAULT.align_items
242    }
243    /// How this node's children should be aligned in the inline axis
244    #[inline(always)]
245    fn justify_items(&self) -> Option<AlignItems> {
246        Style::<Self::CustomIdent>::DEFAULT.justify_items
247    }
248
249    /// Get a grid item's row or column placement depending on the axis passed
250    #[inline(always)]
251    fn grid_template_tracks(&self, axis: AbsoluteAxis) -> Option<Self::TemplateTrackList<'_>> {
252        match axis {
253            AbsoluteAxis::Horizontal => self.grid_template_columns(),
254            AbsoluteAxis::Vertical => self.grid_template_rows(),
255        }
256    }
257
258    /// Get a grid container's align-content or justify-content alignment depending on the axis passed
259    #[inline(always)]
260    fn grid_align_content(&self, axis: AbstractAxis) -> AlignContent {
261        match axis {
262            AbstractAxis::Inline => self.justify_content().unwrap_or(AlignContent::STRETCH),
263            AbstractAxis::Block => self.align_content().unwrap_or(AlignContent::STRETCH),
264        }
265    }
266}
267
268/// The set of styles required for a CSS Grid item (child of a CSS Grid container)
269pub trait GridItemStyle: CoreStyle {
270    /// Defines which row in the grid the item should start and end at
271    #[inline(always)]
272    fn grid_row(&self) -> Line<GridPlacement<Self::CustomIdent>> {
273        Default::default()
274    }
275    /// Defines which column in the grid the item should start and end at
276    #[inline(always)]
277    fn grid_column(&self) -> Line<GridPlacement<Self::CustomIdent>> {
278        Default::default()
279    }
280
281    /// How this node should be aligned in the cross/block axis
282    /// Falls back to the parents [`AlignItems`] if not set
283    #[inline(always)]
284    fn align_self(&self) -> Option<AlignSelf> {
285        Style::<Self::CustomIdent>::DEFAULT.align_self
286    }
287    /// How this node should be aligned in the inline axis
288    /// Falls back to the parents [`super::JustifyItems`] if not set
289    #[inline(always)]
290    fn justify_self(&self) -> Option<AlignSelf> {
291        Style::<Self::CustomIdent>::DEFAULT.justify_self
292    }
293
294    /// Get a grid item's row or column placement depending on the axis passed
295    #[inline(always)]
296    fn grid_placement(&self, axis: AbsoluteAxis) -> Line<GridPlacement<Self::CustomIdent>> {
297        match axis {
298            AbsoluteAxis::Horizontal => self.grid_column(),
299            AbsoluteAxis::Vertical => self.grid_row(),
300        }
301    }
302}
303
304/// Controls whether grid items are placed row-wise or column-wise. And whether the sparse or dense packing algorithm is used.
305///
306/// The "dense" packing algorithm attempts to fill in holes earlier in the grid, if smaller items come up later. This may cause items to appear out-of-order, when doing so would fill in holes left by larger items.
307///
308/// Defaults to [`GridAutoFlow::Row`]
309///
310/// [MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/grid-auto-flow)
311#[derive(Copy, Clone, PartialEq, Eq, Debug, Default)]
312#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
313pub enum GridAutoFlow {
314    /// Items are placed by filling each row in turn, adding new rows as necessary
315    #[default]
316    Row,
317    /// Items are placed by filling each column in turn, adding new columns as necessary.
318    Column,
319    /// Combines `Row` with the dense packing algorithm.
320    RowDense,
321    /// Combines `Column` with the dense packing algorithm.
322    ColumnDense,
323}
324
325#[cfg(feature = "parse")]
326impl FromCss for GridAutoFlow {
327    fn from_css<'i>(parser: &mut Parser<'i, '_>) -> CssParseResult<'i, Self> {
328        let mut axis: Option<&'static str> = None;
329        let mut dense = false;
330
331        for _ in 0..2 {
332            if let Ok(ident) = parser.try_parse(|parser| parser.expect_ident_cloned()) {
333                match &*ident {
334                    "row" => {
335                        axis = Some("row");
336                    }
337                    "column" => {
338                        axis = Some("column");
339                    }
340                    "dense" => dense = true,
341                    _ => {
342                        return Err(parser.new_unexpected_token_error(Token::Ident(ident)));
343                    }
344                }
345            } else {
346                break;
347            }
348        }
349
350        match (axis, dense) {
351            (Some("row"), false) => Ok(Self::Row),
352            (Some("row") | None, true) => Ok(Self::RowDense),
353            (Some("column"), false) => Ok(Self::Column),
354            (Some("column"), true) => Ok(Self::ColumnDense),
355            (None, false) => {
356                let token = parser.next().cloned()?;
357                Err(parser.new_unexpected_token_error(token))
358            }
359            _ => unreachable!(),
360        }
361    }
362}
363#[cfg(feature = "parse")]
364from_str_from_css!(GridAutoFlow);
365
366impl GridAutoFlow {
367    /// Whether grid auto placement uses the sparse placement algorithm or the dense placement algorithm
368    /// See: <https://developer.mozilla.org/en-US/docs/Web/CSS/grid-auto-flow#values>
369    pub const fn is_dense(&self) -> bool {
370        match self {
371            Self::Row | Self::Column => false,
372            Self::RowDense | Self::ColumnDense => true,
373        }
374    }
375
376    /// Whether grid auto placement fills areas row-wise or column-wise
377    /// See: <https://developer.mozilla.org/en-US/docs/Web/CSS/grid-auto-flow#values>
378    pub const fn primary_axis(&self) -> AbsoluteAxis {
379        match self {
380            Self::Row | Self::RowDense => AbsoluteAxis::Horizontal,
381            Self::Column | Self::ColumnDense => AbsoluteAxis::Vertical,
382        }
383    }
384}
385
386/// A grid line placement specification which is generic over the coordinate system that it uses to define
387/// grid line positions.
388///
389/// `GenericGridPlacement<GridLine>` is aliased as GridPlacement and is exposed to users of Taffy to define styles.
390/// `GenericGridPlacement<OriginZeroLine>` is aliased as OriginZeroGridPlacement and is used internally for placement computations.
391#[derive(Copy, Clone, PartialEq, Eq, Debug)]
392#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
393pub enum GenericGridPlacement<LineType: GridCoordinate> {
394    /// Place item according to the auto-placement algorithm, and the parent's grid_auto_flow property
395    Auto,
396    /// Place item at specified line (column or row) index
397    Line(LineType),
398    /// Item should span specified number of tracks (columns or rows)
399    Span(u16),
400}
401
402/// A grid line placement using the normalized OriginZero coordinates to specify line positions.
403pub(crate) type OriginZeroGridPlacement = GenericGridPlacement<OriginZeroLine>;
404
405/// A grid line placement using CSS grid line coordinates to specify line positions. This uses the same coordinate
406/// system as the public `GridPlacement` type but doesn't support named lines (these are expected to have already
407/// been resolved by the time values of this type are constructed).
408pub(crate) type NonNamedGridPlacement = GenericGridPlacement<GridLine>;
409
410/// A grid line placement specification. Used for grid-[row/column]-[start/end]. Named tracks are not implemented.
411///
412/// Defaults to `GridPlacement::Auto`
413///
414/// [Specification](https://www.w3.org/TR/css3-grid-layout/#typedef-grid-row-start-grid-line)
415#[derive(Clone, PartialEq, Debug, Default)]
416#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
417pub enum GridPlacement<S: CheapCloneStr = DefaultCheapStr> {
418    /// Place item according to the auto-placement algorithm, and the parent's grid_auto_flow property
419    #[default]
420    Auto,
421    /// Place item at specified line (column or row) index
422    Line(GridLine),
423    /// Place item at specified named line (column or row)
424    NamedLine(S, i16),
425    /// Item should span specified number of tracks (columns or rows)
426    Span(u16),
427    /// Item should span until the nth line named `<name>`.
428    ///
429    /// If there are less than n lines named `<name>` in the specified direction then
430    /// all implicit lines will be counted.
431    NamedSpan(S, u16),
432}
433impl<S: CheapCloneStr> TaffyAuto for GridPlacement<S> {
434    const AUTO: Self = Self::Auto;
435}
436impl<S: CheapCloneStr> TaffyGridLine for GridPlacement<S> {
437    fn from_line_index(index: i16) -> Self {
438        GridPlacement::<S>::Line(GridLine::from(index))
439    }
440}
441impl<S: CheapCloneStr> TaffyGridLine for Line<GridPlacement<S>> {
442    fn from_line_index(index: i16) -> Self {
443        Line { start: GridPlacement::<S>::from_line_index(index), end: GridPlacement::<S>::Auto }
444    }
445}
446impl<S: CheapCloneStr> TaffyGridSpan for GridPlacement<S> {
447    fn from_span(span: u16) -> Self {
448        GridPlacement::<S>::Span(span)
449    }
450}
451impl<S: CheapCloneStr> TaffyGridSpan for Line<GridPlacement<S>> {
452    fn from_span(span: u16) -> Self {
453        Line { start: GridPlacement::<S>::from_span(span), end: GridPlacement::<S>::Auto }
454    }
455}
456
457#[cfg(feature = "parse")]
458/// Saturates an `i32` to the range representable by `i16`.
459fn saturating_i16(value: i32) -> i16 {
460    value.clamp(i16::MIN as i32, i16::MAX as i32) as i16
461}
462
463#[cfg(feature = "parse")]
464/// Saturates an `i32` to the range representable by `u16`.
465fn saturating_u16(value: i32) -> u16 {
466    value.clamp(u16::MIN as i32, u16::MAX as i32) as u16
467}
468
469#[cfg(feature = "parse")]
470impl<S: CheapCloneStr> FromCss for GridPlacement<S> {
471    fn from_css<'i>(parser: &mut Parser<'i, '_>) -> CssParseResult<'i, Self> {
472        let mut span = false;
473        let mut number = None;
474        let mut ident = None;
475
476        while !parser.is_exhausted() {
477            let token = parser.next()?.clone();
478            match &token {
479                Token::Ident(s) => match s.as_ref() {
480                    "auto" => {
481                        if span || number.is_some() || ident.is_some() {
482                            return Err(parser.new_unexpected_token_error(token));
483                        }
484                        parser.expect_exhausted()?;
485                        return Ok(Self::Auto);
486                    }
487                    "span" => {
488                        if span {
489                            return Err(parser.new_unexpected_token_error(token));
490                        }
491                        span = true;
492                    }
493                    other => {
494                        if ident.is_some() {
495                            return Err(parser.new_unexpected_token_error(token));
496                        }
497                        ident = Some(S::from(other));
498                    }
499                },
500                Token::Number { int_value: Some(value), .. } if *value != 0 => {
501                    if number.is_some() {
502                        return Err(parser.new_unexpected_token_error(token));
503                    }
504                    number = Some(*value);
505                }
506                _ => return Err(parser.new_unexpected_token_error(token)),
507            };
508        }
509
510        match (span, number, ident) {
511            (true, None, None) => Ok(Self::Span(0)),
512            (true, Some(number), None) => Ok(Self::Span(saturating_u16(number))),
513            (true, None, Some(ident)) => Ok(Self::NamedSpan(ident, 0)),
514            (true, Some(number), Some(ident)) => Ok(Self::NamedSpan(ident, saturating_u16(number))),
515            (false, Some(number), None) => Ok(Self::Line(GridLine::from(saturating_i16(number)))),
516            (false, Some(number), Some(ident)) => Ok(Self::NamedLine(ident, saturating_i16(number))),
517            (false, None, Some(ident)) => Ok(Self::NamedLine(ident, 0)),
518            (false, None, None) => Err(parser.new_error(cssparser::BasicParseErrorKind::EndOfInput)),
519        }
520    }
521}
522
523#[cfg(feature = "parse")]
524impl<S: CheapCloneStr> core::str::FromStr for GridPlacement<S> {
525    type Err = ParseError;
526    fn from_str(input: &str) -> Result<Self, Self::Err> {
527        parse_css_str_entirely(input)
528    }
529}
530
531impl<S: CheapCloneStr> GridPlacement<S> {
532    /// Apply a mapping function if the [`GridPlacement`] is a `Line`. Otherwise return `self` unmodified.
533    pub fn into_origin_zero_placement_ignoring_named(&self, explicit_track_count: u16) -> OriginZeroGridPlacement {
534        match self {
535            Self::Auto => OriginZeroGridPlacement::Auto,
536            // Spans are clamped to the maximum track limit
537            // https://www.w3.org/TR/css-grid-1/#overlarge-grids
538            Self::Span(span) => OriginZeroGridPlacement::Span(min(*span, MAX_GRID_TRACKS)),
539            // Grid line zero is an invalid index, so it gets treated as Auto
540            // See: https://developer.mozilla.org/en-US/docs/Web/CSS/grid-row-start#values
541            Self::Line(line) => match line.as_i16() {
542                0 => OriginZeroGridPlacement::Auto,
543                _ => OriginZeroGridPlacement::Line(line.into_origin_zero_line(explicit_track_count)),
544            },
545            Self::NamedLine(_, _) => OriginZeroGridPlacement::Auto,
546            Self::NamedSpan(_, _) => OriginZeroGridPlacement::Auto,
547        }
548    }
549}
550
551impl<S: CheapCloneStr> Line<GridPlacement<S>> {
552    /// Apply a mapping function if the [`GridPlacement`] is a `Line`. Otherwise return `self` unmodified.
553    pub fn into_origin_zero_ignoring_named(&self, explicit_track_count: u16) -> Line<OriginZeroGridPlacement> {
554        Line {
555            start: self.start.into_origin_zero_placement_ignoring_named(explicit_track_count),
556            end: self.end.into_origin_zero_placement_ignoring_named(explicit_track_count),
557        }
558    }
559}
560
561impl NonNamedGridPlacement {
562    /// Apply a mapping function if the [`GridPlacement`] is a `Track`. Otherwise return `self` unmodified.
563    pub fn into_origin_zero_placement(
564        &self,
565        explicit_track_count: u16,
566        // resolve_named: impl Fn(&str) -> Option<GridLine>
567    ) -> OriginZeroGridPlacement {
568        match self {
569            Self::Auto => OriginZeroGridPlacement::Auto,
570            // Spans are clamped to the maximum track limit
571            // https://www.w3.org/TR/css-grid-1/#overlarge-grids
572            Self::Span(span) => OriginZeroGridPlacement::Span(min(*span, MAX_GRID_TRACKS)),
573            // Grid line zero is an invalid index, so it gets treated as Auto
574            // See: https://developer.mozilla.org/en-US/docs/Web/CSS/grid-row-start#values
575            Self::Line(line) => match line.as_i16() {
576                0 => OriginZeroGridPlacement::Auto,
577                _ => OriginZeroGridPlacement::Line(line.into_origin_zero_line(explicit_track_count)),
578            },
579        }
580    }
581}
582
583impl<T: GridCoordinate> Line<GenericGridPlacement<T>> {
584    /// Resolves the span for an indefinite placement (a placement that does not consist of two `Track`s).
585    /// Panics if called on a definite placement
586    pub const fn indefinite_span(&self) -> u16 {
587        use GenericGridPlacement as GP;
588        match (self.start, self.end) {
589            (GP::Line(_), GP::Auto) => 1,
590            (GP::Auto, GP::Line(_)) => 1,
591            (GP::Auto, GP::Auto) => 1,
592            (GP::Line(_), GP::Span(span)) => span,
593            (GP::Span(span), GP::Line(_)) => span,
594            (GP::Span(span), GP::Auto) => span,
595            (GP::Auto, GP::Span(span)) => span,
596            (GP::Span(span), GP::Span(_)) => span,
597            (GP::Line(_), GP::Line(_)) => panic!("indefinite_span should only be called on indefinite grid tracks"),
598        }
599    }
600}
601
602impl<S: CheapCloneStr> Line<GridPlacement<S>> {
603    #[inline]
604    /// Whether the track position is definite in this axis (or the item will need auto placement)
605    /// The track position is definite if least one of the start and end positions is a NON-ZERO track index
606    /// (0 is an invalid line in GridLine coordinates, and falls back to "auto" which is indefinite)
607    pub fn is_definite(&self) -> bool {
608        match (&self.start, &self.end) {
609            (GridPlacement::Line(line), _) if line.as_i16() != 0 => true,
610            (_, GridPlacement::Line(line)) if line.as_i16() != 0 => true,
611            (GridPlacement::NamedLine(_, _), _) => true,
612            (_, GridPlacement::NamedLine(_, _)) => true,
613            _ => false,
614        }
615    }
616}
617
618impl Line<NonNamedGridPlacement> {
619    #[inline]
620    /// Whether the track position is definite in this axis (or the item will need auto placement)
621    /// The track position is definite if least one of the start and end positions is a NON-ZERO track index
622    /// (0 is an invalid line in GridLine coordinates, and falls back to "auto" which is indefinite)
623    pub fn is_definite(&self) -> bool {
624        match (&self.start, &self.end) {
625            (GenericGridPlacement::Line(line), _) if line.as_i16() != 0 => true,
626            (_, GenericGridPlacement::Line(line)) if line.as_i16() != 0 => true,
627            _ => false,
628        }
629    }
630
631    /// Apply a mapping function if the [`GridPlacement`] is a `Track`. Otherwise return `self` unmodified.
632    pub fn into_origin_zero(&self, explicit_track_count: u16) -> Line<OriginZeroGridPlacement> {
633        Line {
634            start: self.start.into_origin_zero_placement(explicit_track_count),
635            end: self.end.into_origin_zero_placement(explicit_track_count),
636        }
637    }
638}
639
640impl Line<OriginZeroGridPlacement> {
641    #[inline]
642    /// Whether the track position is definite in this axis (or the item will need auto placement)
643    /// The track position is definite if least one of the start and end positions is a track index
644    pub const fn is_definite(&self) -> bool {
645        matches!((self.start, self.end), (GenericGridPlacement::Line(_), _) | (_, GenericGridPlacement::Line(_)))
646    }
647
648    /// If at least one of the of the start and end positions is a track index then the other end can be resolved
649    /// into a track index purely based on the information contained with the placement specification
650    pub fn resolve_definite_grid_lines(&self) -> Line<OriginZeroLine> {
651        use OriginZeroGridPlacement as GP;
652        match (self.start, self.end) {
653            (GP::Line(line1), GP::Line(line2)) => {
654                if line1 == line2 {
655                    Line { start: line1, end: line1 + 1 }
656                } else {
657                    Line { start: min(line1, line2), end: max(line1, line2) }
658                }
659            }
660            (GP::Line(line), GP::Span(span)) => Line { start: line, end: line + span },
661            (GP::Line(line), GP::Auto) => Line { start: line, end: line + 1 },
662            (GP::Span(span), GP::Line(line)) => Line { start: line - span, end: line },
663            (GP::Auto, GP::Line(line)) => Line { start: line - 1, end: line },
664            _ => panic!("resolve_definite_grid_tracks should only be called on definite grid tracks"),
665        }
666    }
667
668    /// For absolutely positioned items:
669    ///   - Tracks resolve to definite tracks
670    ///   - For Spans:
671    ///      - If the other position is a Track, they resolve to a definite track relative to the other track
672    ///      - Else resolve to None
673    ///   - Auto resolves to None
674    ///
675    /// When finally positioning the item, a value of None means that the item's grid area is bounded by the grid
676    /// container's border box on that side.
677    pub fn resolve_absolutely_positioned_grid_tracks(&self) -> Line<Option<OriginZeroLine>> {
678        use OriginZeroGridPlacement as GP;
679        match (self.start, self.end) {
680            (GP::Line(track1), GP::Line(track2)) => {
681                if track1 == track2 {
682                    Line { start: Some(track1), end: Some(track1 + 1) }
683                } else {
684                    Line { start: Some(min(track1, track2)), end: Some(max(track1, track2)) }
685                }
686            }
687            (GP::Line(track), GP::Span(span)) => Line { start: Some(track), end: Some(track + span) },
688            (GP::Line(track), GP::Auto) => Line { start: Some(track), end: None },
689            (GP::Span(span), GP::Line(track)) => Line { start: Some(track - span), end: Some(track) },
690            (GP::Auto, GP::Line(track)) => Line { start: None, end: Some(track) },
691            _ => Line { start: None, end: None },
692        }
693    }
694
695    /// If neither of the start and end positions is a track index then the other end can be resolved
696    /// into a track index if a definite start position is supplied externally
697    pub fn resolve_indefinite_grid_tracks(&self, start: OriginZeroLine) -> Line<OriginZeroLine> {
698        use OriginZeroGridPlacement as GP;
699        match (self.start, self.end) {
700            (GP::Auto, GP::Auto) => Line { start, end: start + 1 },
701            (GP::Span(span), GP::Auto) => Line { start, end: start + span },
702            (GP::Auto, GP::Span(span)) => Line { start, end: start + span },
703            (GP::Span(span), GP::Span(_)) => Line { start, end: start + span },
704            _ => panic!("resolve_indefinite_grid_tracks should only be called on indefinite grid tracks"),
705        }
706    }
707}
708
709/// Represents the start and end points of a GridItem within a given axis
710impl<S: CheapCloneStr> Default for Line<GridPlacement<S>> {
711    fn default() -> Self {
712        Line { start: GridPlacement::<S>::Auto, end: GridPlacement::<S>::Auto }
713    }
714}
715
716/// Maximum track sizing function
717///
718/// Specifies the maximum size of a grid track. A grid track will automatically size between it's minimum and maximum size based
719/// on the size of it's contents, the amount of available space, and the sizing constraint the grid is being size under.
720/// See <https://developer.mozilla.org/en-US/docs/Web/CSS/grid-template-columns>
721#[derive(Copy, Clone, PartialEq, Debug)]
722#[cfg_attr(feature = "serde", derive(Serialize))]
723pub struct MaxTrackSizingFunction(pub(crate) CompactLength);
724impl TaffyZero for MaxTrackSizingFunction {
725    const ZERO: Self = Self(CompactLength::ZERO);
726}
727impl TaffyAuto for MaxTrackSizingFunction {
728    const AUTO: Self = Self(CompactLength::AUTO);
729}
730impl TaffyMinContent for MaxTrackSizingFunction {
731    const MIN_CONTENT: Self = Self(CompactLength::MIN_CONTENT);
732}
733impl TaffyMaxContent for MaxTrackSizingFunction {
734    const MAX_CONTENT: Self = Self(CompactLength::MAX_CONTENT);
735}
736impl FromLength for MaxTrackSizingFunction {
737    fn from_length<Input: Into<f64> + Copy>(value: Input) -> Self {
738        Self::length(value.into() as f32)
739    }
740}
741impl FromPercent for MaxTrackSizingFunction {
742    fn from_percent<Input: Into<f64> + Copy>(value: Input) -> Self {
743        Self::percent(value.into() as f32)
744    }
745}
746impl TaffyFitContent for MaxTrackSizingFunction {
747    fn fit_content(argument: LengthPercentage) -> Self {
748        Self(CompactLength::fit_content(argument))
749    }
750}
751impl FromFr for MaxTrackSizingFunction {
752    fn from_fr<Input: Into<f64> + Copy>(value: Input) -> Self {
753        Self::fr(value.into() as f32)
754    }
755}
756impl From<LengthPercentage> for MaxTrackSizingFunction {
757    fn from(input: LengthPercentage) -> Self {
758        Self(input.0)
759    }
760}
761impl From<LengthPercentageAuto> for MaxTrackSizingFunction {
762    fn from(input: LengthPercentageAuto) -> Self {
763        Self(input.0)
764    }
765}
766impl From<Dimension> for MaxTrackSizingFunction {
767    fn from(input: Dimension) -> Self {
768        Self(input.0)
769    }
770}
771impl From<MinTrackSizingFunction> for MaxTrackSizingFunction {
772    fn from(input: MinTrackSizingFunction) -> Self {
773        Self(input.0)
774    }
775}
776
777#[cfg(feature = "parse")]
778impl FromCss for MaxTrackSizingFunction {
779    fn from_css<'i>(parser: &mut Parser<'i, '_>) -> CssParseResult<'i, Self> {
780        let token = parser.next()?.clone();
781        match token {
782            Token::Percentage { unit_value, .. } => Ok(Self::percent(unit_value)),
783            Token::Dimension { unit, value, .. } if unit == "px" => Ok(Self::length(value)),
784            Token::Dimension { unit, value, .. } if unit == "fr" && value.is_sign_positive() => Ok(Self::fr(value)),
785            Token::Ident(ref ident) => match ident.as_ref() {
786                "auto" => Ok(Self::auto()),
787                "min-content" => Ok(Self::min_content()),
788                "max-content" => Ok(Self::max_content()),
789                _ => Err(parser.new_unexpected_token_error(token))?,
790            },
791            Token::Function(ref name) if name.as_ref() == "fit-content" => parser.parse_nested_block(|parser| {
792                let token = parser.next()?.clone();
793                match token {
794                    Token::Percentage { unit_value, .. } => Ok(Self::fit_content_percent(unit_value)),
795                    Token::Dimension { unit, value, .. } if unit == "px" => Ok(Self::fit_content_px(value)),
796                    token => Err(parser.new_unexpected_token_error(token))?,
797                }
798            }),
799            token => Err(parser.new_unexpected_token_error(token))?,
800        }
801    }
802}
803
804#[cfg(feature = "parse")]
805from_str_from_css!(MaxTrackSizingFunction);
806
807#[cfg(feature = "serde")]
808impl<'de> serde::Deserialize<'de> for MaxTrackSizingFunction {
809    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
810    where
811        D: serde::Deserializer<'de>,
812    {
813        let inner = CompactLength::deserialize(deserializer)?;
814        // Note: validation intentionally excludes the CALC_TAG as deserializing calc() values is not supported
815        if matches!(
816            inner.tag(),
817            CompactLength::LENGTH_TAG
818                | CompactLength::PERCENT_TAG
819                | CompactLength::AUTO_TAG
820                | CompactLength::MIN_CONTENT_TAG
821                | CompactLength::MAX_CONTENT_TAG
822                | CompactLength::FIT_CONTENT_PX_TAG
823                | CompactLength::FIT_CONTENT_PERCENT_TAG
824                | CompactLength::FR_TAG
825        ) {
826            Ok(Self(inner))
827        } else {
828            Err(serde::de::Error::custom("Invalid tag"))
829        }
830    }
831}
832
833impl MaxTrackSizingFunction {
834    /// An absolute length in some abstract units. Users of Taffy may define what they correspond
835    /// to in their application (pixels, logical pixels, mm, etc) as they see fit.
836    #[inline(always)]
837    pub const fn length(val: f32) -> Self {
838        Self(CompactLength::length(val))
839    }
840
841    /// A percentage length relative to the size of the containing block.
842    ///
843    /// **NOTE: percentages are represented as a f32 value in the range [0.0, 1.0] NOT the range [0.0, 100.0]**
844    #[inline(always)]
845    pub const fn percent(val: f32) -> Self {
846        Self(CompactLength::percent(val))
847    }
848
849    /// The dimension should be automatically computed according to algorithm-specific rules
850    /// regarding the default size of boxes.
851    #[inline(always)]
852    pub const fn auto() -> Self {
853        Self(CompactLength::auto())
854    }
855
856    /// The size should be the "min-content" size.
857    /// This is the smallest size that can fit the item's contents with ALL soft line-wrapping opportunities taken
858    #[inline(always)]
859    pub const fn min_content() -> Self {
860        Self(CompactLength::min_content())
861    }
862
863    /// The size should be the "max-content" size.
864    /// This is the smallest size that can fit the item's contents with NO soft line-wrapping opportunities taken
865    #[inline(always)]
866    pub const fn max_content() -> Self {
867        Self(CompactLength::max_content())
868    }
869
870    /// The size should be computed according to the "fit content" formula:
871    ///    `max(min_content, min(max_content, limit))`
872    /// where:
873    ///    - `min_content` is the [min-content](Self::min_content) size
874    ///    - `max_content` is the [max-content](Self::max_content) size
875    ///    - `limit` is a LENGTH value passed to this function
876    ///
877    /// The effect of this is that the item takes the size of `limit` clamped
878    /// by the min-content and max-content sizes.
879    #[inline(always)]
880    pub const fn fit_content_px(limit: f32) -> Self {
881        Self(CompactLength::fit_content_px(limit))
882    }
883
884    /// The size should be computed according to the "fit content" formula:
885    ///    `max(min_content, min(max_content, limit))`
886    /// where:
887    ///    - `min_content` is the [min-content](Self::min_content) size
888    ///    - `max_content` is the [max-content](Self::max_content) size
889    ///    - `limit` is a PERCENTAGE value passed to this function
890    ///
891    /// The effect of this is that the item takes the size of `limit` clamped
892    /// by the min-content and max-content sizes.
893    #[inline(always)]
894    pub const fn fit_content_percent(limit: f32) -> Self {
895        Self(CompactLength::fit_content_percent(limit))
896    }
897
898    /// The dimension as a fraction of the total available grid space (`fr` units in CSS)
899    /// Specified value is the numerator of the fraction. Denominator is the sum of all fraction specified in that grid dimension
900    /// Spec: <https://www.w3.org/TR/css3-grid-layout/#fr-unit>
901    #[inline(always)]
902    pub const fn fr(val: f32) -> Self {
903        Self(CompactLength::fr(val))
904    }
905
906    /// A `calc()` value. The value passed here is treated as an opaque handle to
907    /// the actual calc representation and may be a pointer, index, etc.
908    ///
909    /// The low 3 bits are used as a tag value and will be returned as 0.
910    #[inline]
911    #[cfg(feature = "calc")]
912    pub fn calc(ptr: *const ()) -> Self {
913        Self(CompactLength::calc(ptr))
914    }
915
916    /// Create a LengthPercentageAuto from a raw `CompactLength`.
917    /// # Safety
918    /// CompactLength must represent a valid variant for LengthPercentageAuto
919    #[allow(unsafe_code)]
920    pub unsafe fn from_raw(val: CompactLength) -> Self {
921        Self(val)
922    }
923
924    /// Get the underlying `CompactLength` representation of the value
925    pub fn into_raw(self) -> CompactLength {
926        self.0
927    }
928
929    /// Returns true if the max track sizing function is `MinContent`, `MaxContent`, `FitContent` or `Auto`, else false.
930    #[inline(always)]
931    pub fn is_intrinsic(&self) -> bool {
932        self.0.is_intrinsic()
933    }
934
935    /// Returns true if the max track sizing function is `MaxContent`, `FitContent` or `Auto` else false.
936    /// "In all cases, treat auto and fit-content() as max-content, except where specified otherwise for fit-content()."
937    /// See: <https://www.w3.org/TR/css-grid-1/#algo-terms>
938    #[inline(always)]
939    pub fn is_max_content_alike(&self) -> bool {
940        self.0.is_max_content_alike()
941    }
942
943    /// Returns true if the an Fr value, else false.
944    #[inline(always)]
945    pub fn is_fr(&self) -> bool {
946        self.0.is_fr()
947    }
948
949    /// Returns true if the is `Auto`, else false.
950    #[inline(always)]
951    pub fn is_auto(&self) -> bool {
952        self.0.is_auto()
953    }
954
955    /// Returns true if value is MinContent
956    #[inline(always)]
957    pub fn is_min_content(&self) -> bool {
958        self.0.is_min_content()
959    }
960
961    /// Returns true if value is MaxContent
962    #[inline(always)]
963    pub fn is_max_content(&self) -> bool {
964        self.0.is_max_content()
965    }
966
967    /// Returns true if value is FitContent(...)
968    #[inline(always)]
969    pub fn is_fit_content(&self) -> bool {
970        self.0.is_fit_content()
971    }
972
973    /// Returns true if value is MaxContent or FitContent(...)
974    #[inline(always)]
975    pub fn is_max_or_fit_content(&self) -> bool {
976        self.0.is_max_or_fit_content()
977    }
978
979    /// Returns whether the value can be resolved using `Self::definite_value`
980    #[inline(always)]
981    pub fn has_definite_value(self, parent_size: Option<f32>) -> bool {
982        match self.0.tag() {
983            CompactLength::LENGTH_TAG => true,
984            CompactLength::PERCENT_TAG => parent_size.is_some(),
985            #[cfg(feature = "calc")]
986            _ if self.0.is_calc() => parent_size.is_some(),
987            _ => false,
988        }
989    }
990
991    /// Returns fixed point values directly. Attempts to resolve percentage values against
992    /// the passed available_space and returns if this results in a concrete value (which it
993    /// will if the available_space is `Some`). Otherwise returns None.
994    #[inline(always)]
995    pub fn definite_value(
996        self,
997        parent_size: Option<f32>,
998        calc_resolver: impl Fn(*const (), f32) -> f32,
999    ) -> Option<f32> {
1000        match self.0.tag() {
1001            CompactLength::LENGTH_TAG => Some(self.0.value()),
1002            CompactLength::PERCENT_TAG => parent_size.map(|size| self.0.value() * size),
1003            #[cfg(feature = "calc")]
1004            _ if self.0.is_calc() => parent_size.map(|size| calc_resolver(self.0.calc_value(), size)),
1005            _ => None,
1006        }
1007    }
1008
1009    /// Resolve the maximum size of the track as defined by either:
1010    ///     - A fixed track sizing function
1011    ///     - A percentage track sizing function (with definite available space)
1012    ///     - A fit-content sizing function with fixed argument
1013    ///     - A fit-content sizing function with percentage argument (with definite available space)
1014    /// All other kinds of track sizing function return None.
1015    #[inline(always)]
1016    pub fn definite_limit(
1017        self,
1018        parent_size: Option<f32>,
1019        calc_resolver: impl Fn(*const (), f32) -> f32,
1020    ) -> Option<f32> {
1021        match self.0.tag() {
1022            CompactLength::FIT_CONTENT_PX_TAG => Some(self.0.value()),
1023            CompactLength::FIT_CONTENT_PERCENT_TAG => parent_size.map(|size| self.0.value() * size),
1024            _ => self.definite_value(parent_size, calc_resolver),
1025        }
1026    }
1027
1028    /// Resolve percentage values against the passed parent_size, returning Some(value)
1029    /// Non-percentage values always return None.
1030    #[inline(always)]
1031    pub fn resolved_percentage_size(
1032        self,
1033        parent_size: f32,
1034        calc_resolver: impl Fn(*const (), f32) -> f32,
1035    ) -> Option<f32> {
1036        self.0.resolved_percentage_size(parent_size, calc_resolver)
1037    }
1038
1039    /// Whether the track sizing functions depends on the size of the parent node
1040    #[inline(always)]
1041    pub fn uses_percentage(self) -> bool {
1042        self.0.uses_percentage()
1043    }
1044}
1045
1046/// Minimum track sizing function
1047///
1048/// Specifies the minimum size of a grid track. A grid track will automatically size between it's minimum and maximum size based
1049/// on the size of it's contents, the amount of available space, and the sizing constraint the grid is being size under.
1050/// See <https://developer.mozilla.org/en-US/docs/Web/CSS/grid-template-columns>
1051#[derive(Copy, Clone, PartialEq, Debug)]
1052#[cfg_attr(feature = "serde", derive(Serialize))]
1053pub struct MinTrackSizingFunction(pub(crate) CompactLength);
1054impl TaffyZero for MinTrackSizingFunction {
1055    const ZERO: Self = Self(CompactLength::ZERO);
1056}
1057impl TaffyAuto for MinTrackSizingFunction {
1058    const AUTO: Self = Self(CompactLength::AUTO);
1059}
1060impl TaffyMinContent for MinTrackSizingFunction {
1061    const MIN_CONTENT: Self = Self(CompactLength::MIN_CONTENT);
1062}
1063impl TaffyMaxContent for MinTrackSizingFunction {
1064    const MAX_CONTENT: Self = Self(CompactLength::MAX_CONTENT);
1065}
1066impl FromLength for MinTrackSizingFunction {
1067    fn from_length<Input: Into<f64> + Copy>(value: Input) -> Self {
1068        Self::length(value.into() as f32)
1069    }
1070}
1071impl FromPercent for MinTrackSizingFunction {
1072    fn from_percent<Input: Into<f64> + Copy>(value: Input) -> Self {
1073        Self::percent(value.into() as f32)
1074    }
1075}
1076impl From<LengthPercentage> for MinTrackSizingFunction {
1077    fn from(input: LengthPercentage) -> Self {
1078        Self(input.0)
1079    }
1080}
1081impl From<LengthPercentageAuto> for MinTrackSizingFunction {
1082    fn from(input: LengthPercentageAuto) -> Self {
1083        Self(input.0)
1084    }
1085}
1086impl From<Dimension> for MinTrackSizingFunction {
1087    fn from(input: Dimension) -> Self {
1088        Self(input.0)
1089    }
1090}
1091
1092impl From<MaxTrackSizingFunction> for MinTrackSizingFunction {
1093    fn from(input: MaxTrackSizingFunction) -> Self {
1094        if input.is_fr() || input.is_fit_content() {
1095            return Self::auto();
1096        }
1097        Self(input.0)
1098    }
1099}
1100
1101#[cfg(feature = "parse")]
1102impl FromCss for MinTrackSizingFunction {
1103    fn from_css<'i>(parser: &mut Parser<'i, '_>) -> CssParseResult<'i, Self> {
1104        let token = parser.next()?.clone();
1105        match token {
1106            Token::Percentage { unit_value, .. } => Ok(Self::percent(unit_value)),
1107            Token::Dimension { unit, value, .. } if unit == "px" => Ok(Self::length(value)),
1108            Token::Ident(ref ident) => match ident.as_ref() {
1109                "auto" => Ok(Self::auto()),
1110                "min-content" => Ok(Self::min_content()),
1111                "max-content" => Ok(Self::max_content()),
1112                _ => Err(parser.new_unexpected_token_error(token))?,
1113            },
1114            token => Err(parser.new_unexpected_token_error(token))?,
1115        }
1116    }
1117}
1118
1119#[cfg(feature = "parse")]
1120from_str_from_css!(MinTrackSizingFunction);
1121
1122#[cfg(feature = "serde")]
1123impl<'de> serde::Deserialize<'de> for MinTrackSizingFunction {
1124    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1125    where
1126        D: serde::Deserializer<'de>,
1127    {
1128        let inner = CompactLength::deserialize(deserializer)?;
1129        // Note: validation intentionally excludes the CALC_TAG as deserializing calc() values is not supported
1130        if matches!(
1131            inner.tag(),
1132            CompactLength::LENGTH_TAG
1133                | CompactLength::PERCENT_TAG
1134                | CompactLength::AUTO_TAG
1135                | CompactLength::MIN_CONTENT_TAG
1136                | CompactLength::MAX_CONTENT_TAG
1137                | CompactLength::FIT_CONTENT_PX_TAG
1138                | CompactLength::FIT_CONTENT_PERCENT_TAG
1139        ) {
1140            Ok(Self(inner))
1141        } else {
1142            Err(serde::de::Error::custom("Invalid tag"))
1143        }
1144    }
1145}
1146
1147impl MinTrackSizingFunction {
1148    /// An absolute length in some abstract units. Users of Taffy may define what they correspond
1149    /// to in their application (pixels, logical pixels, mm, etc) as they see fit.
1150    #[inline(always)]
1151    pub const fn length(val: f32) -> Self {
1152        Self(CompactLength::length(val))
1153    }
1154
1155    /// A percentage length relative to the size of the containing block.
1156    ///
1157    /// **NOTE: percentages are represented as a f32 value in the range [0.0, 1.0] NOT the range [0.0, 100.0]**
1158    #[inline(always)]
1159    pub const fn percent(val: f32) -> Self {
1160        Self(CompactLength::percent(val))
1161    }
1162
1163    /// The dimension should be automatically computed according to algorithm-specific rules
1164    /// regarding the default size of boxes.
1165    #[inline(always)]
1166    pub const fn auto() -> Self {
1167        Self(CompactLength::auto())
1168    }
1169
1170    /// The size should be the "min-content" size.
1171    /// This is the smallest size that can fit the item's contents with ALL soft line-wrapping opportunities taken
1172    #[inline(always)]
1173    pub const fn min_content() -> Self {
1174        Self(CompactLength::min_content())
1175    }
1176
1177    /// The size should be the "max-content" size.
1178    /// This is the smallest size that can fit the item's contents with NO soft line-wrapping opportunities taken
1179    #[inline(always)]
1180    pub const fn max_content() -> Self {
1181        Self(CompactLength::max_content())
1182    }
1183
1184    /// A `calc()` value. The value passed here is treated as an opaque handle to
1185    /// the actual calc representation and may be a pointer, index, etc.
1186    ///
1187    /// The low 3 bits are used as a tag value and will be returned as 0.
1188    #[inline]
1189    #[cfg(feature = "calc")]
1190    pub fn calc(ptr: *const ()) -> Self {
1191        Self(CompactLength::calc(ptr))
1192    }
1193
1194    /// Create a LengthPercentageAuto from a raw `CompactLength`.
1195    /// # Safety
1196    /// CompactLength must represent a valid variant for LengthPercentageAuto
1197    #[allow(unsafe_code)]
1198    pub unsafe fn from_raw(val: CompactLength) -> Self {
1199        Self(val)
1200    }
1201
1202    /// Get the underlying `CompactLength` representation of the value
1203    pub fn into_raw(self) -> CompactLength {
1204        self.0
1205    }
1206
1207    /// Returns true if the min track sizing function is `MinContent`, `MaxContent` or `Auto`, else false.
1208    #[inline(always)]
1209    pub fn is_intrinsic(&self) -> bool {
1210        self.0.is_intrinsic()
1211    }
1212
1213    /// Returns true if the min track sizing function is `MinContent` or `MaxContent`, else false.
1214    #[inline(always)]
1215    pub fn is_min_or_max_content(&self) -> bool {
1216        self.0.is_min_or_max_content()
1217    }
1218
1219    /// Returns true if the value is an fr value
1220    #[inline(always)]
1221    pub fn is_fr(&self) -> bool {
1222        self.0.is_fr()
1223    }
1224
1225    /// Returns true if the is `Auto`, else false.
1226    #[inline(always)]
1227    pub fn is_auto(&self) -> bool {
1228        self.0.is_auto()
1229    }
1230
1231    /// Returns true if value is MinContent
1232    #[inline(always)]
1233    pub fn is_min_content(&self) -> bool {
1234        self.0.is_min_content()
1235    }
1236
1237    /// Returns true if value is MaxContent
1238    #[inline(always)]
1239    pub fn is_max_content(&self) -> bool {
1240        self.0.is_max_content()
1241    }
1242
1243    /// Returns fixed point values directly. Attempts to resolve percentage values against
1244    /// the passed available_space and returns if this results in a concrete value (which it
1245    /// will if the available_space is `Some`). Otherwise returns `None`.
1246    #[inline(always)]
1247    pub fn definite_value(
1248        self,
1249        parent_size: Option<f32>,
1250        calc_resolver: impl Fn(*const (), f32) -> f32,
1251    ) -> Option<f32> {
1252        match self.0.tag() {
1253            CompactLength::LENGTH_TAG => Some(self.0.value()),
1254            CompactLength::PERCENT_TAG => parent_size.map(|size| self.0.value() * size),
1255            #[cfg(feature = "calc")]
1256            _ if self.0.is_calc() => parent_size.map(|size| calc_resolver(self.0.calc_value(), size)),
1257            _ => None,
1258        }
1259    }
1260
1261    /// Resolve percentage values against the passed parent_size, returning Some(value)
1262    /// Non-percentage values always return None.
1263    #[inline(always)]
1264    pub fn resolved_percentage_size(
1265        self,
1266        parent_size: f32,
1267        calc_resolver: impl Fn(*const (), f32) -> f32,
1268    ) -> Option<f32> {
1269        self.0.resolved_percentage_size(parent_size, calc_resolver)
1270    }
1271
1272    /// Whether the track sizing functions depends on the size of the parent node
1273    #[inline(always)]
1274    pub fn uses_percentage(self) -> bool {
1275        #[cfg(feature = "calc")]
1276        {
1277            matches!(self.0.tag(), CompactLength::PERCENT_TAG) || self.0.is_calc()
1278        }
1279        #[cfg(not(feature = "calc"))]
1280        {
1281            matches!(self.0.tag(), CompactLength::PERCENT_TAG)
1282        }
1283    }
1284}
1285
1286/// The sizing function for a grid track (row/column)
1287///
1288/// May either be a MinMax variant which specifies separate values for the min-/max- track sizing functions
1289/// or a scalar value which applies to both track sizing functions.
1290pub type TrackSizingFunction = MinMax<MinTrackSizingFunction, MaxTrackSizingFunction>;
1291impl TrackSizingFunction {
1292    /// Extract the min track sizing function
1293    pub fn min_sizing_function(&self) -> MinTrackSizingFunction {
1294        self.min
1295    }
1296    /// Extract the max track sizing function
1297    pub fn max_sizing_function(&self) -> MaxTrackSizingFunction {
1298        self.max
1299    }
1300    /// Determine whether at least one of the components ("min" and "max") are fixed sizing function
1301    pub fn has_fixed_component(&self) -> bool {
1302        self.min.0.is_length_or_percentage() || self.max.0.is_length_or_percentage()
1303    }
1304}
1305impl TaffyAuto for TrackSizingFunction {
1306    const AUTO: Self = Self { min: MinTrackSizingFunction::AUTO, max: MaxTrackSizingFunction::AUTO };
1307}
1308impl TaffyMinContent for TrackSizingFunction {
1309    const MIN_CONTENT: Self =
1310        Self { min: MinTrackSizingFunction::MIN_CONTENT, max: MaxTrackSizingFunction::MIN_CONTENT };
1311}
1312impl TaffyMaxContent for TrackSizingFunction {
1313    const MAX_CONTENT: Self =
1314        Self { min: MinTrackSizingFunction::MAX_CONTENT, max: MaxTrackSizingFunction::MAX_CONTENT };
1315}
1316impl TaffyFitContent for TrackSizingFunction {
1317    fn fit_content(argument: LengthPercentage) -> Self {
1318        Self { min: MinTrackSizingFunction::AUTO, max: MaxTrackSizingFunction::fit_content(argument) }
1319    }
1320}
1321impl TaffyZero for TrackSizingFunction {
1322    const ZERO: Self = Self { min: MinTrackSizingFunction::ZERO, max: MaxTrackSizingFunction::ZERO };
1323}
1324impl FromLength for TrackSizingFunction {
1325    fn from_length<Input: Into<f64> + Copy>(value: Input) -> Self {
1326        Self { min: MinTrackSizingFunction::from_length(value), max: MaxTrackSizingFunction::from_length(value) }
1327    }
1328}
1329impl FromPercent for TrackSizingFunction {
1330    fn from_percent<Input: Into<f64> + Copy>(percent: Input) -> Self {
1331        Self { min: MinTrackSizingFunction::from_percent(percent), max: MaxTrackSizingFunction::from_percent(percent) }
1332    }
1333}
1334impl FromFr for TrackSizingFunction {
1335    fn from_fr<Input: Into<f64> + Copy>(flex: Input) -> Self {
1336        Self { min: MinTrackSizingFunction::AUTO, max: MaxTrackSizingFunction::from_fr(flex) }
1337    }
1338}
1339impl From<LengthPercentage> for TrackSizingFunction {
1340    fn from(input: LengthPercentage) -> Self {
1341        Self { min: input.into(), max: input.into() }
1342    }
1343}
1344impl From<LengthPercentageAuto> for TrackSizingFunction {
1345    fn from(input: LengthPercentageAuto) -> Self {
1346        Self { min: input.into(), max: input.into() }
1347    }
1348}
1349impl From<Dimension> for TrackSizingFunction {
1350    fn from(input: Dimension) -> Self {
1351        Self { min: input.into(), max: input.into() }
1352    }
1353}
1354
1355#[cfg(feature = "parse")]
1356impl FromCss for TrackSizingFunction {
1357    fn from_css<'i>(parser: &mut Parser<'i, '_>) -> CssParseResult<'i, Self> {
1358        // Try to parse a minmax() function
1359        if let Ok(value) = parser.try_parse(|parser| {
1360            parser.expect_function_matching("minmax")?;
1361            parser.parse_nested_block(|parser| {
1362                let min = MinTrackSizingFunction::from_css(parser)?;
1363                parser.expect_comma()?;
1364                let max = MaxTrackSizingFunction::from_css(parser)?;
1365
1366                Ok(Self { min, max })
1367            })
1368        }) {
1369            return Ok(value);
1370        }
1371
1372        // Else parse a max track sizing function
1373        let max = MaxTrackSizingFunction::from_css(parser)?;
1374        let min = max.into();
1375        Ok(Self { min, max })
1376    }
1377}
1378
1379#[cfg(feature = "parse")]
1380from_str_from_css!(TrackSizingFunction);
1381
1382/// The first argument to a repeated track definition. This type represents the type of automatic repetition to perform.
1383///
1384/// See <https://www.w3.org/TR/css-grid-1/#auto-repeat> for an explanation of how auto-repeated track definitions work
1385/// and the difference between AutoFit and AutoFill.
1386#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1387#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1388pub enum RepetitionCount {
1389    /// Auto-repeating tracks should be generated to fit the container
1390    /// See: <https://developer.mozilla.org/en-US/docs/Web/CSS/repeat#auto-fill>
1391    AutoFill,
1392    /// Auto-repeating tracks should be generated to fit the container
1393    /// See: <https://developer.mozilla.org/en-US/docs/Web/CSS/repeat#auto-fit>
1394    AutoFit,
1395    /// The specified tracks should be repeated exacts N times
1396    Count(u16),
1397}
1398impl From<u16> for RepetitionCount {
1399    fn from(value: u16) -> Self {
1400        Self::Count(value)
1401    }
1402}
1403
1404/// Error returned when trying to convert a string to a GridTrackRepetition and that string is not
1405/// either "auto-fit" or "auto-fill"
1406#[derive(Debug)]
1407pub struct InvalidStringRepetitionValue;
1408#[cfg(feature = "std")]
1409impl std::error::Error for InvalidStringRepetitionValue {}
1410impl core::fmt::Display for InvalidStringRepetitionValue {
1411    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1412        f.write_str("&str can only be converted to GridTrackRepetition if it's value is 'auto-fit' or 'auto-fill'")
1413    }
1414}
1415impl TryFrom<&str> for RepetitionCount {
1416    type Error = InvalidStringRepetitionValue;
1417    fn try_from(value: &str) -> Result<Self, InvalidStringRepetitionValue> {
1418        match value {
1419            "auto-fit" => Ok(Self::AutoFit),
1420            "auto-fill" => Ok(Self::AutoFill),
1421            _ => Err(InvalidStringRepetitionValue),
1422        }
1423    }
1424}
1425
1426#[cfg(feature = "parse")]
1427impl FromCss for RepetitionCount {
1428    fn from_css<'i>(parser: &mut Parser<'i, '_>) -> CssParseResult<'i, Self> {
1429        match parser.next()?.clone() {
1430            Token::Number { int_value: Some(value), .. } if value.is_positive() => {
1431                Ok(Self::Count(saturating_u16(value)))
1432            }
1433            Token::Ident(ident) if ident == "auto-fit" => Ok(Self::AutoFit),
1434            Token::Ident(ident) if ident == "auto-fill" => Ok(Self::AutoFill),
1435            token => Err(parser.new_unexpected_token_error(token))?,
1436        }
1437    }
1438}
1439#[cfg(feature = "parse")]
1440from_str_from_css!(RepetitionCount);
1441
1442/// A typed representation of a `repeat(..)` in `grid-template-*` value
1443#[derive(Clone, PartialEq, Debug)]
1444#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1445pub struct GridTemplateRepetition<S: CheapCloneStr> {
1446    /// The number of the times the repeat is repeated
1447    pub count: RepetitionCount,
1448    /// The tracks to repeat
1449    pub tracks: Vec<TrackSizingFunction>,
1450    /// The line names for the repeated tracks
1451    pub line_names: Vec<Vec<S>>,
1452}
1453
1454#[rustfmt::skip]
1455impl<S: CheapCloneStr> GenericRepetition for &'_ GridTemplateRepetition<S> {
1456    type CustomIdent = S;
1457    type RepetitionTrackList<'a> = core::iter::Copied<core::slice::Iter<'a, TrackSizingFunction>> where Self: 'a;
1458    type TemplateLineNames<'a> = core::iter::Map<core::slice::Iter<'a, Vec<S>>, fn(&Vec<S>) -> core::slice::Iter<'_, S>> where Self: 'a;
1459    #[inline(always)]
1460    fn count(&self) -> RepetitionCount {
1461        self.count
1462    }
1463    #[inline(always)]
1464    fn track_count(&self) -> u16 {
1465        self.tracks.len().min(u16::MAX as usize) as u16
1466    }
1467    #[inline(always)]
1468    fn tracks(&self) -> Self::RepetitionTrackList<'_> {
1469        self.tracks.iter().copied()
1470    }
1471    #[inline(always)]
1472    fn lines_names(&self) -> Self::TemplateLineNames<'_> {
1473        self.line_names.iter().map(|names| names.iter())
1474    }
1475}
1476
1477/// An element in a `grid-template-columns` or `grid-template-rows` definition.
1478/// Either a track sizing function or a repeat().
1479///
1480/// See <https://developer.mozilla.org/en-US/docs/Web/CSS/grid-template-columns>
1481#[derive(Clone, PartialEq, Debug)]
1482#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1483pub enum GridTemplateComponent<S: CheapCloneStr> {
1484    /// A single non-repeated track
1485    Single(TrackSizingFunction),
1486    /// Automatically generate grid tracks to fit the available space using the specified definite track lengths
1487    /// Only valid if every track in template (not just the repetition) has a fixed size.
1488    Repeat(GridTemplateRepetition<S>),
1489}
1490
1491impl<S: CheapCloneStr> GridTemplateComponent<S> {
1492    /// Convert a `GridTemplateComponent` into a `GridTemplateComponentRef`
1493    pub fn as_component_ref(&self) -> GenericGridTemplateComponent<S, &GridTemplateRepetition<S>> {
1494        match self {
1495            GridTemplateComponent::Single(size) => GenericGridTemplateComponent::Single(*size),
1496            GridTemplateComponent::Repeat(repetition) => GenericGridTemplateComponent::Repeat(repetition),
1497        }
1498    }
1499}
1500
1501impl<S: CheapCloneStr> GridTemplateComponent<S> {
1502    /// Whether the track definition is a auto-repeated fragment
1503    pub fn is_auto_repetition(&self) -> bool {
1504        matches!(
1505            self,
1506            Self::Repeat(GridTemplateRepetition { count: RepetitionCount::AutoFit | RepetitionCount::AutoFill, .. })
1507        )
1508    }
1509}
1510impl<S: CheapCloneStr> TaffyAuto for GridTemplateComponent<S> {
1511    const AUTO: Self = Self::Single(TrackSizingFunction::AUTO);
1512}
1513impl<S: CheapCloneStr> TaffyMinContent for GridTemplateComponent<S> {
1514    const MIN_CONTENT: Self = Self::Single(TrackSizingFunction::MIN_CONTENT);
1515}
1516impl<S: CheapCloneStr> TaffyMaxContent for GridTemplateComponent<S> {
1517    const MAX_CONTENT: Self = Self::Single(TrackSizingFunction::MAX_CONTENT);
1518}
1519impl<S: CheapCloneStr> TaffyFitContent for GridTemplateComponent<S> {
1520    fn fit_content(argument: LengthPercentage) -> Self {
1521        Self::Single(TrackSizingFunction::fit_content(argument))
1522    }
1523}
1524impl<S: CheapCloneStr> TaffyZero for GridTemplateComponent<S> {
1525    const ZERO: Self = Self::Single(TrackSizingFunction::ZERO);
1526}
1527impl<S: CheapCloneStr> FromLength for GridTemplateComponent<S> {
1528    fn from_length<Input: Into<f64> + Copy>(value: Input) -> Self {
1529        Self::Single(TrackSizingFunction::from_length(value))
1530    }
1531}
1532impl<S: CheapCloneStr> FromPercent for GridTemplateComponent<S> {
1533    fn from_percent<Input: Into<f64> + Copy>(percent: Input) -> Self {
1534        Self::Single(TrackSizingFunction::from_percent(percent))
1535    }
1536}
1537impl<S: CheapCloneStr> FromFr for GridTemplateComponent<S> {
1538    fn from_fr<Input: Into<f64> + Copy>(flex: Input) -> Self {
1539        Self::Single(TrackSizingFunction::from_fr(flex))
1540    }
1541}
1542impl<S: CheapCloneStr> From<MinMax<MinTrackSizingFunction, MaxTrackSizingFunction>> for GridTemplateComponent<S> {
1543    fn from(input: MinMax<MinTrackSizingFunction, MaxTrackSizingFunction>) -> Self {
1544        Self::Single(input)
1545    }
1546}
1547
1548#[cfg(feature = "parse")]
1549impl<S: CheapCloneStr> FromCss for GridTemplateComponent<S> {
1550    fn from_css<'i>(parser: &mut Parser<'i, '_>) -> CssParseResult<'i, Self> {
1551        // Try to parse a minmax() function
1552        if let Ok(value) = parser.try_parse(|parser| {
1553            parser.expect_function_matching("repeat")?;
1554            parser.parse_nested_block(|parser| {
1555                let count = RepetitionCount::from_css(parser)?;
1556                parser.expect_comma()?;
1557                let tracks = GridTemplateTracks::<S, TrackSizingFunction>::from_css(parser)?;
1558
1559                Ok(Self::Repeat(GridTemplateRepetition { count, tracks: tracks.tracks, line_names: tracks.line_names }))
1560            })
1561        }) {
1562            return Ok(value);
1563        }
1564
1565        // Else parse a track sizing function
1566        let track_sizing_function = TrackSizingFunction::from_css(parser)?;
1567        Ok(Self::Single(track_sizing_function))
1568    }
1569}
1570#[cfg(feature = "parse")]
1571impl<S: CheapCloneStr> core::str::FromStr for GridTemplateComponent<S> {
1572    type Err = ParseError;
1573    fn from_str(input: &str) -> Result<Self, Self::Err> {
1574        parse_css_str_entirely(input)
1575    }
1576}
1577
1578#[derive(Clone, PartialEq, Debug)]
1579#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1580#[doc(hidden)]
1581pub struct GridTemplateTracks<S: CheapCloneStr, Track> {
1582    /// The tracks to repeat
1583    pub tracks: Vec<Track>,
1584    /// The line names for the repeated tracks
1585    pub line_names: Vec<Vec<S>>,
1586}
1587
1588impl<S: CheapCloneStr, Track> Default for GridTemplateTracks<S, Track> {
1589    fn default() -> Self {
1590        Self { tracks: Vec::new(), line_names: Vec::new() }
1591    }
1592}
1593
1594#[cfg(feature = "parse")]
1595impl<S: CheapCloneStr, Track: FromCss + Debug> FromCss for GridTemplateTracks<S, Track> {
1596    fn from_css<'i>(parser: &mut Parser<'i, '_>) -> CssParseResult<'i, Self> {
1597        fn try_parse_line_names<'i, S: CheapCloneStr>(parser: &mut Parser<'i, '_>) -> CssParseResult<'i, Vec<S>> {
1598            parser.try_parse(|parser| {
1599                parser.expect_square_bracket_block()?;
1600                parser.parse_nested_block(|parser| {
1601                    let mut line_names = Vec::new();
1602                    while !parser.is_exhausted() {
1603                        line_names.push(S::from(parser.expect_ident_cloned()?.as_ref()));
1604                    }
1605                    Ok(line_names)
1606                })
1607            })
1608        }
1609
1610        let mut tracks = Self::default();
1611        if let Ok(line_names) = try_parse_line_names(parser) {
1612            tracks.line_names.push(line_names);
1613        }
1614
1615        while !parser.is_exhausted() {
1616            tracks.tracks.push(Track::from_css(parser)?);
1617            if let Ok(line_names) = try_parse_line_names(parser) {
1618                tracks.line_names.push(line_names);
1619            }
1620        }
1621
1622        if tracks.tracks.is_empty() {
1623            return Err(parser.new_error(cssparser::BasicParseErrorKind::EndOfInput));
1624        }
1625
1626        Ok(tracks)
1627    }
1628}
1629#[cfg(feature = "parse")]
1630impl<S: CheapCloneStr, Track: FromCss + Debug> core::str::FromStr for GridTemplateTracks<S, Track> {
1631    type Err = ParseError;
1632    fn from_str(input: &str) -> Result<Self, Self::Err> {
1633        parse_css_str_entirely(input)
1634    }
1635}
1636
1637#[derive(Default)]
1638#[doc(hidden)]
1639pub struct GridAutoTracks(pub Vec<TrackSizingFunction>);
1640
1641#[cfg(feature = "parse")]
1642impl FromCss for GridAutoTracks {
1643    fn from_css<'i>(parser: &mut Parser<'i, '_>) -> CssParseResult<'i, Self> {
1644        let mut tracks = Self::default();
1645        while !parser.is_exhausted() {
1646            tracks.0.push(TrackSizingFunction::from_css(parser)?);
1647        }
1648        if tracks.0.is_empty() {
1649            return Err(parser.new_error(cssparser::BasicParseErrorKind::EndOfInput));
1650        }
1651        Ok(tracks)
1652    }
1653}
1654#[cfg(feature = "parse")]
1655from_str_from_css!(GridAutoTracks);
1656
1657#[cfg(all(test, feature = "parse"))]
1658mod tests {
1659    use super::*;
1660    use crate::sys::DefaultCheapStr;
1661
1662    #[test]
1663    fn grid_placement_parser_saturates_numeric_values() {
1664        assert_eq!(
1665            "32768".parse::<GridPlacement<DefaultCheapStr>>().unwrap(),
1666            GridPlacement::Line(GridLine::from(i16::MAX))
1667        );
1668        assert_eq!(
1669            "-32769".parse::<GridPlacement<DefaultCheapStr>>().unwrap(),
1670            GridPlacement::Line(GridLine::from(i16::MIN))
1671        );
1672        assert_eq!("span 65536".parse::<GridPlacement<DefaultCheapStr>>().unwrap(), GridPlacement::Span(u16::MAX));
1673
1674        let named_line = "32768 line".parse::<GridPlacement<DefaultCheapStr>>().unwrap();
1675        assert!(matches!(named_line, GridPlacement::NamedLine(_, i16::MAX)));
1676
1677        let named_span = "span 65536 line".parse::<GridPlacement<DefaultCheapStr>>().unwrap();
1678        assert!(matches!(named_span, GridPlacement::NamedSpan(_, u16::MAX)));
1679    }
1680
1681    #[test]
1682    fn repetition_parser_saturates_numeric_values() {
1683        assert_eq!("65536".parse::<RepetitionCount>().unwrap(), RepetitionCount::Count(u16::MAX));
1684
1685        let component = "repeat(65536, 1px)".parse::<GridTemplateComponent<DefaultCheapStr>>().unwrap();
1686        assert!(matches!(
1687            component,
1688            GridTemplateComponent::Repeat(GridTemplateRepetition { count: RepetitionCount::Count(u16::MAX), .. })
1689        ));
1690    }
1691
1692    #[test]
1693    fn repetition_track_count_saturates() {
1694        let repetition = GridTemplateRepetition::<DefaultCheapStr> {
1695            count: RepetitionCount::Count(1),
1696            tracks: vec![TrackSizingFunction::AUTO; u16::MAX as usize + 1],
1697            line_names: Vec::new(),
1698        };
1699        assert_eq!((&repetition).track_count(), u16::MAX);
1700    }
1701}