Use `#[component]` macro for core components, deleting `leptos_core` package

This commit is contained in:
Greg Johnston 2022-12-22 16:46:48 -05:00
parent 34b4917837
commit 7c25cd9200
14 changed files with 347 additions and 598 deletions

View File

@ -3,7 +3,6 @@ members = [
# core
"leptos",
"leptos_dom",
"leptos_core",
"leptos_macro",
"leptos_reactive",
"leptos_server",

View File

@ -9,12 +9,14 @@ description = "Leptos is a full-stack, isomorphic Rust web framework leveraging
readme = "../README.md"
[dependencies]
cfg-if = "1"
leptos_config = { path = "../leptos_config", default-features = false, version = "0.0.18" }
leptos_core = { path = "../leptos_core", default-features = false, version = "0.0.18" }
leptos_dom = { path = "../leptos_dom", default-features = false, version = "0.0.18" }
leptos_macro = { path = "../leptos_macro", default-features = false, version = "0.0.18" }
leptos_reactive = { path = "../leptos_reactive", default-features = false, version = "0.0.18" }
leptos_server = { path = "../leptos_server", default-features = false, version = "0.0.18" }
tracing = "0.1"
typed-builder = "0.11"
[dev-dependencies]
leptos = { path = ".", default-features = false }
@ -23,7 +25,6 @@ leptos = { path = ".", default-features = false }
default = ["csr", "serde"]
csr = [
"leptos/csr",
"leptos_core/csr",
"leptos_dom/web",
"leptos_macro/csr",
"leptos_reactive/csr",
@ -31,7 +32,6 @@ csr = [
]
hydrate = [
"leptos/hydrate",
"leptos_core/hydrate",
"leptos_dom/web",
"leptos_macro/hydrate",
"leptos_reactive/hydrate",
@ -40,14 +40,12 @@ hydrate = [
ssr = [
"leptos/ssr",
"leptos_dom/ssr",
"leptos_core/ssr",
"leptos_macro/ssr",
"leptos_reactive/ssr",
"leptos_server/ssr",
]
stable = [
"leptos/stable",
"leptos_core/stable",
"leptos_dom/stable",
"leptos_macro/stable",
"leptos_reactive/stable",

63
leptos/src/for_loop.rs Normal file
View File

@ -0,0 +1,63 @@
use leptos_dom::IntoView;
use leptos_macro::component;
use leptos_reactive::Scope;
use std::hash::Hash;
/// Iterates over children and displays them, keyed by the `key` function given.
///
/// This is much more efficient than naively iterating over nodes with `.iter().map(|n| view! { cx, ... })...`,
/// as it avoids re-creating DOM nodes that are not being changed.
///
/// ```
/// # use leptos::*;
///
/// #[derive(Copy, Clone, Debug, PartialEq, Eq)]
/// struct Counter {
/// id: HydrationKey,
/// count: RwSignal<i32>
/// }
///
/// fn Counters(cx: Scope) -> Element {
/// let (counters, set_counters) = create_signal::<Vec<Counter>>(cx, vec![]);
///
/// view! {
/// cx,
/// <div>
/// <For
/// // a function that returns the items we're iterating over; a signal is fine
/// each=counters
/// // a unique key for each item
/// key=|counter| counter.id
/// // renders each item to a view
/// view=move |counter: Counter| {
/// view! {
/// cx,
/// <button>"Value: " {move || counter.count.get()}</button>
/// }
/// }
/// />
/// </div>
/// }
/// }
/// ```
#[component]
pub fn For<IF, I, T, EF, N, KF, K>(
cx: Scope,
/// Items over which the component should iterate.
each: IF,
/// A key function that will be applied to each item.
key: KF,
/// The view that will be displayed for each item.
view: EF,
) -> impl IntoView
where
IF: Fn() -> I + 'static,
I: IntoIterator<Item = T>,
EF: Fn(T) -> N + 'static,
N: IntoView,
KF: Fn(&T) -> K + 'static,
K: Eq + Hash + 'static,
T: 'static,
{
leptos_dom::Each::new(each, key, view).into_view(cx)
}

View File

@ -127,7 +127,6 @@
//! ```
pub use leptos_config::*;
pub use leptos_core::*;
pub use leptos_dom;
pub use leptos_dom::wasm_bindgen::{JsCast, UnwrapThrowExt};
pub use leptos_dom::*;
@ -136,4 +135,16 @@ pub use leptos_reactive::*;
pub use leptos_server;
pub use leptos_server::*;
pub use tracing;
pub use typed_builder;
mod for_loop;
pub use for_loop::*;
mod suspense;
pub use suspense::*;
mod transition;
pub use transition::*;
pub use leptos_reactive::debug_warn;
extern crate self as leptos;

117
leptos/src/suspense.rs Normal file
View File

@ -0,0 +1,117 @@
use cfg_if::cfg_if;
use leptos_macro::component;
use std::rc::Rc;
use leptos_dom::{Fragment, HydrationCtx, IntoView};
use leptos_reactive::{provide_context, Scope, SuspenseContext};
/// If any [Resources](leptos_reactive::Resource) are read in the `children` of this
/// component, it will show the `fallback` while they are loading. Once all are resolved,
/// it will render the `children`.
///
/// Note that the `children` will be rendered initially (in order to capture the fact that
/// those resources are read under the suspense), so you cannot assume that resources have
/// `Some` value in `children`.
///
/// ```
/// # use leptos_reactive::*;
/// # use leptos_core::*;
/// # use leptos_macro::*;
/// # use leptos_dom::*; use leptos::*;
/// # run_scope(create_runtime(), |cx| {
/// # if cfg!(not(any(feature = "csr", feature = "hydrate", feature = "ssr"))) {
/// async fn fetch_cats(how_many: u32) -> Result<Vec<String>, ()> { Ok(vec![]) }
///
/// let (cat_count, set_cat_count) = create_signal::<u32>(cx, 1);
///
/// let cats = create_resource(cx, cat_count, |count| fetch_cats(count));
///
/// view! { cx,
/// <div>
/// <Suspense fallback=move || view! { cx, <p>"Loading (Suspense Fallback)..."</p> }>
/// {move || {
/// cats.read().map(|data| match data {
/// Err(_) => view! { cx, <pre>"Error"</pre> },
/// Ok(cats) => view! { cx,
/// <div>{
/// cats.iter()
/// .map(|src| {
/// view! { cx,
/// <img src={src}/>
/// }
/// })
/// .collect::<Vec<_>>()
/// }</div>
/// },
/// })
/// }
/// }
/// </Suspense>
/// </div>
/// };
/// # }
/// # });
/// ```
#[component]
pub fn Suspense<F, E>(
cx: Scope,
/// Returns a fallback UI that will be shown while `async` [Resources](leptos_reactive::Resource) are still loading.
fallback: F,
/// Children will be displayed once all `async` [Resources](leptos_reactive::Resource) have resolved.
children: Box<dyn Fn(Scope) -> Fragment>,
) -> impl IntoView
where
F: Fn() -> E + 'static,
E: IntoView,
{
let context = SuspenseContext::new(cx);
// provide this SuspenseContext to any resources below it
provide_context(cx, context);
let current_id = HydrationCtx::peek();
let orig_child = Rc::new(children);
cfg_if! {
if #[cfg(any(feature = "csr", feature = "hydrate"))] {
if context.ready() {
HydrationCtx::continue_from(current_id);
orig_child(cx).into_view(cx)
} else {
HydrationCtx::continue_from(current_id);
fallback().into_view(cx)
}
} else {
// run the child; we'll probably throw this away, but it will register resource reads
let child = orig_child(cx).into_view(cx);
let initial = {
// no resources were read under this, so just return the child
if context.pending_resources.get() == 0 {
child.clone()
}
// show the fallback, but also prepare to stream HTML
else {
let orig_child = Rc::clone(&orig_child);
cx.register_suspense(context, &current_id.to_string(), {
let current_id = current_id.clone();
move || {
HydrationCtx::continue_from(current_id);
orig_child(cx)
.into_view(cx)
.render_to_string(cx)
.to_string()
}
});
// return the fallback for now, wrapped in fragment identifer
fallback().into_view(cx)
}
};
HydrationCtx::continue_from(current_id);
initial
}
}
}

147
leptos/src/transition.rs Normal file
View File

@ -0,0 +1,147 @@
use cfg_if::cfg_if;
use leptos_dom::{DynChild, Fragment, IntoView, HydrationCtx};
use leptos_macro::component;
use leptos_reactive::{provide_context, Scope, SignalSetter, SuspenseContext};
use std::rc::Rc;
/// If any [Resource](leptos_reactive::Resource)s are read in the `children` of this
/// component, it will show the `fallback` while they are loading. Once all are resolved,
/// it will render the `children`. Unlike [`Suspense`](crate::Suspense), this will not fall
/// back to the `fallback` state if there are further changes after the initial load.
///
/// Note that the `children` will be rendered initially (in order to capture the fact that
/// those resources are read under the suspense), so you cannot assume that resources have
/// `Some` value in `children`.
///
/// ```
/// # use leptos_reactive::*;
/// # use leptos_core::*;
/// # use leptos_macro::*;
/// # use leptos_dom::*; use leptos::*;
/// # run_scope(create_runtime(), |cx| {
/// # if cfg!(not(any(feature = "csr", feature = "hydrate", feature = "ssr"))) {
/// async fn fetch_cats(how_many: u32) -> Result<Vec<String>, ()> { Ok(vec![]) }
///
/// let (cat_count, set_cat_count) = create_signal::<u32>(cx, 1);
/// let (pending, set_pending) = create_signal(cx, false);
///
/// let cats = create_resource(cx, cat_count, |count| fetch_cats(count));
///
/// view! { cx,
/// <div>
/// <Transition
/// fallback=move || view! { cx, <p>"Loading..."</p>}
/// set_pending=set_pending
/// >
/// {move || {
/// cats.read().map(|data| match data {
/// Err(_) => view! { cx, <pre>"Error"</pre> },
/// Ok(cats) => view! { cx,
/// <div>{
/// cats.iter()
/// .map(|src| {
/// view! { cx,
/// <img src={src}/>
/// }
/// })
/// .collect::<Vec<_>>()
/// }</div>
/// },
/// })
/// }
/// }
/// </Transition>
/// </div>
/// };
/// # }
/// # });
/// ```
#[component]
pub fn Transition<F, E>(
cx: Scope,
/// Will be displayed while resources are pending.
fallback: F,
/// A function that will be called when the component transitions into or out of
/// the `pending` state, with its argument indicating whether it is pending (`true`)
/// or not pending (`false`).
#[prop(optional)]
set_pending: Option<SignalSetter<bool>>,
/// Will be displayed once all resources have resolved.
children: Box<dyn Fn(Scope) -> Fragment>
) -> impl IntoView
where
F: Fn() -> E + 'static,
E: IntoView,
{
let context = SuspenseContext::new(cx);
// provide this SuspenseContext to any resources below it
provide_context(cx, context);
let orig_child = Rc::new(children);
cfg_if! {
if #[cfg(any(feature = "csr", feature = "hydrate"))] {
use std::cell::RefCell;
let prev_child = RefCell::new(None);
let cached_id = HydrationCtx::peek();
DynChild::new(move || {
let mut id_to_replace = cached_id.clone();
id_to_replace.offset += 2;
HydrationCtx::continue_from(id_to_replace);
if context.ready() {
let current_child = orig_child(cx).into_view(cx);
*prev_child.borrow_mut() = Some(current_child.clone());
if let Some(pending) = &set_pending {
pending.set(false);
}
current_child
} else if let Some(prev_child) = &*prev_child.borrow() {
if let Some(pending) = &set_pending {
pending.set(true);
}
prev_child.clone()
} else {
if let Some(pending) = &set_pending {
pending.set(true);
}
let fallback = fallback().into_view(cx);
*prev_child.borrow_mut() = Some(fallback.clone());
fallback
}
})
} else {
let current_id = HydrationCtx::peek();
DynChild::new(move || {
// run the child; we'll probably throw this away, but it will register resource reads
let child = orig_child(cx).into_view(cx);
let initial = {
// no resources were read under this, so just return the child
if context.pending_resources.get() == 0 {
child.clone()
}
// show the fallback, but also prepare to stream HTML
else {
let orig_child = Rc::clone(&orig_child);
cx.register_suspense(context, &current_id.to_string(), move || {
orig_child(cx)
.into_view(cx)
.render_to_string(cx)
.to_string()
});
// return the fallback for now, wrapped in fragment identifer
fallback().into_view(cx)
}
};
initial
}).into_view(cx)
}
}
}

View File

@ -1,46 +0,0 @@
[package]
name = "leptos_core"
version = "0.0.18"
edition = "2021"
authors = ["Greg Johnston"]
license = "MIT"
repository = "https://github.com/gbj/leptos"
description = "Core functionality for the Leptos web framework."
[dependencies]
leptos_dom = { path = "../leptos_dom", default-features = false, version = "0.0.18" }
leptos_macro = { path = "../leptos_macro", default-features = false, version = "0.0.18" }
leptos_reactive = { path = "../leptos_reactive", default-features = false, version = "0.0.18" }
log = "0.4"
typed-builder = "0.11"
[dev-dependencies]
leptos = { path = "../leptos", default-features = false, version = "0.0" }
[features]
csr = [
"leptos/csr",
"leptos_dom/web",
"leptos_macro/csr",
"leptos_reactive/csr",
]
hydrate = [
"leptos/hydrate",
"leptos_dom/web",
"leptos_macro/hydrate",
"leptos_reactive/hydrate",
]
ssr = [
"leptos/ssr",
"leptos_macro/ssr",
"leptos_reactive/ssr",
]
stable = [
"leptos/stable",
"leptos_dom/stable",
"leptos_macro/stable",
"leptos_reactive/stable",
]
[package.metadata.cargo-all-features]
denylist = ["stable"]

View File

@ -1,12 +0,0 @@
#![deny(missing_docs)]
//! This crate contains several utility pieces that depend on multiple crates.
//! They are all re-exported in the main `leptos` crate.
mod suspense;
mod transition;
pub use suspense::*;
pub use transition::*;
pub use typed_builder::TypedBuilder;

View File

@ -1,156 +0,0 @@
use std::rc::Rc;
use leptos_dom::{Component, Fragment, HydrationCtx, IntoView};
use leptos_reactive::{provide_context, Scope, SuspenseContext};
use typed_builder::TypedBuilder;
/// Props for the [Suspense](crate::Suspense) component, which shows a fallback
/// while [Resource](leptos_reactive::Resource)s are being read.
#[derive(TypedBuilder)]
pub struct SuspenseProps<F, E>
where
F: Fn() -> E + 'static,
E: IntoView,
{
/// Will be displayed while resources are pending.
pub fallback: F,
/// Will be displayed once all resources have resolved.
pub children: Box<dyn Fn(Scope) -> Fragment>,
}
/// If any [Resource](leptos_reactive::Resource)s are read in the `children` of this
/// component, it will show the `fallback` while they are loading. Once all are resolved,
/// it will render the `children`.
///
/// Note that the `children` will be rendered initially (in order to capture the fact that
/// those resources are read under the suspense), so you cannot assume that resources have
/// `Some` value in `children`.
///
/// ```
/// # use leptos_reactive::*;
/// # use leptos_core::*;
/// # use leptos_macro::*;
/// # use leptos_dom::*; use leptos::*;
/// # run_scope(create_runtime(), |cx| {
/// # if cfg!(not(any(feature = "csr", feature = "hydrate", feature = "ssr"))) {
/// async fn fetch_cats(how_many: u32) -> Result<Vec<String>, ()> { Ok(vec![]) }
///
/// let (cat_count, set_cat_count) = create_signal::<u32>(cx, 1);
///
/// let cats = create_resource(cx, cat_count, |count| fetch_cats(count));
///
/// view! { cx,
/// <div>
/// <Suspense fallback=move || view! { cx, <p>"Loading (Suspense Fallback)..."</p> }>
/// {move || {
/// cats.read().map(|data| match data {
/// Err(_) => view! { cx, <pre>"Error"</pre> },
/// Ok(cats) => view! { cx,
/// <div>{
/// cats.iter()
/// .map(|src| {
/// view! { cx,
/// <img src={src}/>
/// }
/// })
/// .collect::<Vec<_>>()
/// }</div>
/// },
/// })
/// }
/// }
/// </Suspense>
/// </div>
/// };
/// # }
/// # });
/// ```
#[allow(non_snake_case)]
pub fn Suspense<F, E>(cx: Scope, props: SuspenseProps<F, E>) -> impl IntoView
where
F: Fn() -> E + 'static,
E: IntoView,
{
let context = SuspenseContext::new(cx);
// provide this SuspenseContext to any resources below it
provide_context(cx, context);
render_suspense(cx, context, props.fallback, Rc::new(move |cx| (props.children)(cx)))
}
#[cfg(any(feature = "csr", feature = "hydrate"))]
fn render_suspense<F, E>(
_cx: Scope,
context: SuspenseContext,
fallback: F,
child: Rc<dyn Fn(Scope) -> Fragment>,
) -> impl IntoView
where
F: Fn() -> E + 'static,
E: IntoView,
{
use leptos_dom::DynChild;
Component::new("Suspense", move |cx| {
let current_id = HydrationCtx::peek();
if context.ready() {
HydrationCtx::continue_from(current_id);
child(cx).into_view(cx)
} else {
HydrationCtx::continue_from(current_id);
fallback().into_view(cx)
}
})
}
#[cfg(not(any(feature = "csr", feature = "hydrate")))]
fn render_suspense<'a, F, E>(
cx: Scope,
context: SuspenseContext,
fallback: F,
orig_child: Rc<dyn Fn(Scope) -> Fragment>,
) -> impl IntoView
where
F: Fn() -> E + 'static,
E: IntoView,
{
use leptos_dom::DynChild;
let orig_child = Rc::clone(&orig_child);
Component::new("Suspense", move |cx| {
let current_id = HydrationCtx::peek();
// run the child; we'll probably throw this away, but it will register resource reads
let child = orig_child(cx).into_view(cx);
let initial = {
// no resources were read under this, so just return the child
if context.pending_resources.get() == 0 {
child.clone()
}
// show the fallback, but also prepare to stream HTML
else {
let orig_child = Rc::clone(&orig_child);
cx.register_suspense(context, &current_id.to_string(), {
let current_id = current_id.clone();
move || {
HydrationCtx::continue_from(current_id);
orig_child(cx)
.into_view(cx)
.render_to_string(cx)
.to_string()
}
});
// return the fallback for now, wrapped in fragment identifer
fallback().into_view(cx)
}
};
HydrationCtx::continue_from(current_id);
initial
})
}

View File

@ -1,189 +0,0 @@
use leptos_dom::{Component, DynChild, Fragment, IntoView, HydrationCtx};
use leptos_reactive::{provide_context, Scope, SignalSetter, SuspenseContext};
use typed_builder::TypedBuilder;
use std::rc::Rc;
/// Props for the [Suspense](crate::Suspense) component, which shows a fallback
/// while [Resource](leptos_reactive::Resource)s are being read.
#[derive(TypedBuilder)]
pub struct TransitionProps<F, E>
where
F: Fn() -> E + 'static,
E: IntoView,
{
/// Will be displayed while resources are pending.
pub fallback: F,
/// A function that will be called when the component transitions into or out of
/// the `pending` state, with its argument indicating whether it is pending (`true`)
/// or not pending (`false`).
#[builder(default, setter(strip_option, into))]
pub set_pending: Option<SignalSetter<bool>>,
/// Will be displayed once all resources have resolved.
pub children: Box<dyn Fn(Scope) -> Fragment>,
}
/// If any [Resource](leptos_reactive::Resource)s are read in the `children` of this
/// component, it will show the `fallback` while they are loading. Once all are resolved,
/// it will render the `children`. Unlike [`Suspense`](crate::Suspense), this will not fall
/// back to the `fallback` state if there are further changes after the initial load.
///
/// Note that the `children` will be rendered initially (in order to capture the fact that
/// those resources are read under the suspense), so you cannot assume that resources have
/// `Some` value in `children`.
///
/// ```
/// # use leptos_reactive::*;
/// # use leptos_core::*;
/// # use leptos_macro::*;
/// # use leptos_dom::*; use leptos::*;
/// # run_scope(create_runtime(), |cx| {
/// # if cfg!(not(any(feature = "csr", feature = "hydrate", feature = "ssr"))) {
/// async fn fetch_cats(how_many: u32) -> Result<Vec<String>, ()> { Ok(vec![]) }
///
/// let (cat_count, set_cat_count) = create_signal::<u32>(cx, 1);
/// let (pending, set_pending) = create_signal(cx, false);
///
/// let cats = create_resource(cx, cat_count, |count| fetch_cats(count));
///
/// view! { cx,
/// <div>
/// <Transition
/// fallback=move || view! { cx, <p>"Loading..."</p>}
/// set_pending=set_pending
/// >
/// {move || {
/// cats.read().map(|data| match data {
/// Err(_) => view! { cx, <pre>"Error"</pre> },
/// Ok(cats) => view! { cx,
/// <div>{
/// cats.iter()
/// .map(|src| {
/// view! { cx,
/// <img src={src}/>
/// }
/// })
/// .collect::<Vec<_>>()
/// }</div>
/// },
/// })
/// }
/// }
/// </Transition>
/// </div>
/// };
/// # }
/// # });
/// ```
#[allow(non_snake_case)]
pub fn Transition<F, E>(cx: Scope, props: TransitionProps<F, E>) -> impl IntoView
where
F: Fn() -> E + 'static,
E: IntoView,
{
let context = SuspenseContext::new(cx);
// provide this SuspenseContext to any resources below it
provide_context(cx, context);
render_transition(
cx,
context,
props.fallback,
Rc::new(move |cx| (props.children)(cx)),
props.set_pending,
)
}
#[cfg(any(feature = "csr", feature = "hydrate"))]
fn render_transition<F, E>(
_cx: Scope,
context: SuspenseContext,
fallback: F,
child: Rc<dyn Fn(Scope) -> Fragment>,
set_pending: Option<SignalSetter<bool>>,
) -> impl IntoView
where
F: Fn() -> E + 'static,
E: IntoView,
{
use std::cell::RefCell;
let prev_child = RefCell::new(None);
Component::new("Transition", move |cx| {
let cached_id = HydrationCtx::peek();
DynChild::new(move || {
let mut id_to_replace = cached_id.clone();
id_to_replace.offset += 2;
HydrationCtx::continue_from(id_to_replace);
if context.ready() {
let current_child = child(cx).into_view(cx);
*prev_child.borrow_mut() = Some(current_child.clone());
if let Some(pending) = &set_pending {
pending.set(false);
}
current_child
} else if let Some(prev_child) = &*prev_child.borrow() {
if let Some(pending) = &set_pending {
pending.set(true);
}
prev_child.clone()
} else {
if let Some(pending) = &set_pending {
pending.set(true);
}
let fallback = fallback().into_view(cx);
*prev_child.borrow_mut() = Some(fallback.clone());
fallback
}
})
})
}
#[cfg(not(any(feature = "csr", feature = "hydrate")))]
fn render_transition<'a, F, E>(
cx: Scope,
context: SuspenseContext,
fallback: F,
orig_child: Rc<dyn Fn(Scope) -> Fragment>,
set_pending: Option<SignalSetter<bool>>,
) -> impl IntoView
where
F: Fn() -> E + 'static,
E: IntoView,
{
let orig_child = Rc::clone(&orig_child);
let current_id = HydrationCtx::peek();
Component::new("Transition", move |cx| {
let current_id = HydrationCtx::peek();
DynChild::new(move || {
// run the child; we'll probably throw this away, but it will register resource reads
let child = orig_child(cx).into_view(cx);
let initial = {
// no resources were read under this, so just return the child
if context.pending_resources.get() == 0 {
child.clone()
}
// show the fallback, but also prepare to stream HTML
else {
let orig_child = Rc::clone(&orig_child);
cx.register_suspense(context, &current_id.to_string(), move || {
orig_child(cx)
.into_view(cx)
.render_to_string(cx)
.to_string()
});
// return the fallback for now, wrapped in fragment identifer
fallback().into_view(cx)
}
};
initial
}).into_view(cx)
})
}

View File

@ -23,7 +23,6 @@ rustc-hash = "1.1.0"
serde_json = "1"
smallvec = "1"
tracing = "0.1"
typed-builder = "0.11"
wasm-bindgen = { version = "0.2", features = ["enable-interning"] }
wasm-bindgen-futures = "0.4.31"

View File

@ -36,97 +36,8 @@ fn main() {
}
fn view_fn(cx: Scope) -> impl IntoView {
let (tick, set_tick) = create_signal(cx, 0);
let (count, set_count) = create_signal(cx, 0);
let (show, set_show) = create_signal(cx, true);
let (iterable, set_iterable) = create_signal(cx, vec![]);
let (disabled, set_disabled) = create_signal(cx, false);
let (apply_default_class_set, set_apply_default_class_set) =
create_signal(cx, false);
// wasm_bindgen_futures::spawn_local(async move {
// loop {
// gloo::timers::future::sleep(std::time::Duration::from_secs(5)).await;
// set_tick.update(|t| *t += 1);
// }
// });
create_effect(cx, move |_| {
tick();
set_count.update(|c| *c += 1);
});
create_effect(cx, move |_| {
tick();
set_show.update(|s| *s = !*s);
});
create_effect(cx, move |_| {
tick();
set_iterable.update(|i| {
if tick() % 2 == 0 {
*i = vec![0, 1, 2, 3];
} else {
*i = vec![0, 1, 2, 3, 4, 5, 6];
}
})
});
create_effect(cx, move |_| {
tick();
set_disabled.update(|d| *d = !*d);
});
create_effect(cx, move |_| {
tick();
set_apply_default_class_set.update(|cs| *cs = !*cs);
});
[
span(cx).into_view(cx),
div(cx)
.attr("t", || true)
.child(span(cx).attr("t", true))
.child(span(cx).attr("t", || true))
.into_view(cx),
h1(cx)
.child(move || text(count().to_string()))
.into_view(cx),
button(cx)
.on(ev::click, move |_| set_tick.update(|t| *t += 1))
.child(text("Tick"))
.into_view(cx),
button(cx)
.on(ev::click, move |_| set_count.update(|n| *n += 1))
.child(text("Click me"))
.into_view(cx),
button(cx)
.on(ev::Undelegated(ev::click), move |_| {
set_count.update(|n| *n += 1)
})
.child(text("Click me (undelegated)"))
.into_view(cx),
pre(cx)
.child(Each::new(iterable, |i| *i, move |i| text(format!("{i}, "))))
.into_view(cx),
pre(cx)
.child(text("0, 1, 2, 3, 4, 5, 6, 7, 8, 9"))
.into_view(cx),
input(cx)
.class("input", true)
.attr("disabled", move || disabled().then_some(""))
.into_view(cx),
MyComponent.into_view(cx),
h3(cx)
.child(move || show().then(|| text("Now you see me...")))
.into_view(cx),
]
let my_in = input(cx).attr("type", "text");
let val = my_in.value();
}
struct MyComponent;

View File

@ -37,12 +37,9 @@ cfg_if! {
}
}
use leptos_reactive::Scope;
use smallvec::SmallVec;
use std::{borrow::Cow, cell::RefCell, fmt, hash::Hash, ops::Deref, rc::Rc};
use typed_builder::TypedBuilder;
/// The internal representation of the [`EachKey`] core-component.
#[derive(Clone, PartialEq, Eq)]
@ -544,7 +541,7 @@ enum DiffOpAddMode {
#[cfg(all(target_arch = "wasm32", feature = "web"))]
fn apply_cmds<T, EF, N>(
cx: Scope,
cx: leptos_reactive::Scope,
opening: &web_sys::Node,
closing: &web_sys::Node,
mut cmds: Diff,
@ -657,94 +654,4 @@ fn apply_cmds<T, EF, N>(
// Now, remove the holes that might have been left from removing
// items
children.drain_filter(|c| c.is_none());
}
/// Properties for the [For](crate::For) component, a keyed list.
#[derive(TypedBuilder)]
#[builder(doc)]
pub struct ForProps<IF, I, T, EF, N, KF, K>
where
IF: Fn() -> I + 'static,
I: IntoIterator<Item = T>,
EF: Fn(T) -> N + 'static,
N: IntoView,
KF: Fn(&T) -> K + 'static,
K: Eq + Hash + 'static,
T: 'static,
{
/// Items over which the component should iterate.
#[builder(setter(doc = "Items over which the component should iterate."))]
pub each: IF,
/// A key function that will be applied to each item
#[builder(setter(doc = "A key function that will be applied to each item"))]
pub key: KF,
/// Should provide a single child function, which takes
#[builder(setter(
doc = "Should provide a single child function, which takes"
))]
pub view: EF,
}
/// Iterates over children and displays them, keyed by the `key` function given.
///
/// This is much more efficient than naively iterating over nodes with `.iter().map(|n| view! { cx, ... })...`,
/// as it avoids re-creating DOM nodes that are not being changed.
///
/// ```
/// # use leptos::*;
///
/// #[derive(Copy, Clone, Debug, PartialEq, Eq)]
/// struct Counter {
/// id: HydrationKey,
/// count: RwSignal<i32>
/// }
///
/// fn Counters(cx: Scope) -> Element {
/// let (counters, set_counters) = create_signal::<Vec<Counter>>(cx, vec![]);
///
/// view! {
/// cx,
/// <div>
/// <For
/// // a function that returns the items we're iterating over; a signal is fine
/// each=counters
/// // a unique key for each item
/// key=|counter| counter.id
/// view=move |counter: Counter| {
/// view! {
/// cx,
/// <button>"Value: " {move || counter.count.get()}</button>
/// }
/// }
/// />
/// </div>
/// }
/// }
/// ```
///
/// # Props
/// ## Required
/// - **cx**: [`Scope`]
/// - **each**: [`IF`]
/// - Items over which the component should iterate.
/// - **key**: KF
/// - A key function that will be applied to each item
/// - **view**: EF
/// - Should provide a single child function, which takes
#[allow(non_snake_case)]
pub fn For<IF, I, T, EF, N, KF, K>(
cx: Scope,
props: ForProps<IF, I, T, EF, N, KF, K>,
) -> View
where
IF: Fn() -> I + 'static,
I: IntoIterator<Item = T>,
EF: Fn(T) -> N + 'static,
N: IntoView,
KF: Fn(&T) -> K + 'static,
K: Eq + Hash + 'static,
T: 'static,
{
let each_fn = props.view;
Each::new(props.each, props.key, each_fn).into_view(cx)
}
}

View File

@ -132,7 +132,7 @@ impl ToTokens for Model {
#[doc = ""]
#docs
#component_fn_prop_docs
#[derive(::leptos::TypedBuilder)]
#[derive(::leptos::typed_builder::TypedBuilder)]
#[builder(doc)]
#vis struct #props_name #generics #where_clause {
#prop_builder_fields