///|
pub struct When {
conditions : Array[@ir.Condition]
}
///|
pub(all) enum PreferredColorScheme {
ColorSchemeLight
ColorSchemeDark
} derive(Eq, Debug)
///|
pub(all) enum MotionPreference {
MotionReduce
MotionNoPreference
} derive(Eq, Debug)
///|
pub(all) enum ViewportOrientation {
OrientationPortrait
OrientationLandscape
} derive(Eq, Debug)
///|
pub fn when(conditions : Array[@ir.Condition]) -> When {
{ conditions: conditions.copy(), }
}
///|
pub fn When::to_conditions(self : When) -> Array[@ir.Condition] {
self.conditions.copy()
}
///|
pub fn when_all(values : Array[When]) -> When {
let conditions : Array[@ir.Condition] = []
for value in values {
for condition in value.conditions {
conditions.push(condition)
}
}
{ conditions, }
}
///|
pub fn When::and_when(self : When, other : When) -> When {
when_all([self, other])
}
///|
pub let always : When = when([])
///|
pub let hover : When = when([@ir.pseudo(Hover)])
///|
pub let focus : When = when([@ir.pseudo(Focus)])
///|
pub let focus_visible : When = when([@ir.pseudo(FocusVisible)])
///|
pub let focus_within : When = when([@ir.pseudo(FocusWithin)])
///|
pub let active : When = when([@ir.pseudo(Active)])
///|
pub let disabled : When = when([@ir.pseudo(Disabled)])
///|
pub let enabled : When = when([@ir.pseudo(Enabled)])
///|
pub let checked : When = when([@ir.pseudo(Checked)])
///|
pub let required : When = when([@ir.pseudo(Required)])
///|
pub let optional : When = when([@ir.pseudo(Optional)])
///|
pub let read_only : When = when([@ir.pseudo(ReadOnly)])
///|
pub let read_write : When = when([@ir.pseudo(ReadWrite)])
///|
pub let placeholder_shown : When = when([@ir.pseudo(PlaceholderShown)])
///|
pub let first_child : When = when([@ir.pseudo(FirstChild)])
///|
pub let last_child : When = when([@ir.pseudo(LastChild)])
///|
pub let only_child : When = when([@ir.pseudo(OnlyChild)])
///|
pub let empty : When = when([@ir.pseudo(Empty)])
///|
pub let link : When = when([@ir.pseudo(Link)])
///|
pub let visited : When = when([@ir.pseudo(Visited)])
///|
pub let before : When = when([@ir.pseudo_element(Before)])
///|
pub let after : When = when([@ir.pseudo_element(After)])
///|
pub let placeholder : When = when([@ir.pseudo_element(Placeholder)])
///|
pub let selection : When = when([@ir.pseudo_element(Selection)])
///|
pub let marker : When = when([@ir.pseudo_element(Marker)])
///|
pub let first_letter : When = when([@ir.pseudo_element(FirstLetter)])
///|
pub let first_line : When = when([@ir.pseudo_element(FirstLine)])
///|
pub fn media(query : @ir.MediaQuery) -> When {
when([@ir.media(query)])
}
///|
pub fn min_width(value : @values.Length) -> When {
media(@ir.media_query([@ir.media_feature("width", Min, value.to_css())]))
}
///|
pub fn max_width(value : @values.Length) -> When {
media(@ir.media_query([@ir.media_feature("width", Max, value.to_css())]))
}
///|
pub fn width_between(
minimum : @values.Length,
maximum : @values.Length,
) -> When {
media(
@ir.media_query([
@ir.media_feature("width", Min, minimum.to_css()),
@ir.media_feature("width", Max, maximum.to_css()),
]),
)
}
///|
fn PreferredColorScheme::to_css(self : PreferredColorScheme) -> String {
match self {
ColorSchemeLight => "light"
ColorSchemeDark => "dark"
}
}
///|
pub fn prefers_color_scheme(value : PreferredColorScheme) -> When {
media(
@ir.media_query([
@ir.media_feature("prefers-color-scheme", Equal, value.to_css()),
]),
)
}
///|
fn MotionPreference::to_css(self : MotionPreference) -> String {
match self {
MotionReduce => "reduce"
MotionNoPreference => "no-preference"
}
}
///|
pub fn prefers_reduced_motion(value : MotionPreference) -> When {
media(
@ir.media_query([
@ir.media_feature("prefers-reduced-motion", Equal, value.to_css()),
]),
)
}
///|
fn ViewportOrientation::to_css(self : ViewportOrientation) -> String {
match self {
OrientationPortrait => "portrait"
OrientationLandscape => "landscape"
}
}
///|
pub fn orientation(value : ViewportOrientation) -> When {
media(
@ir.media_query([@ir.media_feature("orientation", Equal, value.to_css())]),
)
}
///|
pub fn supports(condition : @ir.SupportsCondition) -> When {
when([@ir.supports(condition)])
}
///|
pub fn supports_property(property : String, value : String) -> When {
supports(@ir.supports_declaration(property, value))
}
///|
pub fn layer(name : String) -> When {
when([@ir.layer(name)])
}
///|
pub fn unsafe_pseudo(value : String) -> When {
when([@ir.unsafe_pseudo(value)])
}
///|
pub fn unsafe_pseudo_element(value : String) -> When {
when([@ir.unsafe_pseudo_element(value)])
}
///|
pub fn unsafe_at_rule(value : String) -> When {
when([@ir.unsafe_at_rule(value)])
}
///|
pub(all) struct PropertyInput {
css : String
dynamic : Bool
variable : @values.VariableUse?
value_origin : @ir.DeclarationValueOrigin
expression : @values.Expression?
}
///|
pub trait LengthInput {
fn length_input(Self) -> PropertyInput
}
///|
pub trait ColorInput {
fn color_input(Self) -> PropertyInput
}
///|
pub trait NumberInput {
fn number_input(Self) -> PropertyInput
}
///|
pub trait PercentageInput {
fn percentage_input(Self) -> PropertyInput
}
///|
pub trait LengthPercentageInput {
fn length_percentage_input(Self) -> PropertyInput
}
///|
pub trait IntegerInput {
fn integer_input(Self) -> PropertyInput
}
///|
pub trait TimeInput {
fn time_input(Self) -> PropertyInput
}
///|
pub trait AngleInput {
fn angle_input(Self) -> PropertyInput
}
///|
pub trait ResolutionInput {
fn resolution_input(Self) -> PropertyInput
}
///|
pub trait UrlInput {
fn url_input(Self) -> PropertyInput
}
///|
fn static_input(css : String) -> PropertyInput {
{
css,
dynamic: false,
variable: None,
value_origin: @ir.LiteralOrigin([css]),
expression: None,
}
}
///|
fn contract_variable(value : @values.VariableUse) -> @ir.ContractVariable {
@ir.contract_variable(value.definition.name, value.definition.category.name())
}
///|
fn[T] contract_token(value : @values.Token[T]) -> @ir.ContractVariable {
contract_variable(value.variable_use())
}
///|
fn[T] contract_slot(value : @values.DynamicSlot[T]) -> @ir.ContractVariable {
contract_variable(value.variable_use())
}
///|
fn[T] token_input(value : @values.Token[T]) -> PropertyInput {
let variable = value.variable_use()
{
css: value.reference_css(),
dynamic: false,
variable: Some(variable),
value_origin: @ir.TokenOrigin(contract_variable(variable)),
expression: None,
}
}
///|
fn[T] slot_input(value : @values.DynamicSlot[T]) -> PropertyInput {
let variable = value.variable_use()
{
css: value.variable_name(),
dynamic: true,
variable: Some(variable),
value_origin: @ir.DynamicSlotOrigin(contract_variable(variable)),
expression: None,
}
}
///|
pub impl LengthInput for @values.Length with fn length_input(self) {
static_input(self.to_css())
}
///|
pub impl LengthInput for @values.Token[@values.Length] with fn length_input(
self,
) {
token_input(self)
}
///|
pub impl LengthInput for @values.DynamicSlot[@values.Length] with fn length_input(
self,
) {
slot_input(self)
}
///|
pub impl ColorInput for @values.Color with fn color_input(self) {
static_input(self.to_css())
}
///|
pub impl ColorInput for @values.Token[@values.Color] with fn color_input(self) {
token_input(self)
}
///|
pub impl ColorInput for @values.DynamicSlot[@values.Color] with fn color_input(
self,
) {
slot_input(self)
}
///|
pub impl NumberInput for @values.Number with fn number_input(self) {
static_input(self.to_css())
}
///|
pub impl NumberInput for @values.Token[@values.Number] with fn number_input(
self,
) {
token_input(self)
}
///|
pub impl NumberInput for @values.DynamicSlot[@values.Number] with fn number_input(
self,
) {
slot_input(self)
}
///|
pub impl PercentageInput for @values.Percentage with fn percentage_input(self) {
static_input(self.to_css())
}
///|
pub impl PercentageInput for @values.Token[@values.Percentage] with fn percentage_input(
self,
) {
token_input(self)
}
///|
pub impl PercentageInput for @values.DynamicSlot[@values.Percentage] with fn percentage_input(
self,
) {
slot_input(self)
}
///|
pub impl LengthPercentageInput for @values.LengthPercentage with fn length_percentage_input(
self,
) {
static_input(self.to_css())
}
///|
pub impl LengthPercentageInput for @values.Token[@values.LengthPercentage] with fn length_percentage_input(
self,
) {
token_input(self)
}
///|
pub impl LengthPercentageInput for @values.DynamicSlot[@values.LengthPercentage] with fn length_percentage_input(
self,
) {
slot_input(self)
}
///|
pub impl IntegerInput for @values.Integer with fn integer_input(self) {
static_input(self.to_css())
}
///|
pub impl IntegerInput for @values.Token[@values.Integer] with fn integer_input(
self,
) {
token_input(self)
}
///|
pub impl IntegerInput for @values.DynamicSlot[@values.Integer] with fn integer_input(
self,
) {
slot_input(self)
}
///|
pub impl TimeInput for @values.Time with fn time_input(self) {
static_input(self.to_css())
}
///|
pub impl TimeInput for @values.Token[@values.Time] with fn time_input(self) {
token_input(self)
}
///|
pub impl TimeInput for @values.DynamicSlot[@values.Time] with fn time_input(
self,
) {
slot_input(self)
}
///|
pub impl AngleInput for @values.Angle with fn angle_input(self) {
static_input(self.to_css())
}
///|
pub impl AngleInput for @values.Token[@values.Angle] with fn angle_input(self) {
token_input(self)
}
///|
pub impl AngleInput for @values.DynamicSlot[@values.Angle] with fn angle_input(
self,
) {
slot_input(self)
}
///|
pub impl ResolutionInput for @values.Resolution with fn resolution_input(self) {
static_input(self.to_css())
}
///|
pub impl ResolutionInput for @values.Token[@values.Resolution] with fn resolution_input(
self,
) {
token_input(self)
}
///|
pub impl ResolutionInput for @values.DynamicSlot[@values.Resolution] with fn resolution_input(
self,
) {
slot_input(self)
}
///|
pub impl UrlInput for @values.Url with fn url_input(self) {
static_input(self.to_css())
}
///|
pub impl UrlInput for @values.Token[@values.Url] with fn url_input(self) {
token_input(self)
}
///|
pub impl UrlInput for @values.DynamicSlot[@values.Url] with fn url_input(self) {
slot_input(self)
}
///|
pub struct Rule {
name : String
declarations : Array[@ir.Declaration]
variables : Array[@values.VariableUse]
}
///|
/// Resolves an explicitly supplied span first, then falls back to the compiler
/// callsite captured by `#callsite`. Keeping this in the builder means normal
/// MoonBit authoring receives actionable diagnostics without macros or a
/// separate source transform.
fn resolve_source_span(
source : @ir.SourceSpan?,
location : SourceLoc,
symbol : String,
) -> @ir.SourceSpan? {
match source {
Some(value) => Some(value)
None => {
let value = @json.parse(location.to_json_string()) catch {
_ => return None
}
guard value is Object(object) &&
object.get("filename") is Some(String(file)) &&
object.get("start_line") is Some(Number(start_line, ..)) &&
object.get("start_column") is Some(Number(start_column, ..)) &&
object.get("end_line") is Some(Number(end_line, ..)) &&
object.get("end_column") is Some(Number(end_column, ..)) else {
return None
}
Some(
@ir.source_span(
file,
start_line.to_int(),
start_column.to_int(),
end_line=end_line.to_int(),
end_column=end_column.to_int(),
symbol~,
),
)
}
}
}
///|
pub struct StyleContract {
name : String
properties : Array[@ir.ContractProperty]
}
///|
pub fn style_contract(name : String) -> StyleContract {
{ name, properties: [], }
}
///|
fn StyleContract::allow_property(
self : StyleContract,
property : String,
values~ : Array[String] = [],
tokens~ : Array[@ir.ContractVariable] = [],
slots~ : Array[@ir.ContractVariable] = [],
expressions~ : Array[String] = [],
) -> StyleContract {
let properties = self.properties.copy()
if values.is_empty() &&
tokens.is_empty() &&
slots.is_empty() &&
expressions.is_empty() {
properties.push(@ir.any_contract_property(property))
} else {
properties.push(
@ir.exact_contract_property(
property,
values~,
tokens~,
slots~,
expressions~,
),
)
}
{ name: self.name, properties, }
}
///|
pub fn StyleContract::to_ir(self : StyleContract) -> @ir.StyleContract {
@ir.style_contract(self.name, self.properties)
}
///|
pub fn rule(name : String) -> Rule {
{ name, declarations: [], variables: [], }
}
///|
fn Rule::append_declaration(
self : Rule,
declaration : @ir.Declaration,
variable~ : @values.VariableUse? = None,
) -> Rule {
let declarations = self.declarations.copy()
declarations.push(declaration)
let variables = self.variables.copy()
for condition in declaration.conditions {
if condition is @ir.ContainerStyle(query) {
for variable in query.variables() {
variables.push(variable)
}
}
}
match variable {
Some(value) => variables.push(value)
None => ()
}
{ name: self.name, declarations, variables, }
}
///|
fn Rule::append_input(
self : Rule,
property : String,
input : PropertyInput,
when_ : When,
source : @ir.SourceSpan?,
suffix : String,
keyframe_allowed : Bool,
) -> Rule {
let invalid_default = if property == "fontWidth" || property == "font-width" {
match input.variable {
Some(variable) =>
if @values.property_value_issues(
property,
variable.definition.default_css,
).is_empty() {
None
} else {
Some(variable.definition.default_css)
}
_ => None
}
} else {
None
}
let value = if input.expression is Some(expression) {
@ir.Expression(expression)
} else if invalid_default is Some(css) {
// Keep invalid defaults visible to the ordinary declaration diagnostics,
// including when a rule is compiled without a registry.
@ir.text(css)
} else if input.dynamic {
let category = match input.variable {
Some(variable) => variable.definition.category.name()
None => "string"
}
@ir.dynamic(input.css, suffix, category~)
} else {
@ir.text(input.css)
}
let next = self.append_declaration(
@ir.declaration(
property,
value,
conditions=when_.to_conditions(),
source~,
keyframe_allowed~,
value_origin=Some(input.value_origin),
),
variable=input.variable,
)
if input.expression is Some(expression) {
for use_ in expression.variables() {
next.variables.push(use_.variable)
}
}
next
}
///|
fn Rule::append_fallbacks(
self : Rule,
property : String,
values : Array[String],
when_ : When,
source : @ir.SourceSpan?,
) -> Rule {
self.append_declaration(
@ir.declaration(
property,
@ir.fallbacks(values),
conditions=when_.to_conditions(),
source~,
keyframe_allowed=true,
value_origin=Some(@ir.LiteralOrigin(values.copy())),
),
)
}
///|
fn Rule::append_clear(
self : Rule,
property : String,
source : @ir.SourceSpan?,
) -> Rule {
self.append_declaration(
@ir.declaration(
property,
@ir.clear_value(),
source~,
value_origin=Some(@ir.ClearOrigin),
),
)
}
///|
fn[T : AnimationInput] Rule::append_animation(
self : Rule,
property : String,
animation : T,
when_ : When,
source : @ir.SourceSpan?,
) -> Rule {
let animation = animation.animation_input()
let next = self.append_declaration(
@ir.declaration(
property,
animation.value,
conditions=when_.to_conditions(),
source~,
),
)
let variables = next.variables.copy()
for variable in animation.variables {
variables.push(variable)
}
{ name: next.name, declarations: next.declarations, variables, }
}
///|
#callsite(autofill(location))
pub fn Rule::unsafe_raw(
self : Rule,
property : String,
value : String,
when~ : When = always,
source~ : @ir.SourceSpan? = None,
location~ : SourceLoc,
) -> Rule {
let source = resolve_source_span(source, location, property)
self.append_declaration(
@ir.declaration(
property,
@ir.text(value),
conditions=when.to_conditions(),
source~,
unsafe_raw=true,
),
)
}
///|
pub fn Rule::to_namespace(self : Rule) -> @ir.Namespace {
@ir.style_namespace(self.name, self.declarations)
}
///|
pub fn Rule::variable_uses(self : Rule) -> Array[@values.VariableUse] {
self.variables.copy()
}
///|
pub struct Frame {
step : @ir.KeyframeStep
rule : Rule
}
///|
pub fn frame(step : @ir.KeyframeStep) -> Frame {
{ step, rule: rule("keyframe"), }
}
///|
pub struct AnimationName {
value : @ir.AnimationName
variables : Array[@values.VariableUse]
}
///|
pub fn keyframes(name_hint : String, frames : Array[Frame]) -> AnimationName {
let lowered : Array[@ir.KeyframeFrame] = []
let variables : Array[@values.VariableUse] = []
for value in frames {
lowered.push(@ir.keyframe_frame(value.step, value.rule.declarations))
for variable in value.rule.variables {
variables.push(variable)
}
}
{ value: @ir.keyframes(name_hint, lowered), variables, }
}
///|
fn Frame::with_rule(self : Frame, next : Rule) -> Frame {
{ step: self.step, rule: next, }
}
///|
pub fn AnimationName::to_ir(self : AnimationName) -> @ir.AnimationName {
self.value
}
///|
pub fn AnimationName::variable_uses(
self : AnimationName,
) -> Array[@values.VariableUse] {
self.variables.copy()
}
///|
pub struct Theme {
name : String
variables : Array[@values.VariableUse]
overrides : Array[@values.VarOverride]
}
///|
pub fn theme(name : String, values : Array[@values.TokenOverride]) -> Theme {
{
name,
variables: values.map(value => value.variable),
overrides: values.map(value => value.override_),
}
}
///|
pub fn Theme::name(self : Theme) -> String {
self.name
}
///|
pub fn Theme::variable_uses(self : Theme) -> Array[@values.VariableUse] {
self.variables.copy()
}
///|
pub fn Theme::overrides(self : Theme) -> Array[@values.VarOverride] {
self.overrides.copy()
}