silex 0.1.0-beta.1

Next Generation High-Performance Rust Web Framework based on fine-grained reactivity and no-virtual-DOM architecture.
Documentation
use silex_core::traits::{Get, IntoSignal, With};
use silex_dom::attribute::{ApplyTarget, ApplyToDom, IntoStorable};
use silex_dom::document;
use std::fmt::Display;
use std::rc::Rc;
use wasm_bindgen::JsCast;

/// Injects a CSS string into the document head with a unique ID.
/// This function is idempotent: if a style with the given ID already exists, it does nothing.
///
/// # Arguments
///
/// * `id` - A unique identifier for the style block (e.g. "style-slx-123456").
/// * `content` - The CSS content to inject.
pub fn inject_style(id: &str, content: &str) {
    let doc = document();

    // Check if style already exists to avoid duplication
    if doc.get_element_by_id(id).is_some() {
        return;
    }

    let head = doc.head().expect("No <head> element found in document");

    // Create <style> element
    let style_el = doc
        .create_element("style")
        .expect("Failed to create style element");

    // Set ID and content
    style_el.set_id(id);
    // style_el.set_attribute("type", "text/css").unwrap(); // Optional in HTML5
    style_el.set_inner_html(content);

    // Append to head
    let style_node: web_sys::Node = style_el.unchecked_into();
    head.append_child(&style_node)
        .expect("Failed to append style to head");
}

/// A structure representing a dynamic CSS class with reactive variables.
/// Generated by the `css!` macro when dynamic interpolation `$(...)` is used.
#[derive(Clone)]
pub struct DynamicCss {
    /// The generated class name (e.g., "slx-1234abcd")
    pub class_name: &'static str,
    /// A list of (css_variable_name, value_getter) pairs.
    /// These are applied as inline styles to the element.
    pub vars: Vec<(&'static str, Rc<dyn Fn() -> String>)>,
}

impl ApplyToDom for DynamicCss {
    fn apply(self, el: &web_sys::Element, target: ApplyTarget) {
        // 1. Apply class name (as normal string class)
        // This ensures the element gets the static CSS rules.
        self.class_name.apply(el, target);

        // 2. Apply dynamic variables (always as inline styles)
        for (name, getter) in self.vars {
            let el = el.clone();
            let name = name.to_string(); // 'static -> String for closure capture

            // Create an effect to keep the CSS variable in sync with the signal/expression
            silex_core::reactivity::Effect::new(move |_| {
                let value = getter();
                // Attempt to get style declaration from HtmlElement or SvgElement
                if let Some(style) = el
                    .dyn_ref::<web_sys::HtmlElement>()
                    .map(|e| e.style())
                    .or_else(|| el.dyn_ref::<web_sys::SvgElement>().map(|e| e.style()))
                {
                    // Set the CSS variable (e.g., --slx-1234-0: red)
                    let _ = style.set_property(&name, &value);
                }
            });
        }
    }
}

// Allow passing DynamicCss directly to .class() or .attr()
impl IntoStorable for DynamicCss {
    type Stored = Self;
    fn into_storable(self) -> Self::Stored {
        self
    }
}

/// Helper function to create a reactive string getter from any signal-like value.
/// Used by the css! macro to handle $(...) interpolation.
pub fn make_dynamic_val<S>(source: S) -> Rc<dyn Fn() -> String>
where
    S: IntoSignal,
    S::Value: Clone + Sized, // Required for Get
    S::Signal: Get + 'static,
    <S::Signal as With>::Value: Display,
{
    let signal = source.into_signal();
    Rc::new(move || format!("{}", signal.get()))
}