//! The builder pattern API for creating UI elements.
//!
//! This API is rendering-backend agnostic and can be used with any rendering backend, not just
//! HTML.
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;
/// The prelude for the builder API. This is independent from the _sycamore prelude_, aka.
/// [`sycamore::prelude`].
///
/// In most cases, it is idiomatic to use a glob import (aka wildcard import) at the beginning of
/// your Rust source file.
///
/// ```rust
/// use sycamore::builder::prelude::*;
/// use sycamore::prelude::*;
/// ```
pub mod prelude {
pub use super::{component, dyn_t, fragment, t, tag};
#[cfg(feature = "web")]
pub use crate::web::html::*;
}
/// A factory for building [`View`]s.
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()
}
}
/// A trait that is implemented only for [`ElementBuilder`] and [`View`].
/// This should be considered implementation details and should not be used.
pub trait ElementBuilderOrView<'a, G: GenericNode> {
/// Convert into a [`View`].
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)
}
}
/// Construct a new [`ElementBuilder`] from a tag name.
///
/// # Example
/// ```
/// # use sycamore::builder::prelude::*;
/// # use sycamore::prelude::*;
/// # fn _test1<G: GenericNode>(cx: Scope) -> View<G> {
/// tag("a") // Not recommended. Use `a()` instead.
/// # .view(cx) }
/// # fn _test2<G: GenericNode>(cx: Scope) -> View<G> {
/// tag("button") // Not recommended. Use `button()` instead.
/// # .view(cx) }
/// # fn _test3<G: GenericNode>(cx: Scope) -> View<G> {
/// tag("my-custom-element")
/// # .view(cx) }
/// // etc...
/// ```
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)
}
/// Utility function for composing new [`ElementBuilder`]s.
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
})
}
/// Set the attribute of the element.
///
/// # Example
/// ```
/// # use sycamore::builder::prelude::*;
/// # use sycamore::prelude::*;
/// # fn _test<G: GenericNode>(cx: Scope) -> View<G> {
/// button().attr("type", "submit")
/// # .view(cx) }
/// ```
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()))
}
/// Set the boolean attribute of the element.
///
/// # Example
/// ```
/// # use sycamore::builder::prelude::*;
/// # use sycamore::prelude::*;
/// # fn _test<G: GenericNode>(cx: Scope) -> View<G> {
/// input().bool_attr("required", true)
/// # .view(cx) }
/// ```
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, "");
}
})
}
/// Adds a dynamic attribute on the node.
///
/// If `value` is `None`, the attribute will be removed from the node.
///
/// # Example
/// ```
/// # use sycamore::builder::prelude::*;
/// # use sycamore::prelude::*;
/// # fn _test<G: GenericNode>(cx: Scope) -> View<G> {
/// let input_type = create_signal(cx, "text");
/// input().dyn_attr("type", || Some(*input_type.get()))
/// # .view(cx) }
/// ```
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);
}
});
})
}
/// Adds a dynamic boolean attribute on the node.
///
/// # Example
/// ```
/// # use sycamore::builder::prelude::*;
/// # use sycamore::prelude::*;
/// # fn _test<G: GenericNode>(cx: Scope) -> View<G> {
/// let required = create_signal(cx, true);
/// input().dyn_bool_attr("required", || *required.get())
/// # .view(cx) }
/// ```
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);
}
});
})
}
/// Set the inner html of the element.
///
/// This will clear any children that have been added with `.c()` or `.t()`.
///
/// The html will not be parsed in non-browser environments. This means that accessing methods
/// such as [`first_child`](GenericNode::first_child) will return `None`.
/// # Example
/// ```
/// # use sycamore::builder::prelude::*;
/// # use sycamore::prelude::*;
/// # fn _test<G: GenericNode>(cx: Scope) -> View<G> {
/// button().dangerously_set_inner_html("<p>Raw HTML!</p>")
/// # .view(cx) }
/// ```
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()))
}
/// Dynamically set the inner html of the element.
///
/// This will clear any children that have been added with `.c()` or `.t()`.
///
/// The html will not be parsed in non-browser environments. This means that accessing methods
/// such as [`first_child`](GenericNode::first_child) will return `None`.
/// # Example
/// ```
/// # use sycamore::builder::prelude::*;
/// # use sycamore::prelude::*;
/// # fn _test<G: GenericNode>(cx: Scope) -> View<G> {
/// button().dyn_dangerously_set_inner_html(|| "<p>Raw HTML!</p>")
/// # .view(cx) }
/// ```
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());
});
})
}
/// Adds a class to the element. This is a shorthand for [`Self::attr`] with the `class`
/// attribute.
///
/// # Example
/// ```
/// # use sycamore::builder::prelude::*;
/// # use sycamore::prelude::*;
/// # fn _test<G: GenericNode>(cx: Scope) -> View<G> {
/// button().class("bg-green-500").t("My button")
/// # .view(cx) }
/// ```
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()))
}
/// Adds a dynamic class on the node.
///
/// If `value` is `None`, the class will be removed from the element.
///
/// # Example
/// # Example
/// ```
/// # use sycamore::builder::prelude::*;
/// # use sycamore::prelude::*;
/// # fn _test<G: GenericNode>(cx: Scope) -> View<G> {
/// let checked_class = create_signal(cx, false);
/// input()
/// .attr("type", "checkbox")
/// .dyn_class("bg-red-500", || *checked_class.get())
/// # .view(cx) }
/// ```
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());
}
});
})
}
/// Sets the id of an element. This is a shorthand for [`Self::attr`] with the `id` attribute.
///
/// # Example
/// ```
/// # use sycamore::builder::prelude::*;
/// # use sycamore::prelude::*;
/// # fn _test<G: GenericNode>(cx: Scope) -> View<G> {
/// button().id("my-button")
/// # .view(cx) }
/// ```
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()))
}
/// Set a property on the element.
///
/// # Example
/// ```
/// # use sycamore::builder::prelude::*;
/// # use sycamore::prelude::*;
/// # fn _test<G: Html>(cx: Scope) -> View<G> {
/// input().prop("value", "I am the value set.")
/// # .view(cx) }
/// ```
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()))
}
/// Set a dynamic property on the element.
///
/// # Example
/// ```
/// # use sycamore::builder::prelude::*;
/// # use sycamore::prelude::*;
/// # fn _test<G: Html>(cx: Scope) -> View<G> {
/// let checked = create_signal(cx, false);
/// input()
/// .attr("type", "checkbox")
/// .dyn_prop("checked", || *checked.get())
/// # .view(cx) }
/// ```
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());
});
})
}
/// Insert a text node under this element. The inserted child is static by default.
///
/// # Example
/// ```
/// # use sycamore::builder::prelude::*;
/// # use sycamore::prelude::*;
/// # fn _test<G: GenericNode>(cx: Scope) -> View<G> {
/// p()
/// .t("Hello World!")
/// .t("Text nodes can be chained as well.")
/// .t("More text...")
/// # .view(cx) }
/// ```
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)))
}
/// Adds a dynamic text node.
///
/// # Example
/// ```
/// # use sycamore::builder::prelude::*;
/// # use sycamore::prelude::*;
/// # fn _test<G: GenericNode>(cx: Scope) -> View<G> {
/// let name = create_signal(cx, "Sycamore");
/// p()
/// .t("Name: ")
/// .dyn_t(|| name.get().to_string())
/// # .view(cx) }
/// ```
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()))
});
})
}
/// Insert a child node under this element. The inserted child is static by default.
///
/// # Example
/// ```
/// # use sycamore::builder::prelude::*;
/// # use sycamore::prelude::*;
/// # fn _test<G: GenericNode>(cx: Scope) -> View<G> {
/// div().c(
/// h1().t("I am a child")
/// )
/// # .view(cx) }
/// ```
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))
}
/// Internal implementation for [`Self::dyn_c`] and [`Self::dyn_t`].
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>() {
// If Server Side Rendering, insert beginning tag for hydration purposes.
el.append_child(&G::marker_with_text("#"));
// Create end marker. This is needed to make sure that the node is inserted into the
// right place.
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, /* We don't know if this is the only child or not so we pessimistically
* set this to true. */
);
return;
}
#[cfg(feature = "hydrate")]
if TypeId::of::<G>() == TypeId::of::<crate::web::HydrateNode>() {
use crate::utils::hydrate::web::*;
// Get start and end markers.
let el_hn = <dyn Any>::downcast_ref::<crate::web::HydrateNode>(el).unwrap();
let initial = get_next_marker(&el_hn.inner_element());
// Do not drop the HydrateNode because it will be cast into a GenericNode.
let initial = ::std::mem::ManuallyDrop::new(initial);
// SAFETY: This is safe because we already checked that the type is HydrateNode.
// __initial is wrapped inside ManuallyDrop to prevent double drop.
let initial = unsafe { ::std::ptr::read(&initial as *const _ as *const _) };
render::insert(
cx,
el,
View::new_dyn(cx, f),
initial,
None,
true, /* We don't know if this is the only child or not so we pessimistically
* set this to true. */
);
return;
}
// G is neither SsrNode nor HydrateNode. Proceed normally.
let marker = G::marker();
el.append_child(&marker);
render::insert(cx, el, View::new_dyn(cx, f), None, Some(&marker), true);
}
/// Internal implementation for [`Self::dyn_c_scoped`] and [`Self::dyn_if`].
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>() {
// If Server Side Rendering, insert beginning tag for hydration purposes.
el.append_child(&G::marker_with_text("#"));
// Create end marker. This is needed to make sure that the node is inserted into the
// right place.
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, /* We don't know if this is the only child or not so we
* pessimistically set this to true. */
);
return;
}
#[cfg(feature = "hydrate")]
if TypeId::of::<G>() == TypeId::of::<crate::web::HydrateNode>() {
use crate::utils::hydrate::web::*;
// Get start and end markers.
let el_hn = <dyn Any>::downcast_ref::<crate::web::HydrateNode>(el).unwrap();
let initial = get_next_marker(&el_hn.inner_element());
// Do not drop the HydrateNode because it will be cast into a GenericNode.
let initial = ::std::mem::ManuallyDrop::new(initial);
// SAFETY: This is safe because we already checked that the type is HydrateNode.
// __initial is wrapped inside ManuallyDrop to prevent double drop.
let initial = unsafe { ::std::ptr::read(&initial as *const _ as *const _) };
render::insert(
cx,
el,
View::new_dyn_scoped(cx, f),
initial,
None,
true, /* We don't know if this is the only child or not so we
* pessimistically set this to true. */
);
return;
}
// G is neither SsrNode nor HydrateNode. Proceed normally.
let marker = G::marker();
el.append_child(&marker);
render::insert(
cx,
el,
View::new_dyn_scoped(cx, f),
None,
Some(&marker),
true,
);
}
/// Adds a dynamic child. Note that most times, [`dyn_if`](Self::dyn_if) can be used instead
/// which is more ergonomic.
///
/// # Example
/// ```
/// # use sycamore::builder::prelude::*;
/// # use sycamore::prelude::*;
/// # fn some_view<G: GenericNode>() -> View<G> { todo!() }
/// # fn _test<G: GenericNode>(cx: Scope) -> View<G> {
/// let a_view = || some_view();
/// div().dyn_c(a_view)
/// # .view(cx) }
/// ```
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)))
}
/// Adds a dynamic, conditional view.
///
/// # Example
/// ```
/// # use sycamore::builder::prelude::*;
/// # use sycamore::prelude::*;
/// # fn _test<G: GenericNode>(cx: Scope) -> View<G> {
/// let visible = create_signal(cx, true);
/// div().dyn_if(
/// || *visible.get(),
/// || p().t("Now you see me"),
/// || p().t("Now you don't!"),
/// )
/// # .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| {
// FIXME: should be dyn_c_internal_scoped to prevent memory leaks.
Self::dyn_c_internal(cx, el, move || {
if *create_selector(cx, {
let cond = Rc::clone(&cond);
#[allow(clippy::redundant_closure)] // FIXME: clippy false positive
move || cond()
})
.get()
{
then().into_view(cx)
} else {
r#else().into_view(cx)
}
});
})
}
/// Adds a dynamic child that is created in a new reactive scope.
///
/// [`dyn_c`](Self::dyn_c) uses [`create_effect`] whereas this method uses
/// [`create_effect_scoped`].
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))
}
/// Attach an event handler to the element.
///
/// # Example
/// ```
/// # use sycamore::builder::prelude::*;
/// # use sycamore::prelude::*;
/// # fn _test<G: GenericNode>(cx: Scope) -> View<G> {
/// button()
/// .t("My button")
/// .on("click", |_| web_sys::console::log_1(&"Clicked".into()))
/// # .view(cx) }
/// ```
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)))
}
/// Get a hold of the raw element by using a [`NodeRef`].
///
/// # Example
/// ```
/// # use sycamore::builder::prelude::*;
/// # use sycamore::prelude::*;
/// # fn _test<G: GenericNode>(cx: Scope) -> View<G> {
/// let node_ref = create_node_ref(cx);
/// input().bind_ref(node_ref.clone())
/// # .view(cx) }
/// ```
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()))
}
/// Construct a [`View`] by evaluating the lazy [`ElementBuilder`].
///
/// This is the method that should be called at the end of the building chain.
///
/// # Example
/// ```
/// # use sycamore::builder::prelude::*;
/// # use sycamore::prelude::*;
/// #[component]
/// fn MyComponent<G: GenericNode>(cx: Scope) -> View<G> {
/// div()
/// /* builder stuff... */
/// .view(cx)
/// }
/// ```
pub fn view(self, cx: Scope<'a>) -> View<G> {
let el = (self.0)(cx);
View::new_node(el)
}
}
/// HTML-specific builder methods.
#[cfg(feature = "web")]
impl<'a, G: Html, F: FnOnce(Scope<'a>) -> G + 'a> ElementBuilder<'a, G, F> {
/// Binds a [`Signal`] to the `value` property of the node.
///
/// The [`Signal`] will be automatically updated when the value is updated.
///
/// # Example
/// ```
/// # use sycamore::builder::prelude::*;
/// # use sycamore::prelude::*;
/// # fn _test<G: Html>(cx: Scope) -> View<G> {
/// let value = create_signal(cx, String::new());
/// input().bind_value(value)
/// # .view(cx) }
/// ```
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);
}),
);
})
}
/// Binds a [`Signal`] to the `checked` property of the node.
///
/// The [`Signal`] will be automatically updated when the value is updated.
///
/// # Example
/// ```
/// # use sycamore::builder::prelude::*;
/// # use sycamore::prelude::*;
/// # fn _test<G: Html>(cx: Scope) -> View<G> {
/// let checked = create_signal(cx, true);
/// input().attr("type", "checkbox").bind_checked(checked)
/// # .view(cx) }
/// ```
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);
}),
);
})
}
}
/// Instantiate a component as a [`View`].
///
/// # Example
/// ```
/// # use sycamore::builder::prelude::*;
/// # use sycamore::prelude::*;
/// #[component]
/// fn MyComponent<G: GenericNode>(cx: Scope) -> View<G> {
/// h1().t("I am a component").view(cx)
/// }
///
/// // Elsewhere...
/// # fn view<G: Html>(cx: Scope) -> View<G> {
/// component(|| MyComponent(cx))
/// # }
/// ```
pub fn component<G>(f: impl FnOnce() -> View<G>) -> View<G>
where
G: GenericNode,
{
component_scope(f)
}
/// Create a [`View`] from an array of [`View`].
///
/// # Example
/// ```
/// # use sycamore::builder::prelude::*;
/// # use sycamore::prelude::*;
/// # fn _test<G: GenericNode>(cx: Scope) -> View<G> {
/// fragment([
/// div().view(cx),
/// div().view(cx),
/// ])
/// # }
/// ```
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()))
}
/// Construct a new top-level text [`View`].
///
/// # Example
/// ```
/// # use sycamore::builder::prelude::*;
/// # use sycamore::prelude::*;
/// # fn _test1<G: GenericNode>(cx: Scope) -> View<G> {
/// t("Hello!")
/// # }
/// # fn _test2<G: GenericNode>(cx: Scope) -> View<G> {
/// t("This is top level text.")
/// # }
/// # fn _test3<G: GenericNode>(cx: Scope) -> View<G> {
/// t("We aren't directly nested under an element.")
/// # }
/// // etc...
/// ```
pub fn t<G: GenericNode>(t: impl AsRef<str>) -> View<G> {
View::new_node(G::text_node(t.as_ref()))
}
/// Construct a new top-level dynamic text [`View`].
///
/// # Example
/// ```
/// # use sycamore::builder::prelude::*;
/// # use sycamore::prelude::*;
/// # fn _test<G: GenericNode>(cx: Scope) -> View<G> {
/// dyn_t(cx, || "Hello!")
/// # }
/// ```
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())))
}