Menu
Public repository / master
AkurAI-Framework / crates/css/src/utilities/layout.rs
Latest change 3aae9f4e85241bcb775c31f5c03094becd3da56a - Pure-Rust UI layer: akurai-ui, akurai-crypto, Tailwind-grade akurai-css by Ólafur Búi Ólafsson
//! Layout: display, position, inset/offsets, z-index, overflow, visibility,
//! box model, aspect ratio, object fit and table layout.
use super::value::{arbitrary, integer, length_or_fraction, negate};
use super::{keyword, prefixed, Utility};
const KEYWORDS: &[(&str, &str)] = &[
// display
("block", "display:block"),
("inline-block", "display:inline-block"),
("inline", "display:inline"),
("flex", "display:flex"),
("inline-flex", "display:inline-flex"),
("grid", "display:grid"),
("inline-grid", "display:inline-grid"),
("hidden", "display:none"),
("contents", "display:contents"),
("flow-root", "display:flow-root"),
("list-item", "display:list-item"),
("table", "display:table"),
("table-row", "display:table-row"),
("table-cell", "display:table-cell"),
(
"sr-only",
"position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;\
clip:rect(0,0,0,0);white-space:nowrap;border-width:0",
),
(
"not-sr-only",
"position:static;width:auto;height:auto;padding:0;margin:0;overflow:visible;\
clip:auto;white-space:normal",
),
// position
("static", "position:static"),
("relative", "position:relative"),
("absolute", "position:absolute"),
("fixed", "position:fixed"),
("sticky", "position:sticky"),
// visibility / isolation / box
("visible", "visibility:visible"),
("invisible", "visibility:hidden"),
("collapse", "visibility:collapse"),
("isolate", "isolation:isolate"),
("isolation-auto", "isolation:auto"),
("box-border", "box-sizing:border-box"),
("box-content", "box-sizing:content-box"),
// aspect / object
("aspect-auto", "aspect-ratio:auto"),
("aspect-square", "aspect-ratio:1 / 1"),
("aspect-video", "aspect-ratio:16 / 9"),
("object-contain", "object-fit:contain"),
("object-cover", "object-fit:cover"),
("object-fill", "object-fit:fill"),
("object-none", "object-fit:none"),
("object-scale-down", "object-fit:scale-down"),
("object-center", "object-position:center"),
("object-top", "object-position:top"),
("object-bottom", "object-position:bottom"),
// tables
("border-collapse", "border-collapse:collapse"),
("border-separate", "border-collapse:separate"),
("table-auto", "table-layout:auto"),
("table-fixed", "table-layout:fixed"),
// z
("z-auto", "z-index:auto"),
];
const OVERFLOW_VALUES: &[&str] = &["auto", "hidden", "clip", "visible", "scroll"];
/// Offsets: `(prefix, properties, tier)`.
const INSET: &[(&str, &[&str], u8)] = &[
("inset-x", &["left", "right"], 1),
("inset-y", &["top", "bottom"], 1),
("inset", &["inset"], 0),
("top", &["top"], 2),
("right", &["right"], 2),
("bottom", &["bottom"], 2),
("left", &["left"], 2),
];
pub fn resolve(u: &str) -> Option<Utility> {
keyword(KEYWORDS, u)
.or_else(|| overflow(u))
.or_else(|| prefixed(u, INSET, inset_value))
.or_else(|| z_index(u, false))
}
/// `-top-1/2`, `-inset-x-2`, `-z-10`.
pub fn negative(u: &str) -> Option<Utility> {
prefixed(u, INSET, |v| negate(&length_or_fraction(v)?)).or_else(|| z_index(u, true))
}
fn inset_value(v: &str) -> Option<String> {
if v == "auto" {
return Some("auto".into());
}
length_or_fraction(v)
}
/// `overflow-hidden`, `overflow-x-auto`, …
fn overflow(u: &str) -> Option<Utility> {
let rest = u.strip_prefix("overflow-")?;
let (prop, v, tier) = match rest.split_once('-') {
Some(("x", v)) => ("overflow-x", v, 1),
Some(("y", v)) => ("overflow-y", v, 1),
_ => ("overflow", rest, 0),
};
OVERFLOW_VALUES
.contains(&v)
.then(|| Utility::decl(prop, v).tier(tier))
}
/// `z-10`, `z-[999]`, and `-z-10` when `neg`.
fn z_index(u: &str, neg: bool) -> Option<Utility> {
let v = u.strip_prefix("z-")?;
let value = match integer(v) {
Some(n) if neg => format!("-{n}"),
Some(n) => n.to_string(),
None if !neg => arbitrary(v)?,
None => return None,
};
Some(Utility::decl("z-index", &value))
}
#[cfg(test)]
mod tests {
use super::*;
fn decl(u: &str) -> Option<String> {
resolve(u).map(|x| x.declaration())
}
#[test]
fn overflow_axes() {
assert_eq!(decl("overflow-auto").as_deref(), Some("overflow:auto"));
assert_eq!(
decl("overflow-y-scroll").as_deref(),
Some("overflow-y:scroll")
);
assert_eq!(decl("overflow-z-auto"), None);
assert_eq!(decl("overflow-x-wat"), None);
}
#[test]
fn insets() {
assert_eq!(decl("inset-1/2").as_deref(), Some("inset:50%"));
assert_eq!(decl("left-px").as_deref(), Some("left:1px"));
let n = |u: &str| negative(u).map(|x| x.declaration());
assert_eq!(n("inset-x-2").as_deref(), Some("left:-8px;right:-8px"));
assert_eq!(n("top-auto"), None);
}
#[test]
fn z_values() {
assert_eq!(decl("z-50").as_deref(), Some("z-index:50"));
assert_eq!(decl("z-x"), None);
assert_eq!(negative("z-[3]"), None);
}
}