Menu
Public repository / master
AkurAI-Framework / crates/css/src/utilities/flex_grid.rs
Latest change 3aae9f4e85241bcb775c31f5c03094becd3da56a - Pure-Rust UI layer: akurai-ui, akurai-crypto, Tailwind-grade akurai-css by Ólafur Búi Ólafsson
//! Flexbox and grid: direction/wrap, flex/grow/shrink/basis, order, alignment
//! (`items-*`, `justify-*`, `content-*`, `self-*`, `place-*`), grid templates,
//! spans/starts/ends, auto flow and implicit tracks.
use super::value::{arbitrary, integer, length_or_fraction, positive};
use super::{keyword, Utility};
const KEYWORDS: &[(&str, &str)] = &[
("flex-row", "flex-direction:row"),
("flex-row-reverse", "flex-direction:row-reverse"),
("flex-col", "flex-direction:column"),
("flex-col-reverse", "flex-direction:column-reverse"),
("flex-wrap", "flex-wrap:wrap"),
("flex-wrap-reverse", "flex-wrap:wrap-reverse"),
("flex-nowrap", "flex-wrap:nowrap"),
("flex-1", "flex:1 1 0%"),
("flex-auto", "flex:1 1 auto"),
("flex-initial", "flex:0 1 auto"),
("flex-none", "flex:none"),
("order-first", "order:-9999"),
("order-last", "order:9999"),
("order-none", "order:0"),
("grid-cols-none", "grid-template-columns:none"),
("grid-cols-subgrid", "grid-template-columns:subgrid"),
("grid-rows-none", "grid-template-rows:none"),
("grid-rows-subgrid", "grid-template-rows:subgrid"),
("col-auto", "grid-column:auto"),
("col-span-full", "grid-column:1 / -1"),
("row-auto", "grid-row:auto"),
("row-span-full", "grid-row:1 / -1"),
("grid-flow-row", "grid-auto-flow:row"),
("grid-flow-col", "grid-auto-flow:column"),
("grid-flow-dense", "grid-auto-flow:dense"),
("grid-flow-row-dense", "grid-auto-flow:row dense"),
("grid-flow-col-dense", "grid-auto-flow:column dense"),
];
/// Tiered keywords: these override the `flex` shorthand, so they rank later.
const LONGHANDS: &[(&str, &str)] = &[
("grow", "flex-grow:1"),
("grow-0", "flex-grow:0"),
("shrink", "flex-shrink:1"),
("shrink-0", "flex-shrink:0"),
];
/// `(prefix, property, values as (suffix, css))` alignment tables.
type Align = (
&'static str,
&'static str,
&'static [(&'static str, &'static str)],
);
const CONTENT_VALUES: &[(&str, &str)] = &[
("start", "flex-start"),
("end", "flex-end"),
("center", "center"),
("between", "space-between"),
("around", "space-around"),
("evenly", "space-evenly"),
("stretch", "stretch"),
("baseline", "baseline"),
("normal", "normal"),
];
const ITEM_VALUES: &[(&str, &str)] = &[
("start", "flex-start"),
("end", "flex-end"),
("center", "center"),
("baseline", "baseline"),
("stretch", "stretch"),
];
const SELF_VALUES: &[(&str, &str)] = &[
("auto", "auto"),
("start", "flex-start"),
("end", "flex-end"),
("center", "center"),
("stretch", "stretch"),
("baseline", "baseline"),
];
const PLACE_VALUES: &[(&str, &str)] = &[
("start", "start"),
("end", "end"),
("center", "center"),
("stretch", "stretch"),
("between", "space-between"),
("around", "space-around"),
("evenly", "space-evenly"),
("baseline", "baseline"),
("auto", "auto"),
];
/// Longest prefixes first so `justify-items-*` is not read as `justify-*`.
const ALIGN: &[Align] = &[
("justify-items", "justify-items", PLACE_VALUES),
("justify-self", "justify-self", PLACE_VALUES),
("justify", "justify-content", CONTENT_VALUES),
("place-content", "place-content", PLACE_VALUES),
("place-items", "place-items", PLACE_VALUES),
("place-self", "place-self", PLACE_VALUES),
("items", "align-items", ITEM_VALUES),
("content", "align-content", CONTENT_VALUES),
("self", "align-self", SELF_VALUES),
];
const AUTO_TRACKS: &[(&str, &str)] = &[
("auto", "auto"),
("min", "min-content"),
("max", "max-content"),
("fr", "minmax(0,1fr)"),
];
pub fn resolve(u: &str) -> Option<Utility> {
keyword(KEYWORDS, u)
.or_else(|| keyword(LONGHANDS, u).map(|x| x.tier(1)))
.or_else(|| align(u))
.or_else(|| templates(u))
.or_else(|| placement(u))
.or_else(|| auto_tracks(u))
.or_else(|| basis(u))
.or_else(|| order(u, false))
.or_else(|| arbitrary_flex(u))
}
/// `-order-1`.
pub fn negative(u: &str) -> Option<Utility> {
order(u, true)
}
fn align(u: &str) -> Option<Utility> {
ALIGN.iter().find_map(|&(prefix, prop, values)| {
let v = u.strip_prefix(prefix)?.strip_prefix('-')?;
let (_, css) = values.iter().find(|(k, _)| *k == v)?;
Some(Utility::decl(prop, css))
})
}
/// `grid-cols-3` ⇒ `repeat(3,minmax(0,1fr))`, `grid-cols-[1fr_2fr]`.
fn templates(u: &str) -> Option<Utility> {
let (prop, v) = if let Some(v) = u.strip_prefix("grid-cols-") {
("grid-template-columns", v)
} else {
("grid-template-rows", u.strip_prefix("grid-rows-")?)
};
let value = match positive(v) {
Some(n) => format!("repeat({n},minmax(0,1fr))"),
None => arbitrary(v)?,
};
Some(Utility::decl(prop, &value))
}
/// `col-span-2`, `col-start-1`, `col-end-3`, and the `row-*` equivalents.
fn placement(u: &str) -> Option<Utility> {
let (axis, rest) = if let Some(rest) = u.strip_prefix("col-") {
("grid-column", rest)
} else {
("grid-row", u.strip_prefix("row-")?)
};
let (kind, v) = rest.split_once('-')?;
let value = match kind {
"span" => {
let n = positive(v)?;
return Some(Utility::decl(axis, &format!("span {n} / span {n}")));
}
"start" | "end" if v == "auto" => "auto".to_string(),
"start" | "end" => positive(v)
.map(|n| n.to_string())
.or_else(|| arbitrary(v))?,
_ => return None,
};
Some(Utility::decl(&format!("{axis}-{kind}"), &value).tier(1))
}
fn auto_tracks(u: &str) -> Option<Utility> {
let (prop, v) = if let Some(v) = u.strip_prefix("auto-cols-") {
("grid-auto-columns", v)
} else {
("grid-auto-rows", u.strip_prefix("auto-rows-")?)
};
let value = AUTO_TRACKS
.iter()
.find(|(k, _)| *k == v)
.map(|(_, css)| css.to_string())
.or_else(|| arbitrary(v))?;
Some(Utility::decl(prop, &value))
}
fn basis(u: &str) -> Option<Utility> {
let v = u.strip_prefix("basis-")?;
let value = if v == "auto" {
"auto".to_string()
} else {
length_or_fraction(v)?
};
Some(Utility::decl("flex-basis", &value).tier(1))
}
fn order(u: &str, neg: bool) -> Option<Utility> {
let n = integer(u.strip_prefix("order-")?)?;
let sign = if neg && n != 0 { "-" } else { "" };
Some(Utility::decl("order", &format!("{sign}{n}")))
}
/// `flex-[2_2_0%]`.
fn arbitrary_flex(u: &str) -> Option<Utility> {
Some(Utility::decl("flex", &arbitrary(u.strip_prefix("flex-")?)?))
}
#[cfg(test)]
mod tests {
use super::*;
fn decl(u: &str) -> Option<String> {
resolve(u).map(|x| x.declaration())
}
#[test]
fn alignment_prefixes_do_not_shadow_each_other() {
assert_eq!(
decl("justify-items-start").as_deref(),
Some("justify-items:start")
);
assert_eq!(
decl("justify-start").as_deref(),
Some("justify-content:flex-start")
);
assert_eq!(
decl("justify-self-auto").as_deref(),
Some("justify-self:auto")
);
assert_eq!(decl("self-auto").as_deref(), Some("align-self:auto"));
assert_eq!(decl("items-between"), None);
}
#[test]
fn placement_values() {
assert_eq!(decl("row-start-2").as_deref(), Some("grid-row-start:2"));
assert_eq!(
decl("col-end-auto").as_deref(),
Some("grid-column-end:auto")
);
assert_eq!(decl("col-span-0"), None);
assert_eq!(decl("col-wat-2"), None);
}
#[test]
fn flex_longhands_rank_after_shorthand() {
assert_eq!(resolve("flex-1").unwrap().tier, 0);
assert_eq!(resolve("grow").unwrap().tier, 1);
assert_eq!(resolve("basis-4").unwrap().tier, 1);
}
#[test]
fn negative_order() {
assert_eq!(
negative("order-2").map(|x| x.declaration()).as_deref(),
Some("order:-2")
);
assert_eq!(negative("order-last"), None);
}
}