1use crate::{react_bindings, VNode};
2use std::any::{type_name, Any};
3use js_sys::JsString;
4use wasm_bindgen::prelude::*;
5
6pub trait KeyType: Into<JsValue> {}
10
11macro_rules! impl_key_type {
12 { $( $T:ty ),* $( , )? } => {
13 $( impl KeyType for $T {} )*
14 };
15}
16
17impl_key_type! {
18 &str, String, JsString,
19 f32, f64,
20 i8, i16, i32, i64, i128, isize,
21 u8, u16, u32, u64, u128, usize,
22}
23
24#[doc(hidden)]
25pub struct BuildParams {
26 name: &'static str,
27 key: Option<JsValue>,
28}
29
30pub trait Component: Sized + 'static {
54 fn render(&self) -> VNode;
59
60 fn key(self, key: Option<impl KeyType>) -> Keyed<Self> {
64 Keyed(self, key.map(|x| x.into()))
65 }
66
67 #[doc(hidden)]
68 fn _build_params(&self) -> BuildParams {
70 BuildParams {
71 name: type_name::<Self>(),
72 key: None,
73 }
74 }
75
76 #[doc(hidden)]
77 fn _build_with_name_and_key(
78 self,
79 name: &'static str,
80 key: Option<JsValue>,
81 ) -> VNode {
82 VNode::Single(react_bindings::create_rust_component(
83 name,
84 &key.unwrap_or(JsValue::UNDEFINED),
85 ComponentWrapper(Box::new(self)),
86 ))
87 }
88
89 fn build(self) -> VNode {
91 let BuildParams { name, key } = self._build_params();
92
93 self._build_with_name_and_key(name, key)
94 }
95
96 fn memoized(self) -> Memoized<Self>
135 where
136 Self: PartialEq,
137 {
138 Memoized(self)
139 }
140}
141
142#[derive(Debug, PartialEq)]
148pub struct Keyed<T>(T, Option<JsValue>);
149
150impl<T: Component> Component for Keyed<T> {
151 fn render(&self) -> VNode {
152 self.0.render()
153 }
154
155 fn _build_params(&self) -> BuildParams {
156 let BuildParams { name, .. } = self.0._build_params();
157
158 BuildParams {
159 name,
160 key: self.1.clone(),
161 }
162 }
163
164 fn _build_with_name_and_key(
165 self,
166 name: &'static str,
167 key: Option<JsValue>,
168 ) -> VNode {
169 self.0._build_with_name_and_key(name, key)
170 }
171}
172
173#[derive(Debug, PartialEq)]
177pub struct Memoized<T>(T);
178
179impl<T: Component + PartialEq> Component for Memoized<T> {
180 fn render(&self) -> VNode {
181 self.0.render()
182 }
183
184 fn _build_params(&self) -> BuildParams {
185 self.0._build_params()
186 }
187
188 fn _build_with_name_and_key(
189 self,
190 name: &'static str,
191 key: Option<JsValue>,
192 ) -> VNode {
193 VNode::Single(react_bindings::create_rust_memo_component(
194 name,
195 &key.unwrap_or(JsValue::UNDEFINED),
196 MemoComponentWrapper(Box::new(self.0)),
197 ))
198 }
199}
200
201trait ObjectSafeComponent {
202 fn render(&self) -> VNode;
203}
204
205impl<T: Component> ObjectSafeComponent for T {
206 fn render(&self) -> VNode {
207 Component::render(self)
208 }
209}
210
211#[doc(hidden)]
212#[wasm_bindgen(js_name = __WasmReact_ComponentWrapper)]
213pub struct ComponentWrapper(Box<dyn ObjectSafeComponent>);
214
215#[wasm_bindgen(js_class = __WasmReact_ComponentWrapper)]
216impl ComponentWrapper {
217 #[wasm_bindgen]
218 pub fn render(&self) -> JsValue {
219 self.0.render().into()
220 }
221}
222
223trait ObjectSafeMemoComponent: ObjectSafeComponent {
224 fn as_any(&self) -> &dyn Any;
225 fn eq(&self, other: &dyn Any) -> bool;
226}
227
228impl<T: Component + PartialEq> ObjectSafeMemoComponent for T {
229 fn as_any(&self) -> &dyn Any {
230 self
231 }
232
233 fn eq(&self, other: &dyn Any) -> bool {
234 other
235 .downcast_ref::<T>()
236 .map(|other| PartialEq::eq(self, other))
237 .unwrap_or(false)
238 }
239}
240
241#[doc(hidden)]
242#[wasm_bindgen(js_name = __WasmReact_MemoComponentWrapper)]
243pub struct MemoComponentWrapper(Box<dyn ObjectSafeMemoComponent>);
244
245#[wasm_bindgen(js_class = __WasmReact_MemoComponentWrapper)]
246impl MemoComponentWrapper {
247 #[wasm_bindgen]
248 pub fn render(&self) -> JsValue {
249 self.0.render().into()
250 }
251
252 #[wasm_bindgen]
253 pub fn eq(&self, other: &MemoComponentWrapper) -> bool {
254 self.0.eq(other.0.as_any())
255 }
256}