silkenweb 0.10.0

A library for building web apps
Documentation
use std::{cell::RefCell, fmt, rc::Rc};

use html_escape::encode_text_minimal;
use wasm_bindgen::JsValue;

use super::{
    private::{self, EventStore, InstantiableDomElement},
    wet::WetElement,
    Dry,
};
use crate::node::element::Namespace;

mod shared_element;

pub use shared_element::SharedDryElement;

#[derive(Clone)]

pub struct DryElement(Rc<RefCell<SharedDryElement<DryNode>>>);

impl DryElement {
    fn from_shared(shared: SharedDryElement<DryNode>) -> Self {
        Self(Rc::new(RefCell::new(shared)))
    }
}

impl private::DomElement for DryElement {
    type Node = DryNode;

    fn new(ns: &Namespace, tag: &str) -> Self {
        Self::from_shared(SharedDryElement::new(ns.clone(), tag))
    }

    fn append_child(&mut self, child: &Self::Node) {
        self.0.borrow_mut().append_child(child)
    }

    fn insert_child_before(
        &mut self,
        index: usize,
        child: &Self::Node,
        next_child: Option<&Self::Node>,
    ) {
        self.0
            .borrow_mut()
            .insert_child_before(index, child, next_child)
    }

    fn replace_child(&mut self, index: usize, new_child: &Self::Node, old_child: &Self::Node) {
        self.0
            .borrow_mut()
            .replace_child(index, new_child, old_child)
    }

    fn remove_child(&mut self, index: usize, child: &Self::Node) {
        self.0.borrow_mut().remove_child(index, child)
    }

    fn clear_children(&mut self) {
        self.0.borrow_mut().clear_children()
    }

    fn add_class(&mut self, name: &str) {
        self.0.borrow_mut().add_class(name)
    }

    fn remove_class(&mut self, name: &str) {
        self.0.borrow_mut().remove_class(name)
    }

    fn attribute<A>(&mut self, name: &str, value: A)
    where
        A: crate::attribute::Attribute,
    {
        self.0.borrow_mut().attribute(name, value)
    }

    fn on(
        &mut self,
        name: &'static str,
        f: impl FnMut(JsValue) + 'static,
        events: &mut EventStore,
    ) {
        self.0.borrow_mut().on(name, f, events)
    }

    fn dom_element(&self) -> web_sys::Element {
        self.try_dom_element()
            .expect("Can't get raw dom element from `Dry` dom element")
    }

    fn try_dom_element(&self) -> Option<web_sys::Element> {
        self.0.borrow().try_dom_element()
    }

    fn style_property(&mut self, name: &str, value: &str) {
        self.0.borrow_mut().style_property(name, value)
    }

    fn effect(&mut self, f: impl FnOnce(&web_sys::Element) + 'static) {
        self.0.borrow_mut().effect(f)
    }

    fn observe_attributes(
        &mut self,
        f: impl FnMut(js_sys::Array, web_sys::MutationObserver) + 'static,
        events: &mut EventStore,
    ) {
        self.0.borrow_mut().observe_attributes(f, events)
    }
}

impl private::InstantiableDomElement for DryElement {
    fn attach_shadow_children(&mut self, children: impl IntoIterator<Item = Self::Node>) {
        self.0.borrow_mut().attach_shadow_children(children)
    }

    fn clone_node(&self) -> Self {
        Self::from_shared(self.0.borrow().clone_node())
    }
}

impl fmt::Display for DryElement {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        self.0.borrow().fmt(f)
    }
}

#[derive(Clone)]
pub struct DryText(Rc<RefCell<SharedDryText<DryNode>>>);

impl DryText {
    pub fn clone_node(&self) -> Self {
        Self(Rc::new(RefCell::new(self.0.borrow().clone_node())))
    }
}

impl fmt::Display for DryText {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        self.0.borrow().fmt(f)
    }
}

impl private::DomText for DryText {
    fn new(text: &str) -> Self {
        Self(Rc::new(RefCell::new(SharedDryText::new(text.to_string()))))
    }

    fn set_text(&mut self, text: &str) {
        self.0.borrow_mut().set_text(text.to_string())
    }
}

#[derive(Clone)]
pub enum DryNode {
    Element(DryElement),
    Text(DryText),
}

impl private::InstantiableDomNode for DryNode {
    type DomType = Dry;

