1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
use std::rc::Rc;

use wasm_bindgen::{closure::Closure, JsValue, UnwrapThrowExt};

use crate::IntoElement;

pub type DynUseRenderFn = dyn Fn() -> Option<crate::Element>;

fn impl_bridge_rust_only_component(js_props: crate::JsProps) -> JsValue {
    #[cfg(debug_assertions)]
    let children = js_props.children();
    #[cfg(debug_assertions)]
    if children.is_some() {
        panic!("rust only component should not accept children from js");
    }

    let bridge = js_props.props_bridge().unwrap();

    let ref_bridge = react_sys::use_ref_usize(bridge);

    let old_bridge = ref_bridge.current();
    if old_bridge != bridge {
        let _valid = unsafe { forgotten::try_free_with_usize(old_bridge) };

        #[cfg(debug_assertions)]
        assert!(_valid, "invalid js props bridge: failed to free");

        ref_bridge.set_current(bridge);
    }

    let render = unsafe { forgotten::try_get_with_usize::<Box<DynUseRenderFn>>(&bridge) };

    let render = render.expect_throw("invalid js props bridge: failed to get");

    let el = render();

    if let Some(el) = el {
        el.unsafe_into_js_element().into()
    } else {
        JsValue::NULL
    }
}

type ClosureBridgeRustOnlyComponent = Closure<dyn Fn(crate::JsProps) -> JsValue>;

fn closure_to_bridge_rust_only_component() -> ClosureBridgeRustOnlyComponent {
    Closure::wrap(
        Box::new(impl_bridge_rust_only_component) as Box<dyn Fn(crate::JsProps) -> JsValue>
    )
}

pub struct UseRenderElement {
    pub use_render: Rc<Box<DynUseRenderFn>>,
    pub key: Option<crate::Key>,
    pub debug_component_name: Option<JsValue>,
    pub debug_props: Option<JsValue>,
}

impl UseRenderElement {
    #[inline]
    pub fn wrap_use_render<E: crate::IntoOptionalElement, F: 'static + Fn() -> E>(
        use_render: F,
        key: Option<crate::Key>,
        debug_component_name: Option<JsValue>,
        debug_props: Option<JsValue>,
    ) -> Self {
        let use_render = move || use_render().into_optional_element();
        Self {
            use_render: Rc::new(Box::new(use_render)),
            key,
            debug_component_name,
            debug_props,
        }
    }
}

impl std::fmt::Debug for UseRenderElement {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("UseRenderElement")
            .field("use_render", &"Rc<RustClosure>")
            .field("key", &self.key)
            .field("debug_component_name", &self.debug_component_name)
            .field("debug_props", &self.debug_props)
            .finish()
    }
}

impl Clone for UseRenderElement {
    #[inline]
    fn clone(&self) -> Self {
        Self {
            use_render: self.use_render.clone(),
            key: self.key.clone(),
            debug_component_name: self.debug_component_name.clone(),
            debug_props: self.debug_props.clone(),
        }
    }
}

impl UseRenderElement {
    /// Note that the created element must not be cloned
    /// or used multiple times as react element.
    ///
    /// `use_render` will be forgotten,
    /// and it will be dropped automatically once changed
    #[inline]
    pub(crate) fn unsafe_create_element_js(self) -> react_sys::Element {
        let Self {
            use_render,
            key,
            debug_component_name,
            debug_props,
        } = self;

        thread_local! {
            static ADAPTER_FN: ClosureBridgeRustOnlyComponent = closure_to_bridge_rust_only_component();
        }

        ADAPTER_FN.with(|comp_fn| {
            use wasm_bindgen::JsCast;

            let obj = js_sys::Object::new();
            let props: &crate::JsProps = obj.unchecked_ref();

            props.set_key(key.map(Into::into).as_ref());

            #[cfg(debug_assertions)]
            if let Some(debug_component_name) = debug_component_name {
                props.set_debug_component_name(&debug_component_name);
            }

            #[cfg(debug_assertions)]
            if let Some(debug_props) = debug_props {
                props.set_debug_props(&debug_props);
            }

            let k = forgotten::forget_rc(use_render);

            let k = k.into_shared();
            let k = k.as_usize();

            props.set_props_bridge(Some(*k));

            react_sys::create_element_no_children(comp_fn.as_ref(), props.as_ref())
        })
    }
}

impl crate::IntoElement for UseRenderElement {
    #[inline]
    fn into_element(self) -> crate::Element {
        crate::Element::bridge_use_render_element(self)
    }
}

impl Into<crate::Element> for UseRenderElement {
    #[inline]
    fn into(self) -> crate::Element {
        self.into_element()
    }
}