use std::iter::FromIterator;
use std::marker::PhantomData;
use std::rc::Rc;
use crate::component::component_scope;
use crate::generic_node::GenericNode;
use crate::noderef::NodeRef;
use crate::reactive::*;
use crate::utils::render;
use crate::view::View;
#[cfg(feature = "web")]
use crate::web::Html;
pub mod prelude {
pub use super::{component, dyn_t, fragment, t, tag};
#[cfg(feature = "web")]
pub use crate::web::html::*;
}
pub struct ElementBuilder<'a, G: GenericNode, F: FnOnce(Scope<'a>) -> G + 'a>(
F,
PhantomData<&'a ()>,
);
impl<'a, G: GenericNode, F: FnOnce(Scope<'a>) -> G + 'a> std::fmt::Debug
for ElementBuilder<'a, G, F>
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ElementBuilder").finish()
}
}
pub trait ElementBuilderOrView<'a, G: GenericNode> {
fn into_view(self, cx: Scope<'a>) -> View<G>;
}
impl<'a, G: GenericNode> ElementBuilderOrView<'a, G> for View<G> {
fn into_view(self, _: Scope<'a>) -> View<G> {
self
}
}
impl<'a, G: GenericNode, F: FnOnce(Scope<'a>) -> G + 'a> ElementBuilderOrView<'a, G>
for ElementBuilder<'a, G, F>
{
fn into_view(self, cx: Scope<'a>) -> View<G> {
self.view(cx)
}
}
pub fn tag<'a, G: GenericNode>(
t: impl AsRef<str> + 'a,
) -> ElementBuilder<'a, G, impl FnOnce(Scope<'a>) -> G> {
ElementBuilder::new(move |_| G::element_from_tag(t.as_ref()))
}
impl<'a, G: GenericNode, F: FnOnce(Scope<'a>) -> G + 'a> ElementBuilder<'a, G, F> {
pub(crate) fn new(f: F) -> Self {
Self(f, PhantomData)
}
fn map(
self,
f: impl FnOnce(Scope<'a>, &G) + 'a,
) -> ElementBuilder<'a, G, impl FnOnce(Scope<'a>) -> G + 'a> {
ElementBuilder::new(move |cx| {
let el = (self.0)(cx);
f(cx, &el);
el
})
}
pub fn attr(
self,
name: &'a str,
value: impl AsRef<str> + 'a,
) -> ElementBuilder<'a, G, impl FnOnce(Scope<'a>) -> G + 'a> {
self.map(move |_, el| el.set_attribute(name, value.as_ref()))
}
pub fn bool_attr(
self,
name: &'a str,
value: bool,
) -> ElementBuilder<'a, G, impl FnOnce(Scope<'a>) -> G + 'a> {
self.map(move |_, el| {
if value {
el.set_attribute(name, "");
}
})
}
pub fn dyn_attr<S: AsRef<str> + 'a>(
self,
name: &'a str,
mut value: impl FnMut() -> Option<S> + 'a,
) -> ElementBuilder<'a, G, impl FnOnce(Scope<'a>) -> G + 'a> {
self.map(move |cx, el| {
let el = el.clone();
create_effect(cx, move || {
let value = value();
if let Some(value) = value {
el.set_attribute(name, value.as_ref());
} else {
el.remove_attribute(name);
}
});
})
}
pub fn dyn_bool_attr(
self,
name: &'a str,
mut value: impl FnMut() -> bool + 'a,
) -> ElementBuilder<'a, G, impl FnOnce(Scope<'a>) -> G + 'a> {
self.map(move |cx, el| {
let el = el.clone();
create_effect(cx, move || {
if value() {
el.set_attribute(name, "");
} else {
el.remove_attribute(name);
}
});
})
}
pub fn dangerously_set_inner_html(
self,
html: impl AsRef<str> + 'a,
) -> ElementBuilder<'a, G, impl FnOnce(Scope<'a>) -> G + 'a> {
self.map(move |_, el| el.dangerously_set_inner_html(html.as_ref()))
}
pub fn dyn_dangerously_set_inner_html<U>(
self,
mut html: impl FnMut() -> U + 'a,
) -> ElementBuilder<'a, G, impl FnOnce(Scope<'a>) -> G + 'a>
where
U: AsRef<str> + 'a,
{
self.map(move |cx, el| {
let el = el.clone();
create_effect(cx, move || {
el.dangerously_set_inner_html(html().as_ref());
});
})
}
pub fn class(
self,
class: impl AsRef<str> + 'a,
) -> ElementBuilder<'a, G, impl FnOnce(Scope<'a>) -> G + 'a> {
self.map(move |_, el| el.add_class(class.as_ref()))
}
pub fn dyn_class(
self,
class: impl AsRef<str> + 'a,
mut apply: impl FnMut() -> bool + 'a,
) -> ElementBuilder<'a, G, impl FnOnce(Scope<'a>) -> G + 'a> {
self.map(move |cx, el| {
let el = el.clone();
create_effect(cx, move || {
if apply() {
el.add_class(class.as_ref());
} else {
el.remove_class(class.as_ref());
}
});
})
}
pub fn id(
self,
class: impl AsRef<str> + 'a,
) -> ElementBuilder<'a, G, impl FnOnce(Scope<'a>) -> G + 'a> {
self.map(move |_, el| el.set_attribute("id", class.as_ref()))
}
pub fn prop(
self,
name: impl AsRef<str> + 'a,
property: impl Into<G::PropertyType> + 'a,
) -> ElementBuilder<'a, G, impl FnOnce(Scope<'a>) -> G + 'a> {
self.map(move |_, el| el.set_property(name.as_ref(), &property.into()))
}
pub fn dyn_prop<V: Into<G::PropertyType> + 'a>(
self,
name: impl AsRef<str> + 'a,
mut property: impl FnMut() -> V + 'a,
) -> ElementBuilder<'a, G, impl FnOnce(Scope<'a>) -> G + 'a> {
self.map(move |cx, el| {
let el = el.clone();
create_effect(cx, move || {
el.set_property(name.as_ref(), &property().into());
});
})
}
pub fn t(self, text: &'a str) -> ElementBuilder<'a, G, impl FnOnce(Scope<'a>) -> G + 'a> {
self.map(|_, el| el.append_child(&G::text_node(text)))
}
pub fn dyn_t<S: AsRef<str> + 'a>(
self,
f: impl FnMut() -> S + 'a,
) -> ElementBuilder<'a, G, impl FnOnce(Scope<'a>) -> G + 'a> {
self.map(|cx, el| {
let memo = create_memo(cx, f);
Self::dyn_c_internal(cx, el, move || {
View::new_node(G::text_node(memo.get().as_ref().as_ref()))
});
})
}
pub fn c(
self,
c: impl ElementBuilderOrView<'a, G> + 'a,
) -> ElementBuilder<'a, G, impl FnOnce(Scope<'a>) -> G + 'a> {
self.map(|cx, el| render::insert(cx, el, c.into_view(cx), None, None, true))
}
fn dyn_c_internal(cx: Scope<'a>, el: &G, f: impl FnMut() -> View<G> + 'a) {
#[allow(unused_imports)]
use std::any::{Any, TypeId};
#[cfg(feature = "ssr")]
if TypeId::of::<G>() == TypeId::of::<crate::web::SsrNode>() {
el.append_child(&G::marker_with_text("#"));
let end_marker = G::marker_with_text("/");
el.append_child(&end_marker);
render::insert(
cx,
el,
View::new_dyn(cx, f),
None,
Some(&end_marker),
true,
);
return;
}
#[cfg(feature = "hydrate")]
if TypeId::of::<G>() == TypeId::of::<crate::web::HydrateNode>() {
use crate::utils::hydrate::web::*;
let el_hn = <dyn Any>::downcast_ref::<crate::web::HydrateNode>(el).unwrap();
let initial = get_next_marker(&el_hn.inner_element());
let initial = ::std::mem::ManuallyDrop::new(initial);
let initial = unsafe { ::std::ptr::read(&initial as *const _ as *const _) };
render::insert(
cx,
el,
View::new_dyn(cx, f),
initial,
None,
true,
);
return;
}
let marker = G::marker();
el.append_child(&marker);
render::insert(cx, el, View::new_dyn(cx, f), None, Some(&marker), true);
}
fn dyn_c_internal_scoped(
cx: Scope<'a>,
el: &G,
f: impl FnMut(BoundedScope<'_, 'a>) -> View<G> + 'a,
) {
#[allow(unused_imports)]
use std::any::{Any, TypeId};
#[cfg(feature = "ssr")]
if TypeId::of::<G>() == TypeId::of::<crate::web::SsrNode>() {
el.append_child(&G::marker_with_text("#"));
let end_marker = G::marker_with_text("/");
el.append_child(&end_marker);
render::insert(
cx,
el,
View::new_dyn_scoped(cx, f),
None,
Some(&end_marker),
true,
);
return;
}
#[cfg(feature = "hydrate")]
if TypeId::of::<G>() == TypeId::of::<crate::web::HydrateNode>() {
use crate::utils::hydrate::web::*;
let el_hn = <dyn Any>::downcast_ref::<crate::web::HydrateNode>(el).unwrap();
let initial = get_next_marker(&el_hn.inner_element());
let initial = ::std::mem::ManuallyDrop::new(initial);
let initial = unsafe { ::std::ptr::read(&initial as *const _ as *const _) };
render::insert(
cx,
el,
View::new_dyn_scoped(cx, f),
initial,
None,
true,
);
return;
}
let marker = G::marker();
el.append_child(&marker);
render::insert(
cx,
el,
View::new_dyn_scoped(cx, f),
None,
Some(&marker),
true,
);
}
pub fn dyn_c<O: ElementBuilderOrView<'a, G> + 'a>(
self,
mut f: impl FnMut() -> O + 'a,
) -> ElementBuilder<'a, G, impl FnOnce(Scope<'a>) -> G + 'a> {
self.map(move |cx, el| Self::dyn_c_internal(cx, el, move || f().into_view(cx)))
}
pub fn dyn_if<O1: ElementBuilderOrView<'a, G> + 'a, O2: ElementBuilderOrView<'a, G> + 'a>(
self,
cond: impl Fn() -> bool + 'a,
mut then: impl FnMut() -> O1 + 'a,
mut r#else: impl FnMut() -> O2 + 'a,
) -> ElementBuilder<'a, G, impl FnOnce(Scope<'a>) -> G + 'a> {
let cond = Rc::new(cond);
self.map(move |cx, el| {
Self::dyn_c_internal(cx, el, move || {
if *create_selector(cx, {
let cond = Rc::clone(&cond);
#[allow(clippy::redundant_closure)] move || cond()
})
.get()
{
then().into_view(cx)
} else {
r#else().into_view(cx)
}
});
})
}
pub fn dyn_c_scoped(
self,
f: impl FnMut(BoundedScope<'_, 'a>) -> View<G> + 'a,
) -> ElementBuilder<'a, G, impl FnOnce(Scope<'a>) -> G + 'a> {
self.map(|cx, el| Self::dyn_c_internal_scoped(cx, el, f))
}
pub fn on(
self,
name: &'a str,
handler: impl Fn(G::EventType) + 'a,
) -> ElementBuilder<'a, G, impl FnOnce(Scope<'a>) -> G + 'a> {
self.map(move |cx, el| el.event(cx, name, Box::new(handler)))
}
pub fn bind_ref(
self,
node_ref: NodeRef<G>,
) -> ElementBuilder<'a, G, impl FnOnce(Scope<'a>) -> G + 'a> {
self.map(move |_, el| node_ref.set(el.clone()))
}
pub fn view(self, cx: Scope<'a>) -> View<G> {
let el = (self.0)(cx);
View::new_node(el)
}
}
#[cfg(feature = "web")]
impl<'a, G: Html, F: FnOnce(Scope<'a>) -> G + 'a> ElementBuilder<'a, G, F> {
pub fn bind_value(
self,
sub: &'a Signal<String>,
) -> ElementBuilder<'a, G, impl FnOnce(Scope<'a>) -> G + 'a> {
self.map(move |cx, el| {
create_effect(cx, {
let el = el.clone();
move || {
el.set_property("value", &sub.get().as_str().into());
}
});
el.event(
cx,
"input",
Box::new(move |e: web_sys::Event| {
let val = js_sys::Reflect::get(
&e.target().expect("missing target on input event"),
&"value".into(),
)
.expect("missing property `value`")
.as_string()
.expect("value should be a string");
sub.set(val);
}),
);
})
}
pub fn bind_checked(
self,
sub: &'a Signal<bool>,
) -> ElementBuilder<'a, G, impl FnOnce(Scope<'a>) -> G + 'a> {
self.map(move |cx, el| {
create_effect(cx, {
let el = el.clone();
move || {
el.set_property("checked", &(*sub.get()).into());
}
});
el.event(
cx,
"change",
Box::new(move |e: web_sys::Event| {
let val = js_sys::Reflect::get(
&e.target().expect("missing target on change event"),
&"checked".into(),
)
.expect("missing property `checked`")
.as_bool()
.expect("could not get property `checked` as a bool");
sub.set(val);
}),
);
})
}
}
pub fn component<G>(f: impl FnOnce() -> View<G>) -> View<G>
where
G: GenericNode,
{
component_scope(f)
}
pub fn fragment<G, const N: usize>(parts: [View<G>; N]) -> View<G>
where
G: GenericNode,
{
View::new_fragment(Vec::from_iter(parts.to_vec()))
}
pub fn t<G: GenericNode>(t: impl AsRef<str>) -> View<G> {
View::new_node(G::text_node(t.as_ref()))
}
pub fn dyn_t<'a, G: GenericNode, S: AsRef<str>>(
cx: Scope<'a>,
mut f: impl FnMut() -> S + 'a,
) -> View<G> {
View::new_dyn(cx, move || View::new_node(G::text_node(f().as_ref())))
}