euv-core 0.3.34

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

/// Visual equality comparison for text nodes.
///
/// Only compares the text content; the backing signal is not considered
/// because it does not affect visual output.
impl PartialEq for TextNode {
    fn eq(&self, other: &Self) -> bool {
        self.get_content() == other.get_content()
    }
}

/// Visual equality comparison for virtual DOM nodes.
///
/// Used by DynamicNode re-rendering to skip unnecessary DOM patches when
/// the rendered output has not changed. Event attributes are always
/// considered equal because re-binding event listeners is handled
/// separately by the handler registry and does not affect visual output.
/// Dynamic nodes manage their own subtree re-rendering, so two Dynamic
/// variants are always considered equal — the inner renderer handles
/// patching when the dynamic content actually changes.
impl PartialEq for VirtualNode {
    fn eq(&self, other: &Self) -> bool {
        match (self, other) {
            (VirtualNode::Text(old_text), VirtualNode::Text(new_text)) => old_text == new_text,
            (
                VirtualNode::Element {
                    tag: old_tag,
                    attributes: old_attrs,
                    children: old_children,
                    ..
                },
                VirtualNode::Element {
                    tag: new_tag,
                    attributes: new_attrs,
                    children: new_children,
                    ..
                },
            ) => {
                old_tag == new_tag
                    && old_attrs.len() == new_attrs.len()
                    && old_attrs
                        .iter()
                        .zip(new_attrs.iter())
                        .all(|(old_attr, new_attr)| old_attr == new_attr)
                    && old_children.len() == new_children.len()
                    && old_children
                        .iter()
                        .zip(new_children.iter())
                        .all(|(old_child, new_child)| old_child == new_child)
            }
            (VirtualNode::Fragment(old_children), VirtualNode::Fragment(new_children)) => {
                old_children.len() == new_children.len()
                    && old_children
                        .iter()
                        .zip(new_children.iter())
                        .all(|(old_child, new_child)| old_child == new_child)
            }
            (VirtualNode::Dynamic(_), VirtualNode::Dynamic(_)) => false,
            (VirtualNode::Empty, VirtualNode::Empty) => true,
            _ => false,
        }
    }
}

/// Provides a default empty dynamic node with a no-op render function.
impl Default for DynamicNode {
    fn default() -> Self {
        let render_fn_inner: Rc<RefCell<RenderFnInner>> =
            Rc::new(RefCell::new(RenderFnInner::new(Box::new(|| {
                VirtualNode::Empty
            }))));
        DynamicNode::new(render_fn_inner, HookContext::default())
    }
}

/// Clones a `DynamicNode` by cloning the shared references.
impl Clone for DynamicNode {
    fn clone(&self) -> Self {
        let cloned: DynamicNode = DynamicNode::new(
            self.get_render_fn().clone(),
            self.get_hook_context().clone(),
        );
        cloned
    }
}

/// Implementation of dynamic node accessor methods.
impl DynamicNode {
    /// Returns the hook context for this dynamic node.
    ///
    /// # Returns
    ///
    /// - `HookContext` - The hook context.
    pub(crate) fn get_hook_context_value(&self) -> HookContext {
        self.get_hook_context().clone()
    }

    /// Invokes the render closure and returns the produced virtual node.
    ///
    /// # Returns
    ///
    /// - `VirtualNode` - The virtual node produced by the render closure.
    pub fn render(&self) -> VirtualNode {
        let mut inner: RefMut<RenderFnInner> = self.get_render_fn().borrow_mut();
        (inner.get_mut_render_fn())()
    }
}

/// Implementation of virtual node construction and property extraction.
impl VirtualNode {
    /// Creates a new element node with the given tag name.
    ///
    /// # Arguments
    ///
    /// - `&str`: The HTML tag name.
    ///
    /// # Returns
    ///
    /// - `VirtualNode`: A new element virtual node.
    pub fn get_element_node(tag_name: &str) -> Self {
        VirtualNode::Element {
            tag: Tag::Element(tag_name.to_string()),
            attributes: Vec::new(),
            children: Vec::new(),
            key: None,
        }
    }

    /// Creates a new text node with the given content.
    ///
    /// # Arguments
    ///
    /// - `&str`: The text content.
    ///
    /// # Returns
    ///
    /// - `VirtualNode`: A new text virtual node.
    pub fn get_text_node(content: &str) -> Self {
        VirtualNode::Text(TextNode::new(content.to_string(), None))
    }

    /// Adds an attribute to this node if it is an element.
    ///
    /// # Arguments
    ///
    /// - `&str`: The attribute name.
    /// - `AttributeValue`: The attribute value.
    ///
    /// # Returns
    ///
    /// - `Self`: This node with the attribute added.
    pub fn with_attribute(mut self, name: &str, value: AttributeValue) -> Self {
        if let VirtualNode::Element {
            ref mut attributes, ..
        } = self
        {
            attributes.push(AttributeEntry::new(name.to_string(), value));
        }
        self
    }

    /// Adds a child node to this node if it is an element.
    ///
    /// # Arguments
    ///
    /// - `VirtualNode`: The child node to add.
    ///
    /// # Returns
    ///
    /// - `Self`: This node with the child added.
    pub fn with_child(mut self, child: VirtualNode) -> Self {
        if let VirtualNode::Element {
            ref mut children, ..
        } = self
        {
            children.push(child);
        }
        self
    }

    /// Returns true if this node is a component node.
    ///
    /// # Returns
    ///
    /// - `bool`: `true` if this is a component element.
    pub fn is_component(&self) -> bool {
        matches!(
            self,
            VirtualNode::Element {
                tag: Tag::Component(_),
                ..
            }
        )
    }

    /// Returns the tag name if this is an element or component node.
    ///
    /// # Returns
    ///
    /// - `Option<String>`: The tag name, or `None` if not an element.
    pub fn tag_name(&self) -> Option<String> {
        match self {
            VirtualNode::Element { tag, .. } => match tag {
                Tag::Element(name) => Some(name.clone()),
                Tag::Component(name) => Some(name.clone()),
            },
            _ => None,
        }
    }

    /// Extracts a string property from this node if it is an element with the named attribute.
    ///
    /// # Arguments
    ///
    /// - `&str`: The attribute name to look for.
    ///
    /// # Returns
    ///
    /// - `Option<String>`: The attribute value as a string, or `None`.
    pub fn try_get_prop(&self, name: &str) -> Option<String> {
        if let VirtualNode::Element { attributes, .. } = self {
            for attr in attributes {
                if attr.get_name() == name {
                    match attr.get_value() {
                        AttributeValue::Text(value) => return Some(value.clone()),
                        AttributeValue::Signal(signal) => return Some(signal.get()),
                        AttributeValue::Dynamic(value) => return Some(value.clone()),
                        _ => {}
                    }
                }
            }
        }
        None
    }

    /// Extracts a typed property from this node by parsing the attribute value string.
    ///
    /// # Arguments
    ///
    /// - `&str`: The attribute name to look for.
    ///
    /// # Returns
    ///
    /// - `Option<T>`: The parsed value, or `None` if not found or parsing fails.
    pub fn try_get_typed_prop<T>(&self, name: &str) -> Option<T>
    where
        T: std::str::FromStr,
    {
        if let VirtualNode::Element { attributes, .. } = self {
            for attr in attributes {
                if attr.get_name() == name {
                    let raw: String = match attr.get_value() {
                        AttributeValue::Text(value) => value.clone(),
                        AttributeValue::Signal(signal) => signal.get(),
                        AttributeValue::Dynamic(value) => value.clone(),
                        _ => continue,
                    };
                    return raw.parse::<T>().ok();
                }
            }
        }
        None
    }

    /// Extracts a signal property from this node if it is an element with the named attribute.
    ///
    /// # Arguments
    ///
    /// - `&str`: The attribute name to look for.
    ///
    /// # Returns
    ///
    /// - `Option<Signal<String>>`: The signal, or `None` if not found.
    pub fn try_get_signal_prop(&self, name: &str) -> Option<Signal<String>> {
        if let VirtualNode::Element { attributes, .. } = self {
            for attr in attributes {
                if attr.get_name() == name
                    && let AttributeValue::Signal(signal) = attr.get_value()
                {
                    return Some(*signal);
                }
            }
        }
        None
    }

    /// Extracts children from this node if it is an element.
    ///
    /// # Returns
    ///
    /// - `Vec<VirtualNode>`: The children, or an empty vec if not an element.
    pub fn get_children(&self) -> Vec<VirtualNode> {
        if let VirtualNode::Element { children, .. } = self {
            children.clone()
        } else {
            Vec::new()
        }
    }

    /// Extracts text content from this node.
    ///
    /// # Returns
    ///
    /// - `Option<String>`: The text content, or `None` if unavailable.
    pub fn try_get_text(&self) -> Option<String> {
        match self {
            VirtualNode::Text(text_node) => Some(text_node.get_content().clone()),
            VirtualNode::Element { children, .. } => {
                children.first().and_then(VirtualNode::try_get_text)
            }
            _ => None,
        }
    }

    /// Extracts an event handler from this node if it is an element with the named event attribute.
    ///
    /// # Arguments
    ///
    /// - `&str`: The event attribute name to look for.
    ///
    /// # Returns
    ///
    /// - `Option<NativeEventHandler>`: The handler, or `None` if not found.
    pub fn try_get_event(&self, name: &str) -> Option<NativeEventHandler> {
        if let VirtualNode::Element { attributes, .. } = self {
            for attr in attributes {
                if attr.get_name() == name
                    && let AttributeValue::Event(handler) = attr.get_value()
                {
                    return Some(handler.clone());
                }
            }
        }
        None
    }

    /// Extracts an event handler from this node by a custom attribute name.
    ///
    /// # Arguments
    ///
    /// - `&str`: The custom callback attribute name to look for.
    ///
    /// # Returns
    ///
    /// - `Option<NativeEventHandler>`: The handler, or `None` if not found.
    pub fn try_get_callback(&self, name: &str) -> Option<NativeEventHandler> {
        if let VirtualNode::Element { attributes, .. } = self {
            for attr in attributes {
                if attr.get_name() == name
                    && let AttributeValue::Event(handler) = attr.get_value()
                {
                    return Some(handler.clone());
                }
            }
        }
        None
    }
}