euv-core 0.4.1

A declarative, cross-platform UI framework for Rust with virtual DOM, reactive signals, and HTML macros for WebAssembly.
Documentation
use crate::*;

/// Creates a reactive style `AttributeValue` that updates when signals change.
///
/// This function replaces the inline `Signal::create(...)` + `subscribe_attr_signal(...)`
/// boilerplate that was previously generated by the `html!` macro for every
/// `style:` attribute containing reactive `if` conditions.
///
/// # Arguments
///
/// - `Fn() -> String + 'static` - A closure that computes the current CSS string.
///   Called on initial render and whenever any signal changes.
///
/// # Returns
///
/// - `AttributeValue` - A `AttributeValue::Signal` backed by a `Signal<String>`
///   that reactively re-evaluates the CSS string on signal updates.
pub fn create_reactive_style_attribute<F>(compute: F) -> AttributeValue
where
    F: Fn() -> String + 'static,
{
    let attr_signal: Signal<String> = Signal::create(compute());
    subscribe_attr_signal(attr_signal, compute);
    AttributeValue::Signal(attr_signal)
}

/// Creates a reactive attribute `AttributeValue` for conditional attribute values.
///
/// This function replaces the inline `Signal::create(...)` + `subscribe_attr_signal(...)`
/// boilerplate that was previously generated by the `html!` macro for every
/// attribute value containing an `if` condition.
///
/// # Arguments
///
/// - `Fn() -> String + 'static` - A closure that computes the current attribute value.
///   Called on initial render and whenever any signal changes.
///
/// # Returns
///
/// - `AttributeValue` - A `AttributeValue::Signal` backed by a `Signal<String>`
///   that reactively re-evaluates the attribute value on signal updates.
pub fn create_reactive_attr_signal<F>(compute: F) -> AttributeValue
where
    F: Fn() -> String + 'static,
{
    let attr_signal: Signal<String> =
        Signal::create(IntoReactiveString::into_reactive_string(compute()));
    subscribe_attr_signal(attr_signal, move || {
        IntoReactiveString::into_reactive_string(compute())
    });
    AttributeValue::Signal(attr_signal)
}

/// Merges multiple class attribute values into a single `AttributeValue`.
///
/// Each input value is adapted into an `AttributeValue` via `IntoReactiveValue`.
/// `CssClass` values are injected into the DOM and their names are collected.
/// All non-empty class names are joined with spaces into a final `Text` attribute.
/// If any value is signal-backed, the result becomes a reactive `Signal` attribute
/// that re-evaluates when any constituent signal changes.
///
/// # Arguments
///
/// - `&[AttributeValue]` - The class attribute values to merge.
///
/// # Returns
///
/// - `AttributeValue` - A merged attribute value containing space-separated class names.
pub fn merge_class_values(values: &[AttributeValue]) -> AttributeValue {
    let has_signal: bool = values
        .iter()
        .any(|value: &AttributeValue| matches!(value, AttributeValue::Signal(_)));
    if has_signal {
        let owned_values: Vec<AttributeValue> = values.to_vec();
        let compute = move || {
            let mut result: String = String::new();
            for value in &owned_values {
                let class_segment: String = match value {
                    AttributeValue::Css(css_class) => {
                        css_class.inject_style();
                        css_class.get_name().to_string()
                    }
                    AttributeValue::Text(text_value) => text_value.clone(),
                    AttributeValue::Signal(signal) => signal.get(),
                    _ => String::new(),
                };
                if !class_segment.is_empty() {
                    if !result.is_empty() {
                        result.push(' ');
                    }
                    result.push_str(&class_segment);
                }
            }
            result
        };
        let attr_signal: Signal<String> = Signal::create(compute());
        subscribe_attr_signal(attr_signal, compute);
        AttributeValue::Signal(attr_signal)
    } else {
        let mut result: String = String::new();
        for value in values {
            let class_segment: String = match value {
                AttributeValue::Css(css_class) => {
                    css_class.inject_style();
                    css_class.get_name().to_string()
                }
                AttributeValue::Text(text_value) => text_value.clone(),
                _ => String::new(),
            };
            if !class_segment.is_empty() {
                if !result.is_empty() {
                    result.push(' ');
                }
                result.push_str(&class_segment);
            }
        }
        AttributeValue::Text(result)
    }
}

/// Merges multiple style attribute values into a single `AttributeValue`.
///
/// Each input value is expected to be a style string (`Text`) or a reactive
/// `Signal<String>` producing a style string. All non-empty style strings are
/// joined with spaces into a final combined style attribute.
/// If any value is signal-backed, the result becomes a reactive `Signal` attribute.
///
/// # Arguments
///
/// - `&[AttributeValue]` - The style attribute values to merge.
///
/// # Returns
///
/// - `AttributeValue` - A merged attribute value containing the combined CSS style string.
pub fn merge_style_values(values: &[AttributeValue]) -> AttributeValue {
    let has_signal: bool = values
        .iter()
        .any(|value: &AttributeValue| matches!(value, AttributeValue::Signal(_)));
    if has_signal {
        let owned_values: Vec<AttributeValue> = values.to_vec();
        let compute = move || {
            let mut result: String = String::new();
            for value in &owned_values {
                let style_segment: String = match value {
                    AttributeValue::Text(text_value) => text_value.clone(),
                    AttributeValue::Signal(signal) => signal.get(),
                    _ => String::new(),
                };
                if !style_segment.is_empty() {
                    if !result.is_empty() {
                        result.push(' ');
                    }
                    result.push_str(&style_segment);
                }
            }
            result
        };
        let attr_signal: Signal<String> = Signal::create(compute());
        subscribe_attr_signal(attr_signal, compute);
        AttributeValue::Signal(attr_signal)
    } else {
        let mut result: String = String::new();
        for value in values {
            let style_segment: String = match value {
                AttributeValue::Text(text_value) => text_value.clone(),
                _ => String::new(),
            };
            if !style_segment.is_empty() {
                if !result.is_empty() {
                    result.push(' ');
                }
                result.push_str(&style_segment);
            }
        }
        AttributeValue::Text(result)
    }
}

/// Inserts a class name into the injected classes set.
///
/// # Arguments
///
/// - `String`: The class name to insert.
///
/// # Returns
///
/// - `bool`: `true` if newly inserted, `false` if already present.
#[allow(static_mut_refs)]
pub(crate) fn mark_injected_class(class_name: String) -> bool {
    unsafe {
        if (*INJECTED_CLASSES.get_0().get()).is_none() {
            (*INJECTED_CLASSES.get_0().get()) = Some(HashSet::new());
        }
        (*INJECTED_CLASSES.get_0().get())
            .as_mut()
            .unwrap_unchecked()
            .insert(class_name)
    }
}