///|
/// Replace `@variant { … }` blocks with the variant applied to their
/// body.
///
/// The body is compiled as if it lived inside a rule with the selector `&`. If
/// the variant leaves that selector alone the wrapper disappears; otherwise the
/// rewritten selector is anchored with `:scope`.
fn substitute_at_variant(
nodes : ArrayView[CssNode],
theme : Map[String, String],
custom_variants : Map[String, CustomVariantTemplate],
) -> (Array[CssNode], Bool) raise CompileError {
let output : Array[CssNode] = []
let mut changed = false
for node in nodes {
match node {
AtRule(name="@variant", params~, nodes=Some(children), span~) => {
changed = true
let (body, _) = substitute_at_variant(children, theme, custom_variants)
for
expanded in expand_at_variant(
trim(params),
body,
theme,
custom_variants,
span,
) {
output.push(expanded)
}
}
Rule(selector~, nodes=children, span~) => {
let (inner, inner_changed) = substitute_at_variant(
children, theme, custom_variants,
)
changed = changed || inner_changed
output.push(Rule(selector~, nodes=inner, span~))
}
AtRule(name~, params~, nodes=Some(children), span~) => {
let (inner, inner_changed) = substitute_at_variant(
children, theme, custom_variants,
)
changed = changed || inner_changed
output.push(AtRule(name~, params~, nodes=Some(inner), span~))
}
Context(values~, nodes=children, span~) => {
let (inner, inner_changed) = substitute_at_variant(
children, theme, custom_variants,
)
changed = changed || inner_changed
output.push(Context(values~, nodes=inner, span~))
}
_ => output.push(node)
}
}
(output, changed)
}
///|
fn expand_at_variant(
params : String,
body : Array[CssNode],
theme : Map[String, String],
custom_variants : Map[String, CustomVariantTemplate],
span : SourceSpan,
) -> Array[CssNode] raise CompileError {
if params == "" {
raise InvalidCss("Cannot use `@variant` without a variant")
}
let output : Array[CssNode] = []
// A comma separated list repeats the body once per variant.
for branch in split_top_level(params, ',') {
let stack = trim(branch)
if stack == "" {
raise InvalidCss("Cannot use `@variant` with empty variant")
}
let mut current : Array[CssNode] = [
Rule(selector="&", nodes=body.copy(), span~),
]
let mut selector = "&"
for variant in split_variants(stack) {
guard apply_variant_to_nodes(
variant,
current,
selector,
theme,
custom_variants,
[],
)
is Some((next_nodes, next_selector)) else {
raise InvalidCss(
"Cannot use `@variant` with unknown variant: \{variant}",
)
}
current = next_nodes
selector = next_selector
}
for
node in replace_candidate_selector_nodes(
strip_self_rules(current),
"&",
":scope",
) {
output.push(node)
}
}
output
}