///|
/// A utility that emits declarations inside a nested selector instead of the
/// candidate selector itself.
///
/// `nested_selector` uses `&` for the candidate selector, matching the upstream
/// `styleRule('&::placeholder', …)` form. `sort_key` mirrors the upstream
/// `--tw-sort` declaration: it replaces the declaration-derived property order
/// and still counts as one declaration.
priv struct SelectorUtility {
nested_selector : String
declarations : Array[Declaration]
sort_key : String
}
///|
fn is_zero_length(value : String) -> Bool {
if value == "" {
return false
}
let mut end = value.length()
while end > 0 {
let c = value[end - 1]
if (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '%' {
end -= 1
} else {
break
}
}
let number = value[:end].to_owned()
if number == "" {
return false
}
let parsed = @string.parse_double(number) catch { _ => return false }
parsed == 0.0
}
///|
/// Resolve the value of a `space-x-*` / `space-y-*` candidate.
///
/// Returns the resolved value plus whether it is a zero length, which upstream
/// collapses to a plain `0` instead of a `calc()` chain.
fn space_value(
theme : Map[String, String],
key : String,
negative : Bool,
) -> (String, Bool)? {
let value = match arbitrary_value(key) {
Some(value) => value
None =>
match theme_value(theme, "--space", key) {
Some(_) =>
match theme_css_value(theme, "--space-\{key}") {
Some(value) => value
None => return None
}
None => {
if key == "px" {
return Some((if negative { "-1px" } else { "1px" }, false))
}
if !is_spacing_number(key) {
return None
}
match numeric_spacing(theme, key) {
Some(value) => value
None => return None
}
}
}
}
let zero = is_zero_length(value) || key == "0"
if zero {
return Some(("0", true))
}
Some((if negative { negate_value(value) } else { value }, false))
}
///|
/// Resolve the width of a `divide-x-*` / `divide-y-*` candidate.
fn divide_width_value(theme : Map[String, String], key : String) -> String? {
if key == "" {
return Some(
theme_css_value(theme, "--default-border-width").unwrap_or("1px"),
)
}
match arbitrary_value(key) {
Some(value) => Some(value)
None =>
match theme_value(theme, "--divide-width", key) {
Some(_) => theme_css_value(theme, "--divide-width-\{key}")
None =>
match theme_value(theme, "--border-width", key) {
Some(_) => theme_css_value(theme, "--border-width-\{key}")
None =>
if is_nonnegative_integer(key) {
Some("\{key}px")
} else {
None
}
}
}
}
}
///|
/// Resolve a color candidate value against an ordered list of theme namespaces.
fn selector_color_value(
theme : Map[String, String],
key : String,
namespaces : ArrayView[String],
) -> String? {
match arbitrary_value(key) {
Some(value) => return Some(value)
None => ()
}
match key {
"inherit" => return Some("inherit")
"current" => return Some("currentcolor")
"transparent" => return Some("transparent")
_ => ()
}
for theme_namespace in namespaces {
if theme_value(theme, theme_namespace, key) is Some(_) {
return theme_css_value(theme, "\{theme_namespace}-\{key}")
}
}
None
}
///|
const DIVIDE_CHILD_SELECTOR : String = ":where(& > :not(:last-child))"
///|
fn space_reverse_utility(name : String) -> SelectorUtility? {
let (axis, sort_key) = match name {
"space-x-reverse" => ("x", "row-gap")
"space-y-reverse" => ("y", "column-gap")
_ => return None
}
Some({
nested_selector: DIVIDE_CHILD_SELECTOR,
declarations: [decl("--tw-space-\{axis}-reverse", "1")],
sort_key,
})
}
///|
fn divide_reverse_utility(name : String) -> SelectorUtility? {
let axis = match name {
"divide-x-reverse" => "x"
"divide-y-reverse" => "y"
_ => return None
}
Some({
nested_selector: DIVIDE_CHILD_SELECTOR,
declarations: [decl("--tw-divide-\{axis}-reverse", "1")],
// Upstream omits `--tw-sort` here, so the custom property decides the order.
sort_key: "",
})
}
///|
fn divide_style_utility(name : String) -> SelectorUtility? {
guard prefixed_value(name, "divide") is Some(style) else { return None }
if !["solid", "dashed", "dotted", "double", "none"].contains(style) {
return None
}
Some({
nested_selector: DIVIDE_CHILD_SELECTOR,
declarations: [
decl("--tw-border-style", style),
decl("border-style", style),
],
sort_key: "divide-style",
})
}
///|
fn space_utility(
theme : Map[String, String],
name : String,
negative : Bool,
) -> SelectorUtility? {
let (axis, start, end, sort_key) = if name.has_prefix("space-x-") {
("x", "margin-inline-start", "margin-inline-end", "row-gap")
} else if name.has_prefix("space-y-") {
("y", "margin-block-start", "margin-block-end", "column-gap")
} else {
return None
}
let key = name["space-x-".length():].to_owned()
guard space_value(theme, key, negative) is Some((value, zero)) else {
return None
}
let reverse = "--tw-space-\{axis}-reverse"
let declarations = if zero {
[decl(reverse, "0"), decl(start, "0"), decl(end, "0")]
} else {
[
decl(reverse, "0"),
decl(start, "calc(\{value} * var(\{reverse}))"),
decl(end, "calc(\{value} * calc(1 - var(\{reverse})))"),
]
}
Some({ nested_selector: DIVIDE_CHILD_SELECTOR, declarations, sort_key })
}
///|
fn divide_width_utility(
theme : Map[String, String],
name : String,
) -> SelectorUtility? {
let (axis, key) = if name == "divide-x" {
("x", "")
} else if name == "divide-y" {
("y", "")
} else if name.has_prefix("divide-x-") {
("x", name["divide-x-".length():].to_owned())
} else if name.has_prefix("divide-y-") {
("y", name["divide-y-".length():].to_owned())
} else {
return None
}
guard divide_width_value(theme, key) is Some(value) else { return None }
let reverse = "--tw-divide-\{axis}-reverse"
let declarations = if axis == "x" {
[
decl(reverse, "0"),
decl("border-inline-style", "var(--tw-border-style)"),
decl("border-inline-start-width", "calc(\{value} * var(\{reverse}))"),
decl(
"border-inline-end-width",
"calc(\{value} * calc(1 - var(\{reverse})))",
),
]
} else {
[
decl(reverse, "0"),
decl("border-bottom-style", "var(--tw-border-style)"),
decl("border-top-style", "var(--tw-border-style)"),
decl("border-top-width", "calc(\{value} * var(\{reverse}))"),
decl("border-bottom-width", "calc(\{value} * calc(1 - var(\{reverse})))"),
]
}
Some({
nested_selector: DIVIDE_CHILD_SELECTOR,
declarations,
sort_key: "divide-\{axis}-width",
})
}
///|
/// Compile a candidate that produces a nested selector rather than a flat
/// declaration list.
fn selector_utility(
theme : Map[String, String],
name : String,
negative : Bool,
modifier : String?,
) -> SelectorUtility? {
if negative && !name.has_prefix("space-") {
return None
}
if modifier is Some(_) &&
!(name.has_prefix("divide-") || name.has_prefix("placeholder-")) {
return None
}
match space_reverse_utility(name) {
Some(utility) => return Some(utility)
None => ()
}
match divide_reverse_utility(name) {
Some(utility) => return Some(utility)
None => ()
}
match divide_style_utility(name) {
Some(utility) => return Some(utility)
None => ()
}
match space_utility(theme, name, negative) {
Some(utility) => return Some(utility)
None => ()
}
match divide_width_utility(theme, name) {
Some(utility) => return Some(utility)
None => ()
}
if prefixed_value(name, "divide") is Some(key) {
if selector_color_value(theme, key, [
"--divide-color", "--border-color", "--color",
])
is Some(color) {
return Some({
nested_selector: DIVIDE_CHILD_SELECTOR,
declarations: apply_candidate_value(
theme,
[decl("border-color", color)],
false,
modifier,
),
sort_key: "divide-color",
})
}
}
if prefixed_value(name, "placeholder") is Some(key) {
if selector_color_value(theme, key, ["--placeholder-color", "--color"])
is Some(color) {
return Some({
nested_selector: "&::placeholder",
declarations: apply_candidate_value(
theme,
[decl("color", color)],
false,
modifier,
),
sort_key: "placeholder-color",
})
}
}
None
}