#![cfg(feature = "client")]
pub use ::web_sys::{
AnimationEvent, ClipboardEvent, CompositionEvent, Event, FocusEvent, InputEvent, KeyboardEvent,
MouseEvent, PointerEvent, TouchEvent, TransitionEvent, UiEvent, WheelEvent,
};
#[doc(hidden)]
pub use {::js_sys, ::serde, ::serde_wasm_bindgen, ::wasm_bindgen, ::web_sys};
#[doc(hidden)]
#[inline]
pub fn serialize_props<P: super::IslandBoundary>(props: &P) -> String {
::serde_json::to_string(props).unwrap()
}
#[cfg(hydrate)]
mod runtime_js {
use super::*;
#[wasm_bindgen(module = "/runtime.mjs")]
extern "C" {
#[wasm_bindgen(js_name = "hydrate")]
pub(super) fn hydrate(vdom: JsValue, container: ::web_sys::Node);
#[wasm_bindgen(js_name = "createElement")]
pub(super) fn create_element(r#type: JsValue, props: Object, children: Array) -> JsValue;
#[wasm_bindgen(js_name = "createRef")]
pub(super) fn create_ref() -> JsValue;
#[wasm_bindgen(js_name = "Fragment")]
pub(super) fn fragment(props: Object) -> JsValue;
#[wasm_bindgen(js_name = "useSignal")]
pub(super) fn signal(value: JsValue) -> Object;
#[wasm_bindgen(js_name = "useComputed")]
pub(super) fn computed(f: Function) -> Object;
#[wasm_bindgen(js_name = "useSignalEffect")]
pub(super) fn effect(f: Function);
#[wasm_bindgen(js_name = "batch")]
pub(super) fn batch(f: Function);
#[wasm_bindgen(js_name = "untracked")]
pub(super) fn untracked(f: Function);
}
}
#[cfg(hydrate)]
use {
::js_sys::{Array, Function, Object, Reflect},
::wasm_bindgen::prelude::*,
};
#[cfg(hydrate)]
pub fn hydrate(vdom: VNode, container: ::web_sys::Node) {
runtime_js::hydrate(vdom.0, container);
}
#[cfg(hydrate)]
pub struct VNode(JsValue);
#[cfg(hydrate)]
pub struct NodeType(JsValue);
#[cfg(hydrate)]
impl NodeType {
pub fn tag(tag: &'static str) -> NodeType {
NodeType(tag.into())
}
pub fn component<B: crate::bound::IslandBoundary>() -> NodeType {
let component_function: Function = Closure::<dyn Fn(JsValue) -> JsValue>::new(|props| {
let props: B = serde_wasm_bindgen::from_value(props).unwrap_throw();
crate::render_in_island(props).into_vdom().0
})
.into_js_value()
.unchecked_into();
NodeType(component_function.unchecked_into())
}
}
#[cfg(hydrate)]
impl VNode {
pub fn new(r#type: NodeType, props: Object, children: Vec<VNode>) -> VNode {
VNode(runtime_js::create_element(
r#type.0,
props,
children.into_iter().map(|vdom| vdom.0).collect::<Array>(),
))
}
pub fn fragment(children: Vec<VNode>) -> VNode {
let props = Object::new();
Reflect::set(
&props,
&"children".into(),
&children.into_iter().map(|vdom| vdom.0).collect::<Array>(),
)
.ok();
VNode(runtime_js::fragment(props))
}
pub fn text(text: impl Into<std::borrow::Cow<'static, str>>) -> VNode {
match text.into() {
std::borrow::Cow::Owned(s) => VNode(s.into()),
std::borrow::Cow::Borrowed(s) => VNode(s.into()),
}
}
}
#[cfg_attr(docsrs, doc(cfg(feature = "client")))]
#[macro_export]
macro_rules! callback {
([$($dep:ident),*], || $result:expr) => {
{
$(let $dep = $dep.clone();)+
move || $result
}
};
([$($dep:ident),*], |_ $(: $Type:ty)?| $result:expr) => {
{
$(let $dep = $dep.clone();)*
move |_ $(: $Type)?| $result
}
};
([$($dep:ident),*], |$($arg:ident $(: $Type:ty)?),+| $result:expr) => {
{
$(let $dep = $dep.clone();)+
move |$($arg $(: $Type)?),+| $result
}
};
}
#[cfg_attr(docsrs, doc(cfg(feature = "client")))]
pub struct Signal<T: serde::Serialize + for<'de> serde::Deserialize<'de>> {
#[cfg(hydrate)]
preact_signal: Object,
current_value: std::rc::Rc<std::cell::UnsafeCell<T>>,
}
impl<T> super::client_attribute<T> for Signal<T>
where
T: serde::Serialize + for<'de> serde::Deserialize<'de>,
{
fn new(value: T) -> Self {
Self {
#[cfg(hydrate)]
preact_signal: runtime_js::signal(serde_wasm_bindgen::to_value(&value).unwrap_throw()),
current_value: std::rc::Rc::new(std::cell::UnsafeCell::new(value)),
}
}
}
impl<T> Signal<T>
where
T: serde::Serialize + for<'de> serde::Deserialize<'de>,
{
pub fn set(&self, value: T) {
#[cfg(not(hydrate))]
{
unsafe {
*self.current_value.get() = value;
}
}
#[cfg(hydrate)]
{
Reflect::set(
&self.preact_signal,
&"value".into(),
&serde_wasm_bindgen::to_value(&value).unwrap_throw(),
)
.unwrap_throw();
}
}
}
impl<T> Clone for Signal<T>
where
T: serde::Serialize + for<'de> serde::Deserialize<'de>,
{
fn clone(&self) -> Self {
Self {
#[cfg(hydrate)]
preact_signal: self.preact_signal.clone(),
current_value: self.current_value.clone(),
}
}
}
impl<T> std::ops::Deref for Signal<T>
where
T: serde::Serialize + for<'de> serde::Deserialize<'de>,
{
type Target = T;
fn deref(&self) -> &Self::Target {
#[cfg(not(hydrate))]
{
unsafe { &*self.current_value.get() }
}
#[cfg(hydrate)]
{
let value = serde_wasm_bindgen::from_value(
Reflect::get(&self.preact_signal, &"value".into()).unwrap_throw(),
)
.unwrap_throw();
unsafe {
*self.current_value.get() = value;
}
unsafe { &*self.current_value.get() }
}
}
}
#[doc(hidden)]
pub struct Computed<T: serde::Serialize + for<'de> serde::Deserialize<'de>>(Signal<T>);
impl<T> Clone for Computed<T>
where
T: serde::Serialize + for<'de> serde::Deserialize<'de>,
{
fn clone(&self) -> Self {
Self(self.0.clone())
}
}
impl<T> std::ops::Deref for Computed<T>
where
T: serde::Serialize + for<'de> serde::Deserialize<'de>,
{
type Target = T;
fn deref(&self) -> &Self::Target {
self.0.deref()
}
}
impl<T, F> super::client_attribute<F> for Computed<T>
where
T: serde::Serialize + for<'de> serde::Deserialize<'de>,
F: Fn() -> T + 'static,
{
fn new(getter: F) -> Self {
#[cfg(not(hydrate))]
{
Self(Signal::new(getter()))
}
#[cfg(hydrate)]
{
let init = getter();
let preact_computed = runtime_js::computed(
Closure::<dyn Fn() -> JsValue>::new(move || {
serde_wasm_bindgen::to_value(&getter()).unwrap_throw()
})
.into_js_value()
.unchecked_into(),
);
Self(Signal {
preact_signal: preact_computed,
current_value: std::rc::Rc::new(std::cell::UnsafeCell::new(init)),
})
}
}
}
#[cfg_attr(docsrs, doc(cfg(feature = "client")))]
#[macro_export]
macro_rules! computed {
($($t:tt)*) => {
$crate::client::Computed::new($crate::callback!($($t)*))
};
}
#[doc(hidden)]
pub struct Effect;
impl<F> super::client_attribute<F> for Effect
where
F: Fn() + 'static,
{
fn new(#[cfg_attr(not(hydrate), allow(unused))] f: F) -> Self {
#[cfg(hydrate)]
{
let f = Closure::<dyn Fn()>::new(f).into_js_value().unchecked_into();
runtime_js::effect(f);
}
Self
}
}
#[cfg_attr(docsrs, doc(cfg(feature = "client")))]
#[macro_export]
macro_rules! effect {
($($t:tt)*) => {
$crate::client::Effect::new($crate::callback!($($t)*))
};
}
#[doc(hidden)]
pub struct Batch;
impl<F> super::client_attribute<F> for Batch
where
F: Fn() + 'static,
{
fn new(#[cfg_attr(not(hydrate), allow(unused))] f: F) -> Self {
#[cfg(hydrate)]
{
let f = Closure::<dyn Fn()>::new(f).into_js_value().unchecked_into();
runtime_js::effect(f);
}
Self
}
}
#[cfg_attr(docsrs, doc(cfg(feature = "client")))]
#[macro_export]
macro_rules! batch {
($($t:tt)*) => {
$crate::client::Batch::new($crate::callback!($($t)*))
};
}
#[doc(hidden)]
pub struct Untracked;
impl<F> super::client_attribute<F> for Untracked
where
F: Fn() + 'static,
{
fn new(#[cfg_attr(not(hydrate), allow(unused))] f: F) -> Self {
#[cfg(hydrate)]
{
let f = Closure::<dyn Fn()>::new(f).into_js_value().unchecked_into();
runtime_js::effect(f);
}
Self
}
}
#[cfg_attr(docsrs, doc(cfg(feature = "client")))]
#[macro_export]
macro_rules! untracked {
($($t:tt)*) => {
$crate::client::Untracked::new($crate::callback!($($t)*))
};
}