use crate::simple_vnode::{VAttr, VNode};
use std::cell::RefCell;
use std::collections::HashMap;
thread_local! {
static VNODE_REGISTRY: RefCell<VNodeRegistry> = RefCell::new(VNodeRegistry::new());
}
struct VNodeRegistry {
nodes: HashMap<u64, VNode>,
next_handle: u64,
}
impl VNodeRegistry {
fn new() -> Self {
Self {
nodes: HashMap::new(),
next_handle: 1,
}
}
fn insert(&mut self, node: VNode) -> u64 {
let handle = self.next_handle;
self.next_handle += 1;
self.nodes.insert(handle, node);
handle
}
fn get(&self, handle: u64) -> Option<&VNode> {
self.nodes.get(&handle)
}
fn take(&mut self, handle: u64) -> Option<VNode> {
self.nodes.remove(&handle)
}
fn clear(&mut self) {
self.nodes.clear();
self.next_handle = 1;
}
}
#[inline]
pub fn vnode_element(tag: impl AsRef<str>) -> u64 {
let node = VNode::Element {
tag: tag.as_ref().to_string(),
attrs: Vec::new(),
children: Vec::new(),
};
VNODE_REGISTRY.with(|registry| registry.borrow_mut().insert(node))
}
#[inline]
pub fn vnode_text(content: impl AsRef<str>) -> u64 {
let node = VNode::Text(content.as_ref().to_string());
VNODE_REGISTRY.with(|registry| registry.borrow_mut().insert(node))
}
#[inline]
pub fn vnode_attr(handle: u64, name: impl AsRef<str>, value: impl AsRef<str>) {
VNODE_REGISTRY.with(|registry| {
let mut reg = registry.borrow_mut();
if let Some(VNode::Element { attrs, .. }) = reg.nodes.get_mut(&handle) {
attrs.push((
name.as_ref().to_string(),
VAttr::Static(value.as_ref().to_string()),
));
}
});
}
#[inline]
pub fn vnode_child(parent_handle: u64, child_handle: u64) {
VNODE_REGISTRY.with(|registry| {
let mut reg = registry.borrow_mut();
if let Some(child) = reg.nodes.remove(&child_handle) {
if let Some(VNode::Element { children, .. }) = reg.nodes.get_mut(&parent_handle) {
children.push(child);
}
}
});
}
#[inline]
pub fn vnode_class(handle: u64, class: impl AsRef<str>) {
let class = class.as_ref();
VNODE_REGISTRY.with(|registry| {
let mut reg = registry.borrow_mut();
if let Some(VNode::Element { attrs, .. }) = reg.nodes.get_mut(&handle) {
let mut found = false;
for (name, value) in attrs.iter_mut() {
if name == "class" {
if let VAttr::Static(existing) = value {
*existing = format!("{} {}", existing, class);
found = true;
break;
}
}
}
if !found {
attrs.push(("class".to_string(), VAttr::Static(class.to_string())));
}
}
});
}
#[inline]
pub fn vnode_style(handle: u64, style: impl AsRef<str>) {
let style = style.as_ref();
VNODE_REGISTRY.with(|registry| {
let mut reg = registry.borrow_mut();
if let Some(VNode::Element { attrs, .. }) = reg.nodes.get_mut(&handle) {
let mut found = false;
for (name, value) in attrs.iter_mut() {
if name == "style" {
if let VAttr::Static(existing) = value {
*existing = format!("{}; {}", existing, style);
found = true;
break;
}
}
}
if !found {
attrs.push(("style".to_string(), VAttr::Static(style.to_string())));
}
}
});
}
#[inline]
pub fn vnode_take(handle: u64) -> Option<VNode> {
VNODE_REGISTRY.with(|registry| registry.borrow_mut().take(handle))
}
#[inline]
pub fn vnode_get(handle: u64) -> Option<VNode> {
VNODE_REGISTRY.with(|registry| registry.borrow().get(handle).cloned())
}
#[inline]
pub fn vnode_clear() {
VNODE_REGISTRY.with(|registry| registry.borrow_mut().clear());
}
#[inline]
pub fn vnode_div(class: &str, style: &str) -> u64 {
let handle = vnode_element("div");
if !class.is_empty() {
vnode_class(handle, class);
}
if !style.is_empty() {
vnode_style(handle, style);
}
handle
}
#[inline]
pub fn vnode_span(text: &str, class: &str, style: &str) -> u64 {
let handle = vnode_element("span");
if !class.is_empty() {
vnode_class(handle, class);
}
if !style.is_empty() {
vnode_style(handle, style);
}
let text_handle = vnode_text(text);
vnode_child(handle, text_handle);
handle
}
#[inline]
pub fn vnode_button(label: &str, class: &str, style: &str) -> u64 {
let handle = vnode_element("button");
if !class.is_empty() {
vnode_class(handle, class);
}
if !style.is_empty() {
vnode_style(handle, style);
}
let text_handle = vnode_text(label);
vnode_child(handle, text_handle);
handle
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_create_element() {
vnode_clear();
let handle = vnode_element("div");
assert!(handle > 0);
let node = vnode_take(handle);
assert!(node.is_some());
if let Some(VNode::Element { tag, .. }) = node {
assert_eq!(tag, "div");
}
}
#[test]
fn test_create_text() {
vnode_clear();
let handle = vnode_text("Hello, World!");
let node = vnode_take(handle);
assert!(node.is_some());
if let Some(VNode::Text(content)) = node {
assert_eq!(content, "Hello, World!");
}
}
#[test]
fn test_add_child() {
vnode_clear();
let parent = vnode_element("div");
let child = vnode_text("Child text");
vnode_child(parent, child);
let node = vnode_take(parent);
if let Some(VNode::Element { children, .. }) = node {
assert_eq!(children.len(), 1);
if let VNode::Text(content) = &children[0] {
assert_eq!(content, "Child text");
}
}
}
#[test]
fn test_add_class() {
vnode_clear();
let handle = vnode_element("button");
vnode_class(handle, "wj-button");
vnode_class(handle, "wj-button-primary");
let node = vnode_take(handle);
if let Some(VNode::Element { attrs, .. }) = node {
let class_attr = attrs.iter().find(|(n, _)| n == "class");
assert!(class_attr.is_some());
if let Some((_, VAttr::Static(classes))) = class_attr {
assert!(classes.contains("wj-button"));
assert!(classes.contains("wj-button-primary"));
}
}
}
#[test]
fn test_convenience_builders() {
vnode_clear();
let button = vnode_button("Click me", "btn", "color: red");
let node = vnode_take(button);
if let Some(VNode::Element {
tag,
children,
attrs,
..
}) = node
{
assert_eq!(tag, "button");
assert_eq!(children.len(), 1);
let has_class = attrs
.iter()
.any(|(n, v)| n == "class" && matches!(v, VAttr::Static(s) if s.contains("btn")));
assert!(has_class);
}
}
}