mirror of
https://github.com/leptos-rs/leptos
synced 2024-11-10 14:54:16 +00:00
Use #[component]
macro for core components, deleting leptos_core
package
This commit is contained in:
parent
34b4917837
commit
7c25cd9200
14 changed files with 347 additions and 598 deletions
|
@ -3,7 +3,6 @@ members = [
|
|||
# core
|
||||
"leptos",
|
||||
"leptos_dom",
|
||||
"leptos_core",
|
||||
"leptos_macro",
|
||||
"leptos_reactive",
|
||||
"leptos_server",
|
||||
|
|
|
@ -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
63
leptos/src/for_loop.rs
Normal 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)
|
||||
}
|
|
@ -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
117
leptos/src/suspense.rs
Normal 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, ¤t_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
147
leptos/src/transition.rs
Normal 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, ¤t_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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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"]
|
|
@ -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;
|
|
@ -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, ¤t_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
|
||||
})
|
||||
}
|
|
@ -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, ¤t_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)
|
||||
})
|
||||
}
|
|
@ -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"
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue