1use std::collections::HashMap;
2
3#[derive(Debug, Clone, PartialEq)]
4pub enum VNode {
5 Element {
6 tag: String,
7 props: Props,
8 children: Vec<VNode>,
9 },
10 Text(String),
11}
12
13#[derive(Debug, Clone, PartialEq, Default)]
14pub struct Props {
15 pub attrs: HashMap<String, String>,
16}
17
18impl Props {
19 pub fn new() -> Self {
20 Self {
21 attrs: HashMap::new(),
22 }
23 }
24 pub fn set(mut self, k: impl Into<String>, v: impl Into<String>) -> Self {
25 self.attrs.insert(k.into(), v.into());
26 self
27 }
28 pub fn from_inline(style: impl Into<String>) -> Self {
29 let mut p = Self::new();
30 p.attrs.insert("style".to_string(), style.into());
31 p
32 }
33 pub fn from_class(class: impl Into<String>) -> Self {
34 let mut p = Self::new();
35 p.attrs.insert("class".to_string(), class.into());
36 p
37 }
38}
39
40impl From<()> for Props {
42 fn from(_: ()) -> Self {
43 Props::default()
44 }
45}
46impl From<Vec<(&str, &str)>> for Props {
47 fn from(v: Vec<(&str, &str)>) -> Self {
48 let mut p = Props::new();
49 for (k, v) in v {
50 p.attrs.insert(k.to_string(), v.to_string());
51 }
52 p
53 }
54}
55
56pub fn h(tag: impl Into<String>, props: impl Into<Props>, children: Vec<VNode>) -> VNode {
57 VNode::Element {
58 tag: tag.into(),
59 props: props.into(),
60 children,
61 }
62}
63pub fn text(t: impl Into<String>) -> VNode {
64 VNode::Text(t.into())
65}
66
67pub mod diff;
68
69pub mod layout;
70
71pub mod style;
72
73pub mod text_wrap;
74
75pub use style::*;
76
77#[derive(Debug, Clone)]
82pub enum VeloxError {
83 SfcParse(String),
85 CssParse(String),
87 Codegen(String),
89 Layout(String),
91 Render(String),
93 Window(String),
95 Build(String),
97 Io(String),
99 FeatureNotEnabled(String),
101 Internal(String),
103}
104
105impl std::fmt::Display for VeloxError {
106 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
107 match self {
108 Self::SfcParse(msg) => write!(f, "SFC parse error: {msg}"),
109 Self::CssParse(msg) => write!(f, "CSS parse error: {msg}"),
110 Self::Codegen(msg) => write!(f, "Code generation error: {msg}"),
111 Self::Layout(msg) => write!(f, "Layout error: {msg}"),
112 Self::Render(msg) => write!(f, "Render error: {msg}"),
113 Self::Window(msg) => write!(f, "Window error: {msg}"),
114 Self::Build(msg) => write!(f, "Build error: {msg}"),
115 Self::Io(msg) => write!(f, "I/O error: {msg}"),
116 Self::FeatureNotEnabled(msg) => write!(f, "Feature not enabled: {msg}"),
117 Self::Internal(msg) => write!(f, "Internal error: {msg}"),
118 }
119 }
120}
121
122impl std::error::Error for VeloxError {}
123
124impl From<std::io::Error> for VeloxError {
125 fn from(e: std::io::Error) -> Self {
126 Self::Io(e.to_string())
127 }
128}
129
130impl From<String> for VeloxError {
131 fn from(msg: String) -> Self {
132 Self::Internal(msg)
133 }
134}
135
136impl From<&str> for VeloxError {
137 fn from(msg: &str) -> Self {
138 Self::Internal(msg.to_string())
139 }
140}
141
142pub type Result<T> = std::result::Result<T, VeloxError>;
144
145#[cfg(test)]
146mod tests {
147 use super::*;
148
149 #[test]
150 fn build_tree() {
151 let node = h(
152 "div",
153 vec![("class", "app")],
154 vec![text("hello"), h("span", (), vec![text("world")])],
155 );
156 if let VNode::Element {
157 tag,
158 props,
159 children,
160 } = node
161 {
162 assert_eq!(tag, "div");
163 assert_eq!(props.attrs.get("class").unwrap(), "app");
164 assert_eq!(children.len(), 2);
165 } else {
166 panic!("expected element");
167 }
168 }
169}