///|
/// Explicit CSS registration metadata. Unregistered variables retain ordinary
/// custom-property inheritance and substitution behavior.
pub(all) struct PropertyRegistration {
inherits : Bool
} derive(Eq, Debug)
///|
/// Value categories supported by the reviewed @property registration API.
pub trait RegisterableValue {}
///|
pub impl RegisterableValue for Color
///|
pub impl RegisterableValue for Length
///|
pub impl RegisterableValue for Number
///|
fn registration_numeric(css : String, category : String) -> Bool {
let units = match category {
"number" => [""]
"percentage" => ["%"]
"angle" => ["deg", "grad", "rad", "turn"]
"length" => ["px", "cm", "mm", "q", "in", "pc", "pt"]
_ => []
}
if css == "0" && category == "length" {
return true
}
units.any(unit => {
if !css.has_suffix(unit) {
return false
}
let number = css[:css.length() - unit.length()].to_owned()
number
.iter()
.all(ch => {
(ch >= '0' && ch <= '9') ||
ch == '.' ||
ch == '+' ||
ch == '-' ||
ch == 'e' ||
ch == 'E'
}) &&
numeric_part(number, "number") is Some(_)
})
}
///|
fn registration_color_initial(css : String) -> Bool {
let contextual = "currentcolor accentcolor accentcolortext activetext buttonborder buttonface buttontext canvas canvastext field fieldtext graytext highlight highlighttext linktext mark marktext selecteditem selecteditemtext visitedtext"
if named_color(css).to_css() == css {
return !contextual.split(" ").any(name => name == css)
}
if css.has_prefix("#") {
return try {
ignore(hex_color(css))
true
} catch {
_ => false
}
}
guard css.find("(") is Some(open) && css.has_suffix(")") else { return false }
let name = css[:open].to_owned()
if ![
"rgb", "rgba", "hsl", "hsla", "hwb", "lab", "lch", "oklab", "oklch", "color",
].contains(name) {
return false
}
let body = css[open + 1:css.length() - 1].to_owned()
let parts = body.split("/").to_array()
if parts.length() > 2 {
return false
}
let comma = body.contains(",")
if comma &&
(parts.length() != 1 || !["rgb", "rgba", "hsl", "hsla"].contains(name)) {
return false
}
let channels = if comma {
parts[0].split(",").map(value => value.trim().to_owned()).to_array()
} else {
parts[0]
.split(" ")
.filter(value => !value.is_empty())
.map(value => value.to_owned())
.to_array()
}
let offset = if name == "color" { 1 } else { 0 }
if offset == 1 &&
(
channels.is_empty() ||
![
"srgb", "srgb-linear", "display-p3", "a98-rgb", "prophoto-rgb", "rec2020",
"xyz-d50", "xyz-d65",
].contains(channels[0])
) {
return false
}
let legacy_alpha = comma &&
(name == "rgba" || name == "hsla") &&
parts.length() == 1 &&
channels.length() == 4
if channels.length() != 3 + offset && !legacy_alpha {
return false
}
for index in offset.. 0 &&
index < 3
let valid = if hue {
registration_numeric(value, "number") ||
registration_numeric(value, "angle")
} else if percentage_required {
registration_numeric(value, "percentage")
} else {
registration_numeric(value, "number") ||
registration_numeric(value, "percentage")
}
if !valid {
return false
}
}
parts.length() == 1 ||
({
let alpha = parts[1].trim().to_owned()
registration_numeric(alpha, "number") ||
registration_numeric(alpha, "percentage")
})
}
///|
/// Register before sharing the token with styles or themes. A registered
/// initial value must be independent of font, viewport, or other variables.
#warnings("-unused_trait_bound")
pub fn[T : RegisterableValue] Token::registered(
self : Token[T],
inherits~ : Bool = true,
) -> Token[T] {
{ ..self, variable: { ..self.variable, registration: Some({ inherits, }), }, }
}
///|
#warnings("-unused_trait_bound")
pub fn[T : RegisterableValue] DynamicSlot::registered(
self : DynamicSlot[T],
inherits~ : Bool = true,
) -> DynamicSlot[T] {
{ ..self, variable: { ..self.variable, registration: Some({ inherits, }), }, }
}
///|
/// Validate the deliberately narrow initial-value subset, including metadata
/// supplied through the low-level IR rather than the typed authoring API.
pub fn VarDefinition::registration_issue(self : VarDefinition) -> ValueIssue? {
if self.registration is None {
return None
}
let css = self.default_css.to_lower()
let valid = match self.category {
NumberCategory => registration_numeric(css, "number")
LengthCategory => registration_numeric(css, "length")
ColorCategory => registration_color_initial(css)
_ => false
}
if valid {
None
} else {
Some({
code: "STYLE_PROPERTY_INITIAL_VALUE",
label: "Registered initial values require a static color, absolute length, or finite number: " +
self.name,
})
}
}
///|
pub extend PropertyRegistration with Eq::{equal, not_equal}
///|
pub extend PropertyRegistration with @debug.Debug::{to_repr}