silkenweb 0.10.0

A library for building web apps
Documentation
use std::fmt;

use clonelet::clone;
use silkenweb_base::document;
use wasm_bindgen::{JsCast, JsValue, UnwrapThrowExt};
use web_sys::{ShadowRootInit, ShadowRootMode};

use super::{
    private::{DomElement, DomText, EventStore, InstantiableDomElement, InstantiableDomNode},
    Wet,
};
use crate::{node::element::Namespace, task::on_animation_frame};

#[derive(Clone)]
pub struct WetElement {
    element: web_sys::Element,
}

impl WetElement {
    pub fn from_element(element: web_sys::Element) -> Self {
        Self { element }
    }

    pub fn create_shadow_root(&self) -> web_sys::ShadowRoot {
        self.element.shadow_root().unwrap_or_else(|| {
            self.element
                .attach_shadow(&ShadowRootInit::new(ShadowRootMode::Open))
                .unwrap_throw()
        })
    }
}

impl fmt::Display for WetElement {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_str(&self.element.outer_html())
    }
}

impl DomElement for WetElement {
    type Node = WetNode;

    fn new(ns: &Namespace, tag: &str) -> Self {
        Self {
            element: ns.create_element(tag),
        }
    }

    fn append_child(&mut self, child: &WetNode) {
        self.element.append_child(child.dom_node()).unwrap_throw();
    }

    fn insert_child_before(
        &mut self,
        _index: usize,
        child: &WetNode,
        next_child: Option<&WetNode>,
    ) {
        self.element
            .insert_before(child.dom_node(), next_child.map(|c| c.dom_node()))
            .unwrap_throw();
    }

    fn replace_child(&mut self, _index: usize, new_child: &WetNode, old_child: &WetNode) {
        self.element
            .replace_child(new_child.dom_node(), old_child.dom_node())
            .unwrap_throw();
    }

    fn remove_child(&mut self, _index: usize, child: &WetNode) {
        self.element.remove_child(child.dom_node()).unwrap_throw();
    }

    fn clear_children(&mut self) {
        self.element.set_text_content(Some(""))
    }

    fn add_class(&mut self, name: &str) {
        self.element.class_list().add_1(name).unwrap_throw()
    }

    fn remove_class(&mut self, name: &str) {
        self.element.class_list().remove_1(name).unwrap_throw()
    }

    fn attribute<A>(&mut self, name: &str, value: A)
    where
        A: crate::attribute::Attribute,
    {
        if let Some(attr) = value.text() {
            self.element.set_attribute(name, attr.as_ref())
        } else {
            self.element.remove_attribute(name)
        }
        .unwrap_throw()
    }

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

    fn dom_element(&self) -> web_sys::Element {
        self.element.clone()
    }

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

    fn style_property(&mut self, name: &str, value: &str) {
        let style_props = if let Some(elem) = self.element.dyn_ref::<web_sys::HtmlElement>() {
            elem.style()
        } else if let Some(elem) = self.element.dyn_ref::<web_sys::SvgElement>() {
            elem.style()
        } else {
            panic!("Unknown element type");
        };

        style_props.set_property(name, value).unwrap_throw();
    }

    fn effect(&mut self, f: impl FnOnce(&web_sys::Element) + 'static) {
        clone!(self.element);
        on_animation_frame(move || f(&element));
    }

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

impl InstantiableDomElement for WetElement {
    fn attach_shadow_children(&mut self, children: impl IntoIterator<Item = Self::Node>) {
        let shadow_root = self.create_shadow_root();

        for child in children {
            shadow_root.append_child(child.dom_node()).unwrap_throw();
        }
    }

    fn clone_node(&self) -> Self {
        Self {
            element: self
                .element
                .clone_node_with_deep(true)
                .unwrap()
                .unchecked_into(),
        }
    }
}

#[derive(Clone)]
pub struct WetText(web_sys::Text);

impl WetText {
    pub fn from_dom(text: web_sys::Text) -> Self {
        Self(text)
    }

    pub fn dom_text(&self) -> &web_sys::Text {
        &self.0
    }

    pub fn text(&self) -> String {
        self.0.text_content().expect("No text content found")
    }

    pub fn clone_node(&self) -> Self {
        Self(self.0.clone_node().unwrap_throw().unchecked_into())
    }
}

impl DomText for WetText {
    fn new(text: &str) -> Self {
        Self(document::create_text_node(text))
    }

    fn set_text(&mut self, text: &str) {
        self.0.set_text_content(Some(text));
    }
}

#[derive(Clone)]
pub struct WetNode(web_sys::Node);

impl WetNode {
    pub fn dom_node(&self) -> &web_sys::Node {
        &self.0
    }

    pub fn clone_node(&self) -> Self {
        Self(self.0.clone_node_with_deep(true).unwrap_throw())
    }
}

impl fmt::Display for WetNode {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        if let Some(elem) = self.0.dyn_ref::<web_sys::Element>() {
            f.write_str(&elem.outer_html())
        } else {
            f.write_str(&self.0.text_content().expect("No text content found"))
        }
    }
}

impl InstantiableDomNode for WetNode {
    type DomType = Wet;

    fn into_element(self) -> WetElement {
        WetElement {
            element: self.0.unchecked_into(),
        }
    }

    fn first_child(&self) -> Self {
        Self(self.0.first_child().unwrap_throw())
    }

    fn next_sibling(&self) -> Self {
        Self(self.0.next_sibling().unwrap_throw())
    }
}

impl From<WetElement> for WetNode {
    fn from(element: WetElement) -> Self {
        Self(element.element.into())
    }
}

impl From<WetText> for WetNode {
    fn from(text: WetText) -> Self {
        Self(text.0.into())
    }
}