    fn into_element(self) -> <Self::DomType as private::Dom>::Element {
        match self {
            DryNode::Element(element) => element,
            DryNode::Text(_) => panic!("Text node when expecting element"),
        }
    }

    fn first_child(&self) -> Self {
        match self {
            DryNode::Element(element) => element
                .0
                .borrow()
                .first_child()
                .expect("No children")
                .clone(),
            DryNode::Text(_) => panic!("Text nodes can't have children"),
        }
    }

    fn next_sibling(&self) -> Self {
        match self {
            DryNode::Element(element) => element
                .0
                .borrow()
                .next_sibling()
                .expect("This is the last child")
                .clone(),
            DryNode::Text(text) => text
                .0
                .borrow()
                .next_sibling()
                .expect("This is the last child")
                .clone(),
        }
    }
}

impl From<DryElement> for DryNode {
    fn from(value: DryElement) -> Self {
        Self::Element(value)
    }
}

impl From<DryText> for DryNode {
    fn from(value: DryText) -> Self {
        Self::Text(value)
    }
}

impl fmt::Display for DryNode {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            DryNode::Element(element) => element.fmt(f),
            DryNode::Text(text) => text.fmt(f),
        }
    }
}

impl DryChild for DryNode {
    fn clone_node(&self) -> Self {
        match self {
            DryNode::Element(element) => DryNode::Element(element.clone_node()),
            DryNode::Text(text) => DryNode::Text(text.clone_node()),
        }
    }

    fn set_next_sibling(&self, next_sibling: Option<&Self>) {
        let next_sibling = next_sibling.cloned();

        match self {
            DryNode::Element(element) => element.0.borrow_mut().set_next_sibling(next_sibling),
            DryNode::Text(text) => text.0.borrow_mut().set_next_sibling(next_sibling),
        }
    }
}

pub trait DryChild: Clone {
    fn clone_node(&self) -> Self;

    fn set_next_sibling(&self, next_sibling: Option<&Self>);
}

pub struct SharedDryText<Node> {
    text: String,
    next_sibling: Option<Node>,
}

impl<Node> SharedDryText<Node> {
    pub fn new(text: String) -> Self {
        Self {
            text,
            next_sibling: None,
        }
    }

    pub fn text(&self) -> &str {
        &self.text
    }

    pub fn set_text(&mut self, text: String) {
        self.text = text;
    }

    pub fn next_sibling(&self) -> Option<&Node> {
        self.next_sibling.as_ref()
    }

    pub fn set_next_sibling(&mut self, next_sibling: Option<Node>) {
        self.next_sibling = next_sibling;
    }

    pub fn clone_node(&self) -> Self {
        Self {
            text: self.text.clone(),
            next_sibling: None,
        }
    }
}

impl<Node> fmt::Display for SharedDryText<Node> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        encode_text_minimal(&self.text).fmt(f)
    }
}

impl<Node> From<SharedDryText<Node>> for String {
    fn from(value: SharedDryText<Node>) -> Self {
        value.text
    }
}

type LazyElementAction = Box<dyn FnOnce(&mut WetElement)>;

#[cfg(test)]
mod tests {
    use silkenweb_macros::cfg_browser;

    #[cfg_browser(false)]
    use crate::task::{render_now, scope};
    use crate::{
        dom::Dry,
        elements::html::*,
        node::element::{ParentElement, ShadowRootParent, TextParentElement},
    };

    #[cfg(feature = "declarative-shadow-dom")]
    #[test]
    fn declarative_shadow_dom() {
        assert_eq!(
            shadow_host().freeze().to_string(),
            r#"<div><template shadowroot="open"><slot></slot></template><h2>Light content</h2></div>"#
        );
    }

    #[cfg(not(feature = "declarative-shadow-dom"))]
    #[test]
    fn declarative_shadow_dom() {
        assert_eq!(
            shadow_host().freeze().to_string(),
            r#"<div><h2>Light content</h2></div>"#
        );
    }

    fn shadow_host() -> Div<Dry> {
        div()
            .attach_shadow_children([slot()])
            .child(h2().text("Light content"))
    }

    #[cfg_browser(false)]
    #[tokio::test]
    async fn style_property() {
        use crate::node::element::Element;

        scope(async {
            let app: Div<Dry> = div()
                .style_property("--test0", "value0")
                .style_property("--test1", "value1");

            render_now().await;

            assert_eq!(
                app.freeze().to_string(),
                r#"<div style="--test0: value0; --test1: value1;"></div>"#
            );
        })
        .await
    }
}