sauron_core/dom/component/
stateful_component.rs1use crate::{
2 dom::{
3 events::on_component_mount, program::MountProcedure, Application, Cmd, Component, DomAttr,
4 DomAttrValue, DomNode, Program,
5 },
6 vdom::{Attribute, AttributeName, Leaf, Node},
7};
8use std::{any::TypeId, cell::RefCell, fmt, rc::Rc};
9
10pub trait StatefulComponent {
12 fn attribute_changed(&mut self, attr: DomAttr);
17 fn remove_attribute(&mut self, _attr_name: AttributeName) {}
19
20 fn child_container(&self) -> Option<DomNode>;
22
23 fn append_children(&mut self, _children: Vec<DomNode>) {}
25
26 fn remove_child(&mut self, _index: usize) {}
28
29 fn connected_callback(&mut self) {}
31 fn disconnected_callback(&mut self) {}
33
34 fn adopted_callback(&mut self) {}
36}
37
38pub struct StatefulModel<MSG> {
40 pub comp: Rc<RefCell<dyn StatefulComponent>>,
42 pub type_id: TypeId,
44 pub attrs: Vec<Attribute<MSG>>,
46 pub children: Vec<Node<MSG>>,
48}
49
50impl<MSG> fmt::Debug for StatefulModel<MSG> {
51 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
52 write!(f, "StatefulModel")
53 }
54}
55
56impl<MSG> StatefulModel<MSG> {
57 pub fn map_msg<F, MSG2>(self, cb: F) -> StatefulModel<MSG2>
59 where
60 F: Fn(MSG) -> MSG2 + Clone + 'static,
61 MSG2: 'static,
62 MSG: 'static,
63 {
64 StatefulModel {
65 type_id: self.type_id,
66 comp: self.comp,
67 attrs: self
68 .attrs
69 .into_iter()
70 .map(|a| a.map_msg(cb.clone()))
71 .collect(),
72 children: self
73 .children
74 .into_iter()
75 .map(|c| c.map_msg(cb.clone()))
76 .collect(),
77 }
78 }
79}
80
81impl<MSG> Clone for StatefulModel<MSG> {
82 fn clone(&self) -> Self {
83 Self {
84 comp: Rc::clone(&self.comp),
85 type_id: self.type_id,
86 attrs: self.attrs.clone(),
87 children: self.children.clone(),
88 }
89 }
90}
91
92impl<MSG> PartialEq for StatefulModel<MSG> {
93 fn eq(&self, other: &Self) -> bool {
94 Rc::ptr_eq(&self.comp, &other.comp)
95 && self.type_id == other.type_id
96 && self.attrs == other.attrs
97 && self.children == other.children
98 }
99}
100
101impl<COMP> Application for COMP
102where
103 COMP: Component<XMSG = ()> + StatefulComponent + 'static,
104{
105 type MSG = COMP::MSG;
106
107 fn init(&mut self) -> Cmd<Self::MSG> {
108 Cmd::from(<Self as Component>::init(self))
109 }
110
111 fn update(&mut self, msg: COMP::MSG) -> Cmd<Self::MSG> {
112 let effects = <Self as Component>::update(self, msg);
113 Cmd::from(effects)
114 }
115
116 fn view(&self) -> Node<Self::MSG> {
117 Component::view(self)
118 }
119
120 fn stylesheet() -> Vec<String> {
121 <Self as Component>::stylesheet()
122 }
123
124 fn style(&self) -> Vec<String> {
125 <Self as Component>::style(self)
126 }
127}
128
129pub fn stateful_component<COMP, MSG, MSG2>(
131 app: COMP,
132 attrs: impl IntoIterator<Item = Attribute<MSG>>,
133 children: impl IntoIterator<Item = Node<MSG>>,
134) -> Node<MSG>
135where
136 COMP: Component<MSG = MSG2, XMSG = ()> + StatefulComponent + Application<MSG = MSG2> + 'static,
137 MSG: 'static,
138 MSG2: 'static,
139{
140 let type_id = TypeId::of::<COMP>();
141 let attrs = attrs.into_iter().collect::<Vec<_>>();
142
143 let app = Rc::new(RefCell::new(app));
144
145 let mut program = Program::from_rc_app(Rc::clone(&app));
146 let children: Vec<Node<MSG>> = children.into_iter().collect();
147 let mount_event = on_component_mount(move |me| {
148 program.mount(
149 &me.target_node.as_node(),
150 MountProcedure::append(),
152 );
153 let stylesheet = <COMP as Component>::stylesheet().join("\n");
154 log::info!("stylesheet: {}", stylesheet);
155 program.inject_style_to_mount(&stylesheet);
156 program.inject_style_to_mount(&program.app_context.dynamic_style());
157 program.update_dom().expect("update dom");
158 });
159 Node::Leaf(Leaf::StatefulComponent(StatefulModel {
160 comp: app,
161 type_id,
162 attrs: attrs.into_iter().chain([mount_event]).collect(),
163 children: children.into_iter().collect(),
164 }))
165}
166
167#[cfg(feature = "with-dom")]
168impl From<wasm_bindgen::JsValue> for DomAttrValue {
169 fn from(val: wasm_bindgen::JsValue) -> Self {
170 if val.is_null() {
171 DomAttrValue::Empty
172 } else if let Some(v) = val.as_bool() {
173 DomAttrValue::Simple(v.into())
174 } else if let Some(v) = val.as_f64() {
175 DomAttrValue::Simple(v.into())
176 } else if let Some(v) = val.as_string() {
177 DomAttrValue::Simple(v.into())
178 } else {
179 todo!("for: {:?}", val)
180 }
181 }
182}