use std::cell::RefCell;
use std::rc::Rc;
use wasm_bindgen::prelude::*;
use web_sys::*;
pub mod mount;
pub mod patch;
pub mod children;
#[derive(Clone, Debug, PartialEq)]
pub enum VNodeType {
Element(String), Text,
Fragment,
Dynamic,
Empty,
}
pub enum VNode {
Element(VElement),
Text(String),
Fragment(Vec<VNode>),
Dynamic(
Rc<RefCell<Box<dyn Fn() -> VNode>>>,
Rc<RefCell<Option<web_sys::Node>>>,
),
Empty,
}
impl VNode {
pub fn element(tag: &str) -> VElementBuilder {
VElementBuilder::new(tag)
}
pub fn text(content: &str) -> Self {
VNode::Text(content.to_string())
}
pub fn fragment(children: Vec<VNode>) -> Self {
VNode::Fragment(children)
}
pub fn empty() -> Self {
VNode::Empty
}
pub fn node_type(&self) -> VNodeType {
match self {
VNode::Element(el) => VNodeType::Element(el.tag.clone()),
VNode::Text(_) => VNodeType::Text,
VNode::Fragment(_) => VNodeType::Fragment,
VNode::Dynamic(_, _) => VNodeType::Dynamic,
VNode::Empty => VNodeType::Empty,
}
}
pub fn same_type(&self, other: &VNode) -> bool {
match (self, other) {
(VNode::Element(a), VNode::Element(b)) => a.tag == b.tag,
(VNode::Text(_), VNode::Text(_)) => true,
(VNode::Fragment(_), VNode::Fragment(_)) => true,
(VNode::Dynamic(_, _), VNode::Dynamic(_, _)) => true,
(VNode::Empty, VNode::Empty) => true,
_ => false,
}
}
pub fn key(&self) -> Option<&str> {
match self {
VNode::Element(el) => el.key.as_deref(),
_ => None,
}
}
pub fn dom_node(&self) -> Option<web_sys::Node> {
match self {
VNode::Element(el) => el.dom_ref.borrow().clone(),
VNode::Dynamic(_, dom_ref) => dom_ref.borrow().clone(),
VNode::Text(_) | VNode::Fragment(_) | VNode::Empty => None,
}
}
pub fn set_dom_node(&self, node: web_sys::Node) {
match self {
VNode::Element(el) => {
*el.dom_ref.borrow_mut() = Some(node);
}
VNode::Dynamic(_, dom_ref) => {
*dom_ref.borrow_mut() = Some(node);
}
VNode::Text(_) | VNode::Fragment(_) | VNode::Empty => {
}
}
}
pub fn keyed_children(&self) -> Vec<(usize, &VNode)> {
match self {
VNode::Element(el) => el
.children
.iter()
.enumerate()
.filter(|(_, c)| c.key().is_some())
.collect(),
_ => vec![],
}
}
}
pub struct VElement {
pub tag: String,
pub attrs: Vec<(&'static str, String)>,
pub events: Vec<(&'static str, EventHandler)>,
pub children: Vec<VNode>,
pub key: Option<String>,
pub dom_ref: Rc<RefCell<Option<web_sys::Node>>>,
pub listener_closures: Rc<RefCell<Vec<(&'static str, Closure<dyn FnMut(web_sys::Event)>)>>>,
}
pub struct EventHandler(Rc<dyn Fn(web_sys::Event)>);
impl EventHandler {
pub fn new<F: Fn(web_sys::Event) + 'static>(f: F) -> Self {
EventHandler(Rc::new(f))
}
pub fn call(&self, event: web_sys::Event) {
(self.0)(event)
}
}
impl Clone for EventHandler {
fn clone(&self) -> Self {
EventHandler(self.0.clone())
}
}
pub struct VElementBuilder {
tag: String,
attrs: Vec<(&'static str, String)>,
events: Vec<(&'static str, EventHandler)>,
children: Vec<VNode>,
key: Option<String>,
}
impl VElementBuilder {
fn new(tag: &str) -> Self {
VElementBuilder {
tag: tag.to_string(),
attrs: Vec::new(),
events: Vec::new(),
children: Vec::new(),
key: None,
}
}
pub fn attr(mut self, name: &'static str, value: &str) -> Self {
self.attrs.push((name, value.to_string()));
self
}
pub fn class(mut self, value: &str) -> Self {
self.attrs.push(("class", value.to_string()));
self
}
pub fn id(mut self, value: &str) -> Self {
self.attrs.push(("id", value.to_string()));
self
}
pub fn key(mut self, value: &str) -> Self {
self.key = Some(value.to_string());
self
}
pub fn on<F: Fn(web_sys::Event) + 'static>(mut self, event: &'static str, handler: F) -> Self {
self.events.push((event, EventHandler::new(handler)));
self
}
pub fn child(mut self, child: VNode) -> Self {
self.children.push(child);
self
}
pub fn children(mut self, children: Vec<VNode>) -> Self {
self.children.extend(children);
self
}
pub fn text(mut self, content: &str) -> Self {
self.children.push(VNode::Text(content.to_string()));
self
}
pub fn build(self) -> VNode {
VNode::Element(VElement {
tag: self.tag,
attrs: self.attrs,
events: self.events,
children: self.children,
key: self.key,
dom_ref: Rc::new(RefCell::new(None)),
listener_closures: Rc::new(RefCell::new(Vec::new())),
})
}
}
pub fn render_to_dom(vnode: &VNode) -> Option<web_sys::Node> {
let document = web_sys::window()?.document()?;
let temp = document.create_element("div").ok()?;
let parent: web_sys::Node = temp.into();
mount::mount_to_dom(vnode, &parent, None)
}