render: honor responsive grid track syntax
This commit is contained in:
1 parent
1e71053169
commit
df9359ec87
2 files changed
+117
-26
No files matched your search
@@ -278,8 +278,9 @@ pub struct LayoutStyle {
|
|||||||
// CSS Grid. Tracks are stored as taffy sizing functions; `grid_areas` is the
|
// CSS Grid. Tracks are stored as taffy sizing functions; `grid_areas` is the
|
||||||
// parsed `grid-template-areas` matrix (one Vec per row, `.` for a null cell),
|
// parsed `grid-template-areas` matrix (one Vec per row, `.` for a null cell),
|
||||||
// resolved to line placements on children in a later pass.
|
// resolved to line placements on children in a later pass.
|
||||||
pub grid_template_columns: Vec<taffy::TrackSizingFunction>,
|
pub grid_template_columns: Vec<taffy::GridTemplateComponent<String>>,
|
||||||
pub grid_template_rows: Vec<taffy::TrackSizingFunction>,
|
pub grid_template_rows: Vec<taffy::GridTemplateComponent<String>>,
|
||||||
|
pub grid_auto_flow: Option<taffy::GridAutoFlow>,
|
||||||
pub grid_areas: Option<Vec<Vec<String>>>,
|
pub grid_areas: Option<Vec<Vec<String>>>,
|
||||||
pub grid_area_name: Option<String>,
|
pub grid_area_name: Option<String>,
|
||||||
pub grid_column: Option<taffy::Line<taffy::GridPlacement>>,
|
pub grid_column: Option<taffy::Line<taffy::GridPlacement>>,
|
||||||
@@ -702,29 +703,22 @@ pub(crate) fn to_taffy_style(style: &LayoutStyle) -> Style {
|
|||||||
s.flex_basis = dimension(style.flex_basis);
|
s.flex_basis = dimension(style.flex_basis);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Grid container tracks and gaps. obscura expands `repeat()` itself during
|
// Grid container tracks and gaps. Numeric repeat() values are expanded
|
||||||
// parsing, so every stored track is a single (non-repeated) `MinMax`; wrap
|
// during parsing, while auto-fill/auto-fit remain native taffy repetition
|
||||||
// each as a `GridTemplateComponent::Single` for taffy 0.12. The 0.7-era
|
// components so their count can use the final container size. The 0.7-era
|
||||||
// fr->Auto row workaround (which stopped `minmax(0,1fr)` image rows from
|
// fr->Auto row workaround (which stopped `minmax(0,1fr)` image rows from
|
||||||
// collapsing to a sliver) is gone: taffy 0.12 treats an in-flow child's
|
// collapsing to a sliver) is gone: taffy 0.12 treats an in-flow child's
|
||||||
// vertical available space as indefinite, so fr rows of an auto-height grid
|
// vertical available space as indefinite, so fr rows of an auto-height grid
|
||||||
// size to their content the way real CSS does.
|
// size to their content the way real CSS does.
|
||||||
if style.display == Display::Grid {
|
if style.display == Display::Grid {
|
||||||
if !style.grid_template_columns.is_empty() {
|
if !style.grid_template_columns.is_empty() {
|
||||||
s.grid_template_columns = style
|
s.grid_template_columns = style.grid_template_columns.clone();
|
||||||
.grid_template_columns
|
|
||||||
.iter()
|
|
||||||
.cloned()
|
|
||||||
.map(taffy::GridTemplateComponent::Single)
|
|
||||||
.collect();
|
|
||||||
}
|
}
|
||||||
if !style.grid_template_rows.is_empty() {
|
if !style.grid_template_rows.is_empty() {
|
||||||
s.grid_template_rows = style
|
s.grid_template_rows = style.grid_template_rows.clone();
|
||||||
.grid_template_rows
|
}
|
||||||
.iter()
|
if let Some(flow) = style.grid_auto_flow {
|
||||||
.cloned()
|
s.grid_auto_flow = flow;
|
||||||
.map(taffy::GridTemplateComponent::Single)
|
|
||||||
.collect();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let cg = style.column_gap.unwrap_or(0.0);
|
let cg = style.column_gap.unwrap_or(0.0);
|
||||||
|
|||||||
@@ -588,6 +588,8 @@ fn apply_value(style: &mut LayoutStyle, name: &str, value: &str) {
|
|||||||
}
|
}
|
||||||
"grid-template-areas" => style.grid_areas = Some(parse_grid_areas(value)),
|
"grid-template-areas" => style.grid_areas = Some(parse_grid_areas(value)),
|
||||||
"grid-template" => parse_grid_template(style, value),
|
"grid-template" => parse_grid_template(style, value),
|
||||||
|
"grid" => parse_grid_shorthand(style, value),
|
||||||
|
"grid-auto-flow" => style.grid_auto_flow = parse_grid_auto_flow(value),
|
||||||
"grid-area" => {
|
"grid-area" => {
|
||||||
// Named area (single ident) or line form `r/c/r/c`. We only resolve
|
// Named area (single ident) or line form `r/c/r/c`. We only resolve
|
||||||
// the named-area case here; line forms are handled by grid-row/column.
|
// the named-area case here; line forms are handled by grid-row/column.
|
||||||
@@ -787,11 +789,16 @@ fn scale_number(s: &str) -> Option<f32> {
|
|||||||
/// minmax(0,1fr)`) into taffy sizing functions. Tokenizes respecting the
|
/// minmax(0,1fr)`) into taffy sizing functions. Tokenizes respecting the
|
||||||
/// parentheses in `minmax(...)` / `fit-content(...)`.
|
/// parentheses in `minmax(...)` / `fit-content(...)`.
|
||||||
/// Parse a track list into taffy sizing functions plus the `[line-name]` map
|
/// Parse a track list into taffy sizing functions plus the `[line-name]` map
|
||||||
/// (name -> 1-based grid line number). `repeat(n, ...)` is expanded to n copies
|
/// (name -> 1-based grid line number). `repeat(n, ...)` is expanded to n copies;
|
||||||
/// (it was previously collapsed to a single phantom `Auto` track, which broke
|
/// auto-fill/auto-fit remain typed repetitions so layout can derive their count
|
||||||
/// every `repeat()`-based grid) and `[name]` annotations are captured, not
|
/// from the used container width. `[name]` annotations are captured rather than
|
||||||
/// turned into tracks. First occurrence of a name wins.
|
/// turned into tracks. First occurrence of a name wins.
|
||||||
pub(crate) fn parse_track_list_named(value: &str) -> (Vec<taffy::TrackSizingFunction>, Vec<(String, i16)>) {
|
pub(crate) fn parse_track_list_named(
|
||||||
|
value: &str,
|
||||||
|
) -> (
|
||||||
|
Vec<taffy::GridTemplateComponent<String>>,
|
||||||
|
Vec<(String, i16)>,
|
||||||
|
) {
|
||||||
let mut tracks = Vec::new();
|
let mut tracks = Vec::new();
|
||||||
let mut names = Vec::new();
|
let mut names = Vec::new();
|
||||||
let mut line: i16 = 1;
|
let mut line: i16 = 1;
|
||||||
@@ -803,7 +810,7 @@ pub(crate) fn parse_track_list_named(value: &str) -> (Vec<taffy::TrackSizingFunc
|
|||||||
|
|
||||||
fn expand_track_token(
|
fn expand_track_token(
|
||||||
tok: &str,
|
tok: &str,
|
||||||
tracks: &mut Vec<taffy::TrackSizingFunction>,
|
tracks: &mut Vec<taffy::GridTemplateComponent<String>>,
|
||||||
names: &mut Vec<(String, i16)>,
|
names: &mut Vec<(String, i16)>,
|
||||||
line: &mut i16,
|
line: &mut i16,
|
||||||
) {
|
) {
|
||||||
@@ -819,10 +826,29 @@ fn expand_track_token(
|
|||||||
if lower.starts_with("repeat(") && t.ends_with(')') {
|
if lower.starts_with("repeat(") && t.ends_with(')') {
|
||||||
let inner = &t["repeat(".len()..t.len() - 1];
|
let inner = &t["repeat(".len()..t.len() - 1];
|
||||||
if let Some((cnt, sub)) = inner.split_once(',') {
|
if let Some((cnt, sub)) = inner.split_once(',') {
|
||||||
// `auto-fill`/`auto-fit` counts need the container width; fall back
|
|
||||||
// to one repetition (a reasonable minimum) rather than dropping it.
|
|
||||||
let count = cnt.trim().parse::<usize>().unwrap_or(1).min(1000);
|
|
||||||
let subtoks = tokenize_tracks(sub.trim());
|
let subtoks = tokenize_tracks(sub.trim());
|
||||||
|
let repetition = match cnt.trim().to_ascii_lowercase().as_str() {
|
||||||
|
"auto-fill" => Some(taffy::RepetitionCount::AutoFill),
|
||||||
|
"auto-fit" => Some(taffy::RepetitionCount::AutoFit),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
if let Some(count) = repetition {
|
||||||
|
let repeated_tracks = subtoks
|
||||||
|
.iter()
|
||||||
|
.filter(|st| !st.trim_start().starts_with('['))
|
||||||
|
.map(|st| track(st))
|
||||||
|
.collect();
|
||||||
|
tracks.push(taffy::GridTemplateComponent::Repeat(
|
||||||
|
taffy::GridTemplateRepetition {
|
||||||
|
count,
|
||||||
|
tracks: repeated_tracks,
|
||||||
|
line_names: Vec::new(),
|
||||||
|
},
|
||||||
|
));
|
||||||
|
*line += 1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let count = cnt.trim().parse::<usize>().unwrap_or(1).min(1000);
|
||||||
for _ in 0..count {
|
for _ in 0..count {
|
||||||
for st in &subtoks {
|
for st in &subtoks {
|
||||||
expand_track_token(st, tracks, names, line);
|
expand_track_token(st, tracks, names, line);
|
||||||
@@ -831,7 +857,7 @@ fn expand_track_token(
|
|||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
tracks.push(track(t));
|
tracks.push(taffy::GridTemplateComponent::Single(track(t)));
|
||||||
*line += 1;
|
*line += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -974,6 +1000,53 @@ fn parse_grid_template(style: &mut LayoutStyle, value: &str) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parse the common `grid` shorthand forms. A side containing `auto-flow`
|
||||||
|
/// defines the implicit placement axis; the opposite side is the explicit
|
||||||
|
/// track list. Without `auto-flow`, this is the `grid-template` shorthand.
|
||||||
|
fn parse_grid_shorthand(style: &mut LayoutStyle, value: &str) {
|
||||||
|
let Some((rows, columns)) = value.split_once('/') else {
|
||||||
|
parse_grid_template(style, value);
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let rows = rows.trim();
|
||||||
|
let columns = columns.trim();
|
||||||
|
if rows.to_ascii_lowercase().contains("auto-flow") {
|
||||||
|
style.grid_template_rows.clear();
|
||||||
|
let (tracks, names) = parse_track_list_named(columns);
|
||||||
|
style.grid_template_columns = tracks;
|
||||||
|
style.grid_col_line_names = (!names.is_empty()).then(|| build_line_map(names));
|
||||||
|
style.grid_auto_flow = Some(if rows.to_ascii_lowercase().contains("dense") {
|
||||||
|
taffy::GridAutoFlow::RowDense
|
||||||
|
} else {
|
||||||
|
taffy::GridAutoFlow::Row
|
||||||
|
});
|
||||||
|
} else if columns.to_ascii_lowercase().contains("auto-flow") {
|
||||||
|
style.grid_template_columns.clear();
|
||||||
|
let (tracks, names) = parse_track_list_named(rows);
|
||||||
|
style.grid_template_rows = tracks;
|
||||||
|
style.grid_row_line_names = (!names.is_empty()).then(|| build_line_map(names));
|
||||||
|
style.grid_auto_flow = Some(if columns.to_ascii_lowercase().contains("dense") {
|
||||||
|
taffy::GridAutoFlow::ColumnDense
|
||||||
|
} else {
|
||||||
|
taffy::GridAutoFlow::Column
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
parse_grid_template(style, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_grid_auto_flow(value: &str) -> Option<taffy::GridAutoFlow> {
|
||||||
|
let lower = value.trim().to_ascii_lowercase();
|
||||||
|
let dense = lower.split_whitespace().any(|token| token == "dense");
|
||||||
|
let column = lower.split_whitespace().any(|token| token == "column");
|
||||||
|
Some(match (column, dense) {
|
||||||
|
(false, false) => taffy::GridAutoFlow::Row,
|
||||||
|
(false, true) => taffy::GridAutoFlow::RowDense,
|
||||||
|
(true, false) => taffy::GridAutoFlow::Column,
|
||||||
|
(true, true) => taffy::GridAutoFlow::ColumnDense,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// Store a `grid-column`/`grid-row` value. Numeric/`span` forms resolve to a
|
/// Store a `grid-column`/`grid-row` value. Numeric/`span` forms resolve to a
|
||||||
/// `taffy::Line` now; a value that names a grid line (`content-start /
|
/// `taffy::Line` now; a value that names a grid line (`content-start /
|
||||||
/// content-end`, or the `grid-column: content` area shorthand) is kept raw and
|
/// content-end`, or the `grid-column: content` area shorthand) is kept raw and
|
||||||
@@ -2252,6 +2325,30 @@ mod tests {
|
|||||||
assert_eq!(s.padding_relative[1], Some(crate::Dimension::Vmin(2.0)));
|
assert_eq!(s.padding_relative[1], Some(crate::Dimension::Vmin(2.0)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn responsive_grid_repetitions_and_shorthand_remain_typed() {
|
||||||
|
let auto = compute_style(
|
||||||
|
"div",
|
||||||
|
Some("display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr))"),
|
||||||
|
);
|
||||||
|
assert!(matches!(
|
||||||
|
auto.grid_template_columns.as_slice(),
|
||||||
|
[taffy::GridTemplateComponent::Repeat(
|
||||||
|
taffy::GridTemplateRepetition {
|
||||||
|
count: taffy::RepetitionCount::AutoFit,
|
||||||
|
..
|
||||||
|
}
|
||||||
|
)]
|
||||||
|
));
|
||||||
|
|
||||||
|
let shorthand = compute_style(
|
||||||
|
"div",
|
||||||
|
Some("display:grid;grid:auto-flow/repeat(3,1fr)"),
|
||||||
|
);
|
||||||
|
assert_eq!(shorthand.grid_auto_flow, Some(taffy::GridAutoFlow::Row));
|
||||||
|
assert_eq!(shorthand.grid_template_columns.len(), 3);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn ua_defaults_and_ignore_unknown() {
|
fn ua_defaults_and_ignore_unknown() {
|
||||||
let s = compute_style("span", Some("color: red; ; bogus: ; display: none"));
|
let s = compute_style("span", Some("color: red; ; bogus: ; display: none"));
|
||||||
|
|||||||
Reference in new issue
Block a user