use crate::{react_bindings, VNode};
use std::any::{type_name, Any};
use js_sys::JsString;
use wasm_bindgen::prelude::*;
pub trait KeyType: Into<JsValue> {}
macro_rules! impl_key_type {
{ $( $T:ty ),* $( , )? } => {
$( impl KeyType for $T {} )*
};
}
impl_key_type! {
&str, String, JsString,
f32, f64,
i8, i16, i32, i64, i128, isize,
u8, u16, u32, u64, u128, usize,
}
#[doc(hidden)]
pub struct BuildParams {
name: &'static str,
key: Option<JsValue>,
}
pub trait Component: Sized + 'static {
fn render(&self) -> VNode;
fn key(self, key: Option<impl KeyType>) -> Keyed<Self> {
Keyed(self, key.map(|x| x.into()))
}
#[doc(hidden)]
fn _build_params(&self) -> BuildParams {
BuildParams {
name: type_name::<Self>(),
key: None,
}
}
#[doc(hidden)]
fn _build_with_name_and_key(
self,
name: &'static str,
key: Option<JsValue>,
) -> VNode {
VNode::Single(react_bindings::create_rust_component(
name,
&key.unwrap_or(JsValue::UNDEFINED),
ComponentWrapper(Box::new(self)),
))
}
fn build(self) -> VNode {
let BuildParams { name, key } = self._build_params();
self._build_with_name_and_key(name, key)
}
fn memoized(self) -> Memoized<Self>
where
Self: PartialEq,
{
Memoized(self)
}
}
#[derive(Debug, PartialEq)]
pub struct Keyed<T>(T, Option<JsValue>);
impl<T: Component> Component for Keyed<T> {
fn render(&self) -> VNode {
self.0.render()
}
fn _build_params(&self) -> BuildParams {
let BuildParams { name, .. } = self.0._build_params();
BuildParams {
name,
key: self.1.clone(),
}
}
fn _build_with_name_and_key(
self,
name: &'static str,
key: Option<JsValue>,
) -> VNode {
self.0._build_with_name_and_key(name, key)
}
}
#[derive(Debug, PartialEq)]
pub struct Memoized<T>(T);
impl<T: Component + PartialEq> Component for Memoized<T> {
fn render(&self) -> VNode {
self.0.render()
}
fn _build_params(&self) -> BuildParams {
self.0._build_params()
}
fn _build_with_name_and_key(
self,
name: &'static str,
key: Option<JsValue>,
) -> VNode {
VNode::Single(react_bindings::create_rust_memo_component(
name,
&key.unwrap_or(JsValue::UNDEFINED),
MemoComponentWrapper(Box::new(self.0)),
))
}
}
trait ObjectSafeComponent {
fn render(&self) -> VNode;
}
impl<T: Component> ObjectSafeComponent for T {
fn render(&self) -> VNode {
Component::render(self)
}
}
#[doc(hidden)]
#[wasm_bindgen(js_name = __WasmReact_ComponentWrapper)]
pub struct ComponentWrapper(Box<dyn ObjectSafeComponent>);
#[wasm_bindgen(js_class = __WasmReact_ComponentWrapper)]
impl ComponentWrapper {
#[wasm_bindgen]
pub fn render(&self) -> JsValue {
self.0.render().into()
}
}
trait ObjectSafeMemoComponent: ObjectSafeComponent {
fn as_any(&self) -> &dyn Any;
fn eq(&self, other: &dyn Any) -> bool;
}
impl<T: Component + PartialEq> ObjectSafeMemoComponent for T {
fn as_any(&self) -> &dyn Any {
self
}
fn eq(&self, other: &dyn Any) -> bool {
other
.downcast_ref::<T>()
.map(|other| PartialEq::eq(self, other))
.unwrap_or(false)
}
}
#[doc(hidden)]
#[wasm_bindgen(js_name = __WasmReact_MemoComponentWrapper)]
pub struct MemoComponentWrapper(Box<dyn ObjectSafeMemoComponent>);
#[wasm_bindgen(js_class = __WasmReact_MemoComponentWrapper)]
impl MemoComponentWrapper {
#[wasm_bindgen]
pub fn render(&self) -> JsValue {
self.0.render().into()
}
#[wasm_bindgen]
pub fn eq(&self, other: &MemoComponentWrapper) -> bool {
self.0.eq(other.0.as_any())
}
}