mirror of
https://github.com/leptos-rs/leptos
synced 2024-11-10 06:44:17 +00:00
Merge branch 'main' into msgpack-encoding
This commit is contained in:
commit
93f68e022f
51 changed files with 1104 additions and 672 deletions
|
@ -4,4 +4,4 @@ version = "0.1.0"
|
|||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
leptos = "0.0"
|
||||
leptos = "0.0.18"
|
||||
|
|
|
@ -4,4 +4,4 @@ version = "0.1.0"
|
|||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
leptos = "0.0"
|
||||
leptos = "0.0.18"
|
||||
|
|
|
@ -4,4 +4,4 @@ version = "0.1.0"
|
|||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
leptos = "0.0"
|
||||
leptos = "0.0.18"
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use leptos::*;
|
||||
|
||||
fn main() {
|
||||
run_scope(|cx| {
|
||||
run_scope(create_runtime(), |cx| {
|
||||
// signal
|
||||
let (count, set_count) = create_signal(cx, 1);
|
||||
|
||||
|
|
|
@ -27,7 +27,7 @@ But that’s _exactly_ how reactive programming works.
|
|||
```rust
|
||||
use leptos::*;
|
||||
|
||||
run_scope(|cx| {
|
||||
run_scope(create_runtime(), |cx| {
|
||||
let (a, set_a) = create_signal(cx, 0);
|
||||
let (b, set_b) = create_signal(cx, 0);
|
||||
let c = move || a() + b();
|
||||
|
@ -46,7 +46,7 @@ Hopefully, this makes some intuitive sense. After all, `c` is a closure. Calling
|
|||
```rust
|
||||
use leptos::*;
|
||||
|
||||
run_scope(|cx| {
|
||||
run_scope(create_runtime(), |cx| {
|
||||
let (a, set_a) = create_signal(cx, 0);
|
||||
let (b, set_b) = create_signal(cx, 0);
|
||||
let c = move || a() + b();
|
||||
|
|
|
@ -7,6 +7,8 @@ fn main() {
|
|||
mount_to_body(|cx| view! { cx, <Counters/> })
|
||||
}
|
||||
|
||||
const MANY_COUNTERS: usize = 1000;
|
||||
|
||||
type CounterHolder = Vec<(usize, (ReadSignal<i32>, WriteSignal<i32>))>;
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
|
@ -28,12 +30,14 @@ pub fn Counters(cx: Scope) -> web_sys::Element {
|
|||
};
|
||||
|
||||
let add_many_counters = move |_| {
|
||||
let mut new_counters = vec![];
|
||||
for next_id in 0..1000 {
|
||||
let next_id = next_counter_id.get();
|
||||
let new_counters = (next_id..next_id + MANY_COUNTERS).map(|id| {
|
||||
let signal = create_signal(cx, 0);
|
||||
new_counters.push((next_id, signal));
|
||||
}
|
||||
set_counters.update(|counters| counters.extend(new_counters.iter()));
|
||||
(id, signal)
|
||||
});
|
||||
|
||||
set_counters.update(move |counters| counters.extend(new_counters));
|
||||
set_next_counter_id.update(|id| *id += MANY_COUNTERS);
|
||||
};
|
||||
|
||||
let clear_counters = move |_| {
|
||||
|
@ -46,7 +50,7 @@ pub fn Counters(cx: Scope) -> web_sys::Element {
|
|||
"Add Counter"
|
||||
</button>
|
||||
<button on:click=add_many_counters>
|
||||
"Add 1000 Counters"
|
||||
{format!("Add {MANY_COUNTERS} Counters")}
|
||||
</button>
|
||||
<button on:click=clear_counters>
|
||||
"Clear Counters"
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
use leptos::*;
|
||||
use leptos::{For, ForProps};
|
||||
|
||||
const MANY_COUNTERS: usize = 1000;
|
||||
|
||||
type CounterHolder = Vec<(usize, (ReadSignal<i32>, WriteSignal<i32>))>;
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
|
@ -22,12 +24,14 @@ pub fn Counters(cx: Scope) -> web_sys::Element {
|
|||
};
|
||||
|
||||
let add_many_counters = move |_| {
|
||||
let mut new_counters = vec![];
|
||||
for next_id in 0..1000 {
|
||||
let next_id = next_counter_id();
|
||||
let new_counters = (next_id..next_id + MANY_COUNTERS).map(|id| {
|
||||
let signal = create_signal(cx, 0);
|
||||
new_counters.push((next_id, signal));
|
||||
}
|
||||
set_counters.update(move |counters| counters.extend(new_counters.iter()));
|
||||
(id, signal)
|
||||
});
|
||||
|
||||
set_counters.update(move |counters| counters.extend(new_counters));
|
||||
set_next_counter_id.update(|id| *id += MANY_COUNTERS);
|
||||
};
|
||||
|
||||
let clear_counters = move |_| {
|
||||
|
@ -40,7 +44,7 @@ pub fn Counters(cx: Scope) -> web_sys::Element {
|
|||
"Add Counter"
|
||||
</button>
|
||||
<button on:click=add_many_counters>
|
||||
"Add 1000 Counters"
|
||||
{format!("Add {MANY_COUNTERS} Counters")}
|
||||
</button>
|
||||
<button on:click=clear_counters>
|
||||
"Clear Counters"
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use leptos::*;
|
||||
use web_sys::Event;
|
||||
use web_sys::MouseEvent;
|
||||
|
||||
// This highlights four different ways that child components can communicate
|
||||
// with their parent:
|
||||
|
@ -71,7 +71,7 @@ pub fn ButtonA(cx: Scope, setter: WriteSignal<bool>) -> Element {
|
|||
#[component]
|
||||
pub fn ButtonB<F>(cx: Scope, on_click: F) -> Element
|
||||
where
|
||||
F: Fn(Event) + 'static,
|
||||
F: Fn(MouseEvent) + 'static,
|
||||
{
|
||||
view! {
|
||||
cx,
|
||||
|
@ -82,11 +82,11 @@ where
|
|||
</button>
|
||||
}
|
||||
|
||||
// just a note: in an ordinary function ButtonB could take on_click: impl Fn(Event) + 'static
|
||||
// just a note: in an ordinary function ButtonB could take on_click: impl Fn(MouseEvent) + 'static
|
||||
// and save you from typing out the generic
|
||||
// the component macro actually expands to define a
|
||||
//
|
||||
// struct ButtonBProps<F> where F: Fn(Event) + 'static {
|
||||
// struct ButtonBProps<F> where F: Fn(MouseEvent) + 'static {
|
||||
// on_click: F
|
||||
// }
|
||||
//
|
||||
|
|
|
@ -136,10 +136,10 @@ pub fn TodoMVC(cx: Scope) -> Element {
|
|||
});
|
||||
|
||||
// Callback to add a todo on pressing the `Enter` key, if the field isn't empty
|
||||
let add_todo = move |ev: web_sys::Event| {
|
||||
let add_todo = move |ev: web_sys::KeyboardEvent| {
|
||||
let target = event_target::<HtmlInputElement>(&ev);
|
||||
ev.stop_propagation();
|
||||
let key_code = ev.unchecked_ref::<web_sys::KeyboardEvent>().key_code();
|
||||
let key_code = ev.key_code();
|
||||
if key_code == ENTER_KEY {
|
||||
let title = event_target_value(&ev);
|
||||
let title = title.trim();
|
||||
|
@ -311,7 +311,7 @@ pub fn Todo(cx: Scope, todo: Todo) -> Element {
|
|||
prop:value={move || todo.title.get()}
|
||||
on:focusout=move |ev| save(&event_target_value(&ev))
|
||||
on:keyup={move |ev| {
|
||||
let key_code = ev.unchecked_ref::<web_sys::KeyboardEvent>().key_code();
|
||||
let key_code = ev.key_code();
|
||||
if key_code == ENTER_KEY {
|
||||
save(&event_target_value(&ev));
|
||||
} else if key_code == ESCAPE_KEY {
|
||||
|
|
|
@ -2,6 +2,10 @@
|
|||
name = "leptos_actix"
|
||||
version = "0.0.1"
|
||||
edition = "2021"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/gbj/leptos"
|
||||
description = "Actix integrations for the Leptos web framework."
|
||||
|
||||
[dependencies]
|
||||
actix-web = "4"
|
||||
|
|
|
@ -50,8 +50,8 @@ pub fn handle_server_fns() -> Route {
|
|||
if let Some(server_fn) = server_fn_by_path(path.as_str()) {
|
||||
let body: &[u8] = &body;
|
||||
|
||||
// TODO this leaks a runtime once per invocation
|
||||
let (cx, disposer) = raw_scope_and_disposer();
|
||||
let runtime = create_runtime();
|
||||
let (cx, disposer) = raw_scope_and_disposer(runtime);
|
||||
|
||||
// provide HttpRequest as context in server scope
|
||||
provide_context(cx, req.clone());
|
||||
|
@ -60,6 +60,7 @@ pub fn handle_server_fns() -> Route {
|
|||
Ok(serialized) => {
|
||||
// clean up the scope, which we only needed to run the server fn
|
||||
disposer.dispose();
|
||||
runtime.dispose();
|
||||
|
||||
let mut res: HttpResponseBuilder;
|
||||
if accept_header.is_some() {
|
||||
|
|
|
@ -2,6 +2,10 @@
|
|||
name = "leptos_axum"
|
||||
version = "0.0.1"
|
||||
edition = "2021"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/gbj/leptos"
|
||||
description = "Axum integrations for the Leptos web framework."
|
||||
|
||||
[dependencies]
|
||||
axum = "0.5"
|
||||
|
|
|
@ -55,8 +55,8 @@ pub async fn handle_server_fns(
|
|||
let body: &[u8] = &body;
|
||||
|
||||
let res = if let Some(server_fn) = server_fn_by_path(path.as_str()) {
|
||||
// TODO this leaks a runtime once per invocation
|
||||
let (cx, disposer) = raw_scope_and_disposer();
|
||||
let runtime = create_runtime();
|
||||
let (cx, disposer) = raw_scope_and_disposer(runtime);
|
||||
|
||||
// provide request as context in server scope
|
||||
provide_context(cx, Arc::new(req));
|
||||
|
@ -65,6 +65,7 @@ pub async fn handle_server_fns(
|
|||
Ok(serialized) => {
|
||||
// clean up the scope, which we only needed to run the server fn
|
||||
disposer.dispose();
|
||||
runtime.dispose();
|
||||
|
||||
// if this is Accept: application/json then send a serialized JSON response
|
||||
let accept_header =
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "leptos"
|
||||
version = "0.0.17"
|
||||
version = "0.0.18"
|
||||
edition = "2021"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
|
@ -9,11 +9,11 @@ description = "Leptos is a full-stack, isomorphic Rust web framework leveraging
|
|||
readme = "../README.md"
|
||||
|
||||
[dependencies]
|
||||
leptos_core = { path = "../leptos_core", default-features = false, version = "0.0.17" }
|
||||
leptos_dom = { path = "../leptos_dom", default-features = false, version = "0.0.17" }
|
||||
leptos_macro = { path = "../leptos_macro", default-features = false, version = "0.0.17" }
|
||||
leptos_reactive = { path = "../leptos_reactive", default-features = false, version = "0.0.17" }
|
||||
leptos_server = { path = "../leptos_server", default-features = false, version = "0.0.17" }
|
||||
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" }
|
||||
|
||||
[features]
|
||||
default = ["csr", "serde"]
|
||||
|
|
|
@ -3,9 +3,9 @@
|
|||
fn simple_ssr_test() {
|
||||
use leptos_dom::*;
|
||||
use leptos_macro::view;
|
||||
use leptos_reactive::{create_scope, create_signal};
|
||||
use leptos_reactive::{create_runtime, create_scope, create_signal};
|
||||
|
||||
_ = create_scope(|cx| {
|
||||
_ = create_scope(create_runtime(), |cx| {
|
||||
let (value, set_value) = create_signal(cx, 0);
|
||||
let rendered = view! {
|
||||
cx,
|
||||
|
@ -30,7 +30,7 @@ fn ssr_test_with_components() {
|
|||
use leptos_core::Prop;
|
||||
use leptos_dom::*;
|
||||
use leptos_macro::*;
|
||||
use leptos_reactive::{create_scope, create_signal, Scope};
|
||||
use leptos_reactive::{create_runtime, create_scope, create_signal, Scope};
|
||||
|
||||
#[component]
|
||||
fn Counter(cx: Scope, initial_value: i32) -> Element {
|
||||
|
@ -45,7 +45,7 @@ fn ssr_test_with_components() {
|
|||
}
|
||||
}
|
||||
|
||||
_ = create_scope(|cx| {
|
||||
_ = create_scope(create_runtime(), |cx| {
|
||||
let rendered = view! {
|
||||
cx,
|
||||
<div class="counters">
|
||||
|
@ -66,9 +66,9 @@ fn ssr_test_with_components() {
|
|||
fn test_classes() {
|
||||
use leptos_dom::*;
|
||||
use leptos_macro::view;
|
||||
use leptos_reactive::{create_scope, create_signal};
|
||||
use leptos_reactive::{create_runtime, create_scope, create_signal};
|
||||
|
||||
_ = create_scope(|cx| {
|
||||
_ = create_scope(create_runtime(), |cx| {
|
||||
let (value, set_value) = create_signal(cx, 5);
|
||||
let rendered = view! {
|
||||
cx,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "leptos_core"
|
||||
version = "0.0.17"
|
||||
version = "0.0.18"
|
||||
edition = "2021"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
|
@ -8,9 +8,9 @@ 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.17" }
|
||||
leptos_macro = { path = "../leptos_macro", default-features = false, version = "0.0.17" }
|
||||
leptos_reactive = { path = "../leptos_reactive", default-features = false, version = "0.0.17" }
|
||||
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"
|
||||
|
||||
[dev-dependencies]
|
||||
|
|
|
@ -15,7 +15,7 @@ where
|
|||
G: Fn(Scope, &T) -> Element,
|
||||
I: Fn(&T) -> K,
|
||||
K: Eq + Hash,
|
||||
T: Eq + 'static,
|
||||
T: 'static,
|
||||
{
|
||||
/// Items over which the component should iterate.
|
||||
pub each: E,
|
||||
|
|
|
@ -168,7 +168,7 @@ mod tests {
|
|||
fn test_map_keyed() {
|
||||
// we can really only run this in SSR mode, so just ignore if we're in CSR or hydrate
|
||||
if !cfg!(any(feature = "csr", feature = "hydrate")) {
|
||||
create_scope(|cx| {
|
||||
create_scope(create_runtime(), |cx| {
|
||||
let (rows, set_rows) =
|
||||
create_signal::<Vec<(usize, ReadSignal<i32>, WriteSignal<i32>)>>(cx, vec![]);
|
||||
|
||||
|
|
|
@ -31,7 +31,7 @@ where
|
|||
/// # use leptos_core::*;
|
||||
/// # use leptos_macro::*;
|
||||
/// # use leptos_dom::*; use leptos::*;
|
||||
/// # run_scope(|cx| {
|
||||
/// # 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![]) }
|
||||
///
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "leptos_dom"
|
||||
version = "0.0.17"
|
||||
version = "0.0.18"
|
||||
edition = "2021"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
|
@ -12,7 +12,7 @@ cfg-if = "1"
|
|||
futures = "0.3"
|
||||
html-escape = "0.2"
|
||||
js-sys = "0.3"
|
||||
leptos_reactive = { path = "../leptos_reactive", default-features = false, version = "0.0.17" }
|
||||
leptos_reactive = { path = "../leptos_reactive", default-features = false, version = "0.0.18" }
|
||||
serde_json = "1"
|
||||
wasm-bindgen = "0.2"
|
||||
wasm-bindgen-futures = "0.4.31"
|
||||
|
@ -53,14 +53,29 @@ features = [
|
|||
"Text",
|
||||
"TreeWalker",
|
||||
"Window",
|
||||
|
||||
# Events we cast to in leptos_macro -- added here so we don't force users to import them
|
||||
"MouseEvent",
|
||||
"DragEvent",
|
||||
"FocusEvent",
|
||||
"KeyboardEvent",
|
||||
"ProgressEvent",
|
||||
"WheelEvent",
|
||||
"InputEvent",
|
||||
"SubmitEvent",
|
||||
"AnimationEvent",
|
||||
"PointerEvent",
|
||||
"TouchEvent",
|
||||
"TransitionEvent"
|
||||
]
|
||||
|
||||
[dev-dependencies]
|
||||
leptos = { path = "../leptos", default-features = false, version = "0.0" }
|
||||
leptos_macro = { path = "../leptos_macro", default-features = false, version = "0.0" }
|
||||
|
||||
|
||||
[features]
|
||||
csr = ["leptos_reactive/csr", "leptos_macro/csr"]
|
||||
hydrate = ["leptos_reactive/hydrate", "leptos_macro/hydrate"]
|
||||
ssr = ["leptos_reactive/ssr", "leptos_macro/ssr"]
|
||||
stable = ["leptos_reactive/stable", "leptos_macro/stable"]
|
||||
csr = ["leptos_reactive/csr", "leptos_macro/csr", "leptos/csr"]
|
||||
hydrate = ["leptos_reactive/hydrate", "leptos_macro/hydrate", "leptos/hydrate"]
|
||||
ssr = ["leptos_reactive/ssr", "leptos_macro/ssr", "leptos/ssr"]
|
||||
stable = ["leptos_reactive/stable", "leptos_macro/stable", "leptos/stable"]
|
||||
|
|
|
@ -140,3 +140,15 @@ macro_rules! is_dev {
|
|||
cfg!(debug_assertions)
|
||||
};
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn __leptos_renderer_error(expected: &'static str, location: &'static str) -> web_sys::Node {
|
||||
cfg_if! {
|
||||
if #[cfg(debug_assertions)] {
|
||||
panic!("Yikes! Something went wrong while Leptos was trying to traverse the DOM to set up the reactive system.\n\nThe renderer expected {expected:?} as {location} and couldn't get it.\n\nThis is almost certainly a bug in the framework, not your application. Please open an issue on GitHub and provide example code if possible.\n\nIn the meantime, these bugs are often related to <Component/>s or {{block}}s when they are siblings of each other. Try wrapping those in a <span> or <div> for now. Sorry for the pain!")
|
||||
} else {
|
||||
_ = expected;
|
||||
panic!("Renderer error. You can find a more detailed error message if you compile in debug mode.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -71,11 +71,11 @@ where
|
|||
F: Fn(Scope) -> T + 'static,
|
||||
T: Mountable,
|
||||
{
|
||||
use leptos_reactive::create_scope;
|
||||
use leptos_reactive::{create_runtime, create_scope};
|
||||
|
||||
// running "mount" intentionally leaks the memory,
|
||||
// as the "mount" has no parent that can clean it up
|
||||
let _ = create_scope(move |cx| {
|
||||
// this is not a leak
|
||||
// CSR and hydrate mode define a single, thread-local Runtime
|
||||
let _ = create_scope(create_runtime(), move |cx| {
|
||||
(f(cx)).mount(&parent);
|
||||
});
|
||||
}
|
||||
|
@ -100,9 +100,11 @@ where
|
|||
F: Fn(Scope) -> T + 'static,
|
||||
T: Mountable,
|
||||
{
|
||||
// running "hydrate" intentionally leaks the memory,
|
||||
// as the "hydrate" has no parent that can clean it up
|
||||
let _ = leptos_reactive::create_scope(move |cx| {
|
||||
use leptos_reactive::create_runtime;
|
||||
|
||||
// this is not a leak
|
||||
// CSR and hydrate mode define a single, thread-local Runtime
|
||||
let _ = leptos_reactive::create_scope(create_runtime(), move |cx| {
|
||||
cx.start_hydration(&parent);
|
||||
(f(cx));
|
||||
cx.end_hydration();
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use std::time::Duration;
|
||||
|
||||
use wasm_bindgen::convert::FromWasmAbi;
|
||||
use wasm_bindgen::{prelude::Closure, JsCast, JsValue, UnwrapThrowExt};
|
||||
|
||||
use crate::{debug_warn, event_delegation, is_server};
|
||||
|
@ -181,8 +182,12 @@ where
|
|||
/// Helper function to extract `event.target.value` from an event.
|
||||
///
|
||||
/// This is useful in the `on:input` or `on:change` listeners for an `<input>` element.
|
||||
pub fn event_target_value(event: &web_sys::Event) -> String {
|
||||
pub fn event_target_value<T>(event: &T) -> String
|
||||
where
|
||||
T: JsCast,
|
||||
{
|
||||
event
|
||||
.unchecked_ref::<web_sys::Event>()
|
||||
.target()
|
||||
.unwrap_throw()
|
||||
.unchecked_into::<web_sys::HtmlInputElement>()
|
||||
|
@ -250,30 +255,37 @@ pub fn set_interval(
|
|||
}
|
||||
|
||||
/// Adds an event listener to the target DOM element using implicit event delegation.
|
||||
pub fn add_event_listener(
|
||||
pub fn add_event_listener<E>(
|
||||
target: &web_sys::Element,
|
||||
event_name: &'static str,
|
||||
cb: impl FnMut(web_sys::Event) + 'static,
|
||||
) {
|
||||
let cb = Closure::wrap(Box::new(cb) as Box<dyn FnMut(web_sys::Event)>).into_js_value();
|
||||
cb: impl FnMut(E) + 'static,
|
||||
) where
|
||||
E: FromWasmAbi + 'static,
|
||||
{
|
||||
let cb = Closure::wrap(Box::new(cb) as Box<dyn FnMut(E)>).into_js_value();
|
||||
let key = event_delegation::event_delegation_key(event_name);
|
||||
_ = js_sys::Reflect::set(target, &JsValue::from_str(&key), &cb);
|
||||
event_delegation::add_event_listener(event_name);
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn add_event_listener_undelegated(
|
||||
pub fn add_event_listener_undelegated<E>(
|
||||
target: &web_sys::Element,
|
||||
event_name: &'static str,
|
||||
cb: impl FnMut(web_sys::Event) + 'static,
|
||||
) {
|
||||
let cb = Closure::wrap(Box::new(cb) as Box<dyn FnMut(web_sys::Event)>).into_js_value();
|
||||
cb: impl FnMut(E) + 'static,
|
||||
) where
|
||||
E: FromWasmAbi + 'static,
|
||||
{
|
||||
let cb = Closure::wrap(Box::new(cb) as Box<dyn FnMut(E)>).into_js_value();
|
||||
_ = target.add_event_listener_with_callback(event_name, cb.unchecked_ref());
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[inline(always)]
|
||||
pub fn ssr_event_listener(_cb: impl FnMut(web_sys::Event) + 'static) {
|
||||
pub fn ssr_event_listener<E>(_cb: impl FnMut(E) + 'static)
|
||||
where
|
||||
E: FromWasmAbi + 'static,
|
||||
{
|
||||
// this function exists only for type inference in templates for SSR
|
||||
}
|
||||
|
||||
|
|
|
@ -12,12 +12,30 @@ pub fn escape_attr(text: &str) -> Cow<'_, str> {
|
|||
}
|
||||
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "ssr")] {
|
||||
if #[cfg(not(any(feature = "csr", feature = "hydrate")))] {
|
||||
use leptos_reactive::*;
|
||||
|
||||
use crate::Element;
|
||||
use futures::{stream::FuturesUnordered, Stream, StreamExt};
|
||||
|
||||
/// Renders a component to a static HTML string.
|
||||
///
|
||||
/// ```
|
||||
/// # cfg_if::cfg_if! { if #[cfg(not(any(feature = "csr", feature = "hydrate")))] {
|
||||
/// # use leptos_reactive::*; use leptos_dom::*; use leptos_macro::view;
|
||||
/// let html = render_to_string(|cx| view! { cx,
|
||||
/// <p>"Hello, world!"</p>
|
||||
/// });
|
||||
/// assert_eq!(html, r#"<p data-hk="0-0">Hello, world!</p>"#);
|
||||
/// # }}
|
||||
/// ```
|
||||
pub fn render_to_string(view: impl FnOnce(Scope) -> Element + 'static) -> String {
|
||||
let runtime = create_runtime();
|
||||
let html = run_scope(runtime, move |cx| view(cx));
|
||||
runtime.dispose();
|
||||
html
|
||||
}
|
||||
|
||||
/// Renders a component to a stream of HTML strings.
|
||||
///
|
||||
/// This renders:
|
||||
|
@ -30,9 +48,12 @@ cfg_if! {
|
|||
/// it is waiting for a resource to resolve from the server, it doesn't run it initially.
|
||||
/// 3) HTML fragments to replace each `<Suspense/>` fallback with its actual data as the resources
|
||||
/// read under that `<Suspense/>` resolve.
|
||||
pub fn render_to_stream(view: impl Fn(Scope) -> Element + 'static) -> impl Stream<Item = String> {
|
||||
pub fn render_to_stream(view: impl FnOnce(Scope) -> Element + 'static) -> impl Stream<Item = String> {
|
||||
// create the runtime
|
||||
let runtime = create_runtime();
|
||||
|
||||
let ((shell, pending_resources, pending_fragments, serializers), _, disposer) =
|
||||
run_scope_undisposed({
|
||||
run_scope_undisposed(runtime, {
|
||||
move |cx| {
|
||||
// the actual app body/template code
|
||||
// this does NOT contain any of the data being loaded asynchronously in resources
|
||||
|
@ -55,6 +76,39 @@ cfg_if! {
|
|||
fragments.push(async move { (fragment_id, fut.await) })
|
||||
}
|
||||
|
||||
// resources and fragments
|
||||
let resources_and_fragments = futures::stream::select(
|
||||
// stream data for each Resource as it resolves
|
||||
serializers.map(|(id, json)| {
|
||||
let id = serde_json::to_string(&id).unwrap();
|
||||
format!(
|
||||
r#"<script>
|
||||
if(__LEPTOS_RESOURCE_RESOLVERS.get({id})) {{
|
||||
console.log("(create_resource) calling resolver");
|
||||
__LEPTOS_RESOURCE_RESOLVERS.get({id})({json:?})
|
||||
}} else {{
|
||||
console.log("(create_resource) saving data for resource creation");
|
||||
__LEPTOS_RESOLVED_RESOURCES.set({id}, {json:?});
|
||||
}}
|
||||
</script>"#,
|
||||
)
|
||||
}),
|
||||
// stream HTML for each <Suspense/> as it resolves
|
||||
fragments.map(|(fragment_id, html)| {
|
||||
format!(
|
||||
r#"
|
||||
<template id="{fragment_id}">{html}</template>
|
||||
<script>
|
||||
var frag = document.querySelector(`[data-fragment-id="{fragment_id}"]`);
|
||||
var tpl = document.getElementById("{fragment_id}");
|
||||
console.log("replace", frag, "with", tpl.content.cloneNode(true));
|
||||
frag.replaceWith(tpl.content.cloneNode(true));
|
||||
</script>
|
||||
"#
|
||||
)
|
||||
})
|
||||
);
|
||||
|
||||
// HTML for the view function and script to store resources
|
||||
futures::stream::once(async move {
|
||||
format!(
|
||||
|
@ -68,42 +122,11 @@ cfg_if! {
|
|||
"#
|
||||
)
|
||||
})
|
||||
|
||||
// TODO this is wrong: it should merge the next two streams, not chain them
|
||||
// you may well need to resolve some fragments before some of the resources are resolved
|
||||
|
||||
// stream data for each Resource as it resolves
|
||||
.chain(serializers.map(|(id, json)| {
|
||||
let id = serde_json::to_string(&id).unwrap();
|
||||
format!(
|
||||
r#"<script>
|
||||
if(__LEPTOS_RESOURCE_RESOLVERS.get({id})) {{
|
||||
console.log("(create_resource) calling resolver");
|
||||
__LEPTOS_RESOURCE_RESOLVERS.get({id})({json:?})
|
||||
}} else {{
|
||||
console.log("(create_resource) saving data for resource creation");
|
||||
__LEPTOS_RESOLVED_RESOURCES.set({id}, {json:?});
|
||||
}}
|
||||
</script>"#,
|
||||
)
|
||||
}))
|
||||
// stream HTML for each <Suspense/> as it resolves
|
||||
.chain(fragments.map(|(fragment_id, html)| {
|
||||
format!(
|
||||
r#"
|
||||
<template id="{fragment_id}">{html}</template>
|
||||
<script>
|
||||
var frag = document.querySelector(`[data-fragment-id="{fragment_id}"]`);
|
||||
var tpl = document.getElementById("{fragment_id}");
|
||||
console.log("replace", frag, "with", tpl.content.cloneNode(true));
|
||||
frag.replaceWith(tpl.content.cloneNode(true));
|
||||
</script>
|
||||
"#
|
||||
)
|
||||
}))
|
||||
// dispose of Scope
|
||||
.chain(futures::stream::once(async {
|
||||
.chain(resources_and_fragments)
|
||||
// dispose of Scope and Runtime
|
||||
.chain(futures::stream::once(async move {
|
||||
disposer.dispose();
|
||||
runtime.dispose();
|
||||
Default::default()
|
||||
}))
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "leptos_macro"
|
||||
version = "0.0.17"
|
||||
version = "0.0.18"
|
||||
edition = "2021"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
|
@ -18,9 +18,10 @@ quote = "1"
|
|||
syn = { version = "1", features = ["full", "parsing", "extra-traits"] }
|
||||
syn-rsx = "0.9"
|
||||
uuid = { version = "1", features = ["v4"] }
|
||||
leptos_dom = { path = "../leptos_dom", version = "0.0.17" }
|
||||
leptos_reactive = { path = "../leptos_reactive", version = "0.0.17" }
|
||||
leptos_server = { path = "../leptos_server", version = "0.0.17" }
|
||||
leptos_dom = { path = "../leptos_dom", version = "0.0.18" }
|
||||
leptos_reactive = { path = "../leptos_reactive", version = "0.0.18" }
|
||||
leptos_server = { path = "../leptos_server", version = "0.0.18" }
|
||||
lazy_static = "1.4"
|
||||
|
||||
[dev-dependencies]
|
||||
log = "0.4"
|
||||
|
|
|
@ -42,7 +42,7 @@ mod server;
|
|||
/// 1. Text content should be provided as a Rust string, i.e., double-quoted:
|
||||
/// ```rust
|
||||
/// # use leptos_reactive::*; use leptos_dom::*; use leptos_macro::view; use leptos_dom::wasm_bindgen::JsCast;
|
||||
/// # run_scope(|cx| {
|
||||
/// # run_scope(create_runtime(), |cx| {
|
||||
/// # if !cfg!(any(feature = "csr", feature = "hydrate")) {
|
||||
/// view! { cx, <p>"Here’s some text"</p> };
|
||||
/// # }
|
||||
|
@ -52,7 +52,7 @@ mod server;
|
|||
/// 2. Self-closing tags need an explicit `/` as in XML/XHTML
|
||||
/// ```rust,compile_fail
|
||||
/// # use leptos_reactive::*; use leptos_dom::*; use leptos_macro::view; use leptos_dom::wasm_bindgen::JsCast;
|
||||
/// # run_scope(|cx| {
|
||||
/// # run_scope(create_runtime(), |cx| {
|
||||
/// # if !cfg!(any(feature = "csr", feature = "hydrate")) {
|
||||
/// // ❌ not like this
|
||||
/// view! { cx, <input type="text" name="name"> }
|
||||
|
@ -62,7 +62,7 @@ mod server;
|
|||
/// ```
|
||||
/// ```rust
|
||||
/// # use leptos_reactive::*; use leptos_dom::*; use leptos_macro::view; use leptos_dom::wasm_bindgen::JsCast;
|
||||
/// # run_scope(|cx| {
|
||||
/// # run_scope(create_runtime(), |cx| {
|
||||
/// # if !cfg!(any(feature = "csr", feature = "hydrate")) {
|
||||
/// // ✅ add that slash
|
||||
/// view! { cx, <input type="text" name="name" /> }
|
||||
|
@ -76,7 +76,7 @@ mod server;
|
|||
/// # use leptos_reactive::*; use leptos_dom::*; use leptos_macro::*; use typed_builder::TypedBuilder; use leptos_dom::wasm_bindgen::JsCast; use leptos_dom as leptos; use leptos_dom::Marker;
|
||||
/// # #[derive(TypedBuilder)] struct CounterProps { initial_value: i32 }
|
||||
/// # fn Counter(cx: Scope, props: CounterProps) -> Element { view! { cx, <p></p>} }
|
||||
/// # run_scope(|cx| {
|
||||
/// # run_scope(create_runtime(), |cx| {
|
||||
/// # if !cfg!(any(feature = "csr", feature = "hydrate")) {
|
||||
/// view! { cx, <div><Counter initial_value=3 /></div> }
|
||||
/// # ;
|
||||
|
@ -94,7 +94,7 @@ mod server;
|
|||
///
|
||||
/// ```rust
|
||||
/// # use leptos_reactive::*; use leptos_dom::*; use leptos_macro::view; use leptos_dom::wasm_bindgen::JsCast; use leptos_dom as leptos; use leptos_dom::Marker;
|
||||
/// # run_scope(|cx| {
|
||||
/// # run_scope(create_runtime(), |cx| {
|
||||
/// # if !cfg!(any(feature = "csr", feature = "hydrate")) {
|
||||
/// let (count, set_count) = create_signal(cx, 0);
|
||||
///
|
||||
|
@ -111,14 +111,15 @@ mod server;
|
|||
/// # });
|
||||
/// ```
|
||||
///
|
||||
/// 5. Event handlers can be added with `on:` attributes.
|
||||
/// 5. Event handlers can be added with `on:` attributes. In most cases, the events are given the correct type
|
||||
/// based on the event name.
|
||||
/// ```rust
|
||||
/// # use leptos_reactive::*; use leptos_dom::*; use leptos_macro::view; use leptos_dom::wasm_bindgen::JsCast;
|
||||
/// # run_scope(|cx| {
|
||||
/// # run_scope(create_runtime(), |cx| {
|
||||
/// # if !cfg!(any(feature = "csr", feature = "hydrate")) {
|
||||
/// view! {
|
||||
/// cx,
|
||||
/// <button on:click=|ev: web_sys::Event| {
|
||||
/// <button on:click=|ev: web_sys::MouseEvent| {
|
||||
/// log::debug!("click event: {ev:#?}");
|
||||
/// }>
|
||||
/// "Click me"
|
||||
|
@ -134,7 +135,7 @@ mod server;
|
|||
/// and `None` deletes the property.
|
||||
/// ```rust
|
||||
/// # use leptos_reactive::*; use leptos_dom::*; use leptos_macro::view; use leptos_dom::wasm_bindgen::JsCast;
|
||||
/// # run_scope(|cx| {
|
||||
/// # run_scope(create_runtime(), |cx| {
|
||||
/// # if !cfg!(any(feature = "csr", feature = "hydrate")) {
|
||||
/// let (name, set_name) = create_signal(cx, "Alice".to_string());
|
||||
///
|
||||
|
@ -156,7 +157,7 @@ mod server;
|
|||
/// 7. Classes can be toggled with `class:` attributes, which take a `bool` (or a signal that returns a `bool`).
|
||||
/// ```rust
|
||||
/// # use leptos_reactive::*; use leptos_dom::*; use leptos_macro::view; use leptos_dom::wasm_bindgen::JsCast;
|
||||
/// # run_scope(|cx| {
|
||||
/// # run_scope(create_runtime(), |cx| {
|
||||
/// # if !cfg!(any(feature = "csr", feature = "hydrate")) {
|
||||
/// let (count, set_count) = create_signal(cx, 2);
|
||||
/// view! { cx, <div class:hidden-div={move || count() < 3}>"Now you see me, now you don’t."</div> }
|
||||
|
@ -168,7 +169,7 @@ mod server;
|
|||
/// Class names can include dashes, but cannot (at the moment) include a dash-separated segment of only numbers.
|
||||
/// ```rust,compile_fail
|
||||
/// # use leptos_reactive::*; use leptos_dom::*; use leptos_macro::view; use leptos_dom::wasm_bindgen::JsCast;
|
||||
/// # run_scope(|cx| {
|
||||
/// # run_scope(create_runtime(), |cx| {
|
||||
/// # if !cfg!(any(feature = "csr", feature = "hydrate")) {
|
||||
/// let (count, set_count) = create_signal(cx, 2);
|
||||
/// // `hidden-div-25` is invalid at the moment
|
||||
|
@ -181,7 +182,7 @@ mod server;
|
|||
/// 8. You can use the `_ref` attribute to store a reference to its DOM element in a variable to use later.
|
||||
/// ```rust
|
||||
/// # use leptos_reactive::*; use leptos_dom::*; use leptos_macro::view; use leptos_dom::wasm_bindgen::JsCast;
|
||||
/// # run_scope(|cx| {
|
||||
/// # run_scope(create_runtime(), |cx| {
|
||||
/// # if !cfg!(any(feature = "csr", feature = "hydrate")) {
|
||||
/// let (value, set_value) = create_signal(cx, 0);
|
||||
/// let my_input: Element;
|
||||
|
@ -203,9 +204,9 @@ mod server;
|
|||
///
|
||||
/// // create event handlers for our buttons
|
||||
/// // note that `value` and `set_value` are `Copy`, so it's super easy to move them into closures
|
||||
/// let clear = move |_ev: web_sys::Event| set_value(0);
|
||||
/// let decrement = move |_ev: web_sys::Event| set_value.update(|value| *value -= 1);
|
||||
/// let increment = move |_ev: web_sys::Event| set_value.update(|value| *value += 1);
|
||||
/// let clear = move |_ev: web_sys::MouseEvent| set_value(0);
|
||||
/// let decrement = move |_ev: web_sys::MouseEvent| set_value.update(|value| *value -= 1);
|
||||
/// let increment = move |_ev: web_sys::MouseEvent| set_value.update(|value| *value += 1);
|
||||
///
|
||||
/// // this JSX is compiled to an HTML template string for performance
|
||||
/// view! {
|
||||
|
@ -243,6 +244,106 @@ pub fn view(tokens: TokenStream) -> TokenStream {
|
|||
}
|
||||
}
|
||||
|
||||
/// Annotates a function so that it can be used with your template as a <Component/>
|
||||
///
|
||||
/// Here are some things you should know.
|
||||
/// 1. **The component function only runs once.** Your component function is not a “render” function
|
||||
/// that re-runs whenever changes happen in the state. It’s a “setup” function that runs once to
|
||||
/// create the user interface, and sets up a reactive system to update it. This means it’s okay
|
||||
/// to do relatively expensive work within the component function, as it will only happen once,
|
||||
/// not on every state change.
|
||||
///
|
||||
/// 2. The component name should be `CamelCase` instead of `snake_case`. This is how the renderer
|
||||
/// recognizes that a particular tag is a component, not an HTML element.
|
||||
///
|
||||
/// ```
|
||||
/// # use leptos::*;
|
||||
/// // ❌ not snake_case
|
||||
/// #[component]
|
||||
/// fn my_component(cx: Scope) -> Element { todo!() }
|
||||
///
|
||||
/// // ✅ CamelCase
|
||||
/// #[component]
|
||||
/// fn MyComponent(cx: Scope) -> Element { todo!() }
|
||||
/// ```
|
||||
///
|
||||
/// 3. The macro generates a type `ComponentProps` for every `Component` (so, `HomePage` generates `HomePageProps`,
|
||||
/// `Button` generates `ButtonProps`, etc.) When you’re importing the component, you also need to **explicitly import
|
||||
/// the prop type.**
|
||||
///
|
||||
/// ```
|
||||
/// # use leptos::*;
|
||||
///
|
||||
/// use component::{MyComponent, MyComponentProps};
|
||||
///
|
||||
/// mod component {
|
||||
/// use leptos::*;
|
||||
///
|
||||
/// #[component]
|
||||
/// pub fn MyComponent(cx: Scope) -> Element { todo!() }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// 4. You can pass generic arguments, but they should be defined in a `where` clause and not inline.
|
||||
///
|
||||
/// ```compile_error
|
||||
/// // ❌ This won't work.
|
||||
/// # use leptos::*;
|
||||
/// #[component]
|
||||
/// fn MyComponent<T: Fn() -> Element>(cx: Scope, render_prop: T) -> Element {
|
||||
/// todo!()
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// ```
|
||||
/// // ✅ Do this instead
|
||||
/// # use leptos::*;
|
||||
/// #[component]
|
||||
/// fn MyComponent<T>(cx: Scope, render_prop: T) -> Element where T: Fn() -> Element {
|
||||
/// todo!()
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// 5. You can access the children passed into the component with the `children` property, which takes
|
||||
/// an argument of the form `Box<dyn Fn() -> Vec<T>>` where `T` is the child type (usually `Element`).
|
||||
///
|
||||
/// ```
|
||||
/// # use leptos::*;
|
||||
/// #[component]
|
||||
/// fn ComponentWithChildren(cx: Scope, children: Box<dyn Fn() -> Vec<Element>>) -> Element {
|
||||
/// // wrap each child in a <strong> element
|
||||
/// let children = children()
|
||||
/// .into_iter()
|
||||
/// .map(|child| view! { cx, <strong>{child}</strong> })
|
||||
/// .collect::<Vec<_>>();
|
||||
///
|
||||
/// // wrap the whole set in a fancy wrapper
|
||||
/// view! { cx,
|
||||
/// <p class="fancy-wrapper">{children}</p>
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// #[component]
|
||||
/// fn WrapSomeChildren(cx: Scope) -> Element {
|
||||
/// view! { cx,
|
||||
/// <ComponentWithChildren>
|
||||
/// <span>"Ooh, look at us!"</span>
|
||||
/// <span>"We're being projected!"</span>
|
||||
/// </ComponentWithChildren>
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// ```
|
||||
/// # use leptos::*;
|
||||
/// #[component]
|
||||
/// fn MyComponent<T>(cx: Scope, render_prop: T) -> Element
|
||||
/// where
|
||||
/// T: Fn() -> Element,
|
||||
/// {
|
||||
/// todo!()
|
||||
/// }
|
||||
/// ```
|
||||
#[proc_macro_attribute]
|
||||
pub fn component(_args: proc_macro::TokenStream, s: TokenStream) -> TokenStream {
|
||||
match syn::parse::<component::InlinePropsBody>(s) {
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
use std::collections::HashMap;
|
||||
use proc_macro2::{Ident, Span, TokenStream};
|
||||
use quote::{quote, quote_spanned};
|
||||
use syn::{spanned::Spanned, ExprPath};
|
||||
|
@ -20,6 +21,79 @@ const NON_BUBBLING_EVENTS: [&str; 11] = [
|
|||
"loadend",
|
||||
];
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
// Specialized event type
|
||||
// https://github.com/yewstack/yew/blob/d422b533ea19a09cddf9b31ecd6cd5e5ce35ce3f/packages/yew/src/html/listener/events.rs
|
||||
static ref EVENTS: HashMap<&'static str, &'static str> = {
|
||||
let mut m = HashMap::new();
|
||||
m.insert("auxclick", "MouseEvent");
|
||||
m.insert("click", "MouseEvent");
|
||||
|
||||
m.insert("contextmenu", "MouseEvent");
|
||||
m.insert("dblclick", "MouseEvent");
|
||||
|
||||
m.insert("drag", "DragEvent");
|
||||
m.insert("dragend", "DragEvent");
|
||||
m.insert("dragenter", "DragEvent");
|
||||
m.insert("dragexit", "DragEvent");
|
||||
m.insert("dragleave", "DragEvent");
|
||||
m.insert("dragover", "DragEvent");
|
||||
m.insert("dragstart", "DragEvent");
|
||||
m.insert("drop", "DragEvent");
|
||||
|
||||
m.insert("blur", "FocusEvent");
|
||||
m.insert("focus", "FocusEvent");
|
||||
m.insert("focusin", "FocusEvent");
|
||||
m.insert("focusout", "FocusEvent");
|
||||
|
||||
m.insert("keydown", "KeyboardEvent");
|
||||
m.insert("keypress", "KeyboardEvent");
|
||||
m.insert("keyup", "KeyboardEvent");
|
||||
|
||||
m.insert("loadstart", "ProgressEvent");
|
||||
m.insert("progress", "ProgressEvent");
|
||||
m.insert("loadend", "ProgressEvent");
|
||||
|
||||
m.insert("mousedown", "MouseEvent");
|
||||
m.insert("mouseenter", "MouseEvent");
|
||||
m.insert("mouseleave", "MouseEvent");
|
||||
m.insert("mousemove", "MouseEvent");
|
||||
m.insert("mouseout", "MouseEvent");
|
||||
m.insert("mouseover", "MouseEvent");
|
||||
m.insert("mouseup", "MouseEvent");
|
||||
m.insert("wheel", "WheelEvent");
|
||||
|
||||
m.insert("input", "InputEvent");
|
||||
|
||||
m.insert("submit", "SubmitEvent");
|
||||
|
||||
m.insert("animationcancel", "AnimationEvent");
|
||||
m.insert("animationend", "AnimationEvent");
|
||||
m.insert("animationiteration", "AnimationEvent");
|
||||
m.insert("animationstart", "AnimationEvent");
|
||||
|
||||
m.insert("gotpointercapture", "PointerEvent");
|
||||
m.insert("lostpointercapture", "PointerEvent");
|
||||
m.insert("pointercancel", "PointerEvent");
|
||||
m.insert("pointerdown", "PointerEvent");
|
||||
m.insert("pointerenter", "PointerEvent");
|
||||
m.insert("pointerleave", "PointerEvent");
|
||||
m.insert("pointermove", "PointerEvent");
|
||||
m.insert("pointerout", "PointerEvent");
|
||||
m.insert("pointerover", "PointerEvent");
|
||||
m.insert("pointerup", "PointerEvent");
|
||||
|
||||
m.insert("touchcancel", "TouchEvent");
|
||||
m.insert("touchend", "TouchEvent");
|
||||
|
||||
m.insert("transitioncancel", "TransitionEvent");
|
||||
m.insert("transitionend", "TransitionEvent");
|
||||
m.insert("transitionrun", "TransitionEvent");
|
||||
m.insert("transitionstart", "TransitionEvent");
|
||||
m
|
||||
};
|
||||
}
|
||||
|
||||
pub(crate) fn render_view(cx: &Ident, nodes: &[Node], mode: Mode) -> TokenStream {
|
||||
let template_uid = Ident::new(
|
||||
&format!("TEMPLATE_{}", Uuid::new_v4().simple()),
|
||||
|
@ -309,14 +383,14 @@ fn element_to_tokens(
|
|||
quote_spanned! {
|
||||
span => let #this_el_ident = #debug_name;
|
||||
//log::debug!("next_sibling ({})", #debug_name);
|
||||
let #this_el_ident = #prev_sib.next_sibling().unwrap_throw();
|
||||
let #this_el_ident = #prev_sib.next_sibling().unwrap_or_else(|| ::leptos::__leptos_renderer_error(#debug_name, "nextSibling"));
|
||||
//log::debug!("=> got {}", #this_el_ident.node_name());
|
||||
}
|
||||
} else {
|
||||
quote_spanned! {
|
||||
span => let #this_el_ident = #debug_name;
|
||||
//log::debug!("first_child ({})", #debug_name);
|
||||
let #this_el_ident = #parent.first_child().unwrap_throw();
|
||||
let #this_el_ident = #parent.first_child().unwrap_or_else(|| ::leptos::__leptos_renderer_error(#debug_name, "firstChild"));
|
||||
//log::debug!("=> got {}", #this_el_ident.node_name());
|
||||
}
|
||||
};
|
||||
|
@ -508,22 +582,26 @@ fn attr_to_tokens(
|
|||
.expect("event listener attributes need a value")
|
||||
.as_ref();
|
||||
|
||||
let name = name.replacen("on:", "", 1);
|
||||
let event_type = EVENTS.get(&name.as_str()).copied().unwrap_or("Event");
|
||||
let event_type = event_type.parse::<TokenStream>().expect("couldn't parse event name");
|
||||
|
||||
if mode != Mode::Ssr {
|
||||
let name = name.replacen("on:", "", 1);
|
||||
if NON_BUBBLING_EVENTS.contains(&name.as_str()) {
|
||||
expressions.push(quote_spanned! {
|
||||
span => ::leptos::add_event_listener_undelegated(#el_id.unchecked_ref(), #name, #handler);
|
||||
span => ::leptos::add_event_listener_undelegated::<web_sys::#event_type>(#el_id.unchecked_ref(), #name, #handler);
|
||||
});
|
||||
} else {
|
||||
expressions.push(quote_spanned! {
|
||||
span => ::leptos::add_event_listener(#el_id.unchecked_ref(), #name, #handler);
|
||||
span => ::leptos::add_event_listener::<web_sys::#event_type>(#el_id.unchecked_ref(), #name, #handler);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
|
||||
// this is here to avoid warnings about unused signals
|
||||
// that are used in event listeners. I'm open to better solutions.
|
||||
expressions.push(quote_spanned! {
|
||||
span => let _ = ssr_event_listener(#handler);
|
||||
span => let _ = ssr_event_listener::<web_sys::#event_type>(#handler);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -738,13 +816,13 @@ fn block_to_tokens(
|
|||
let location = if let Some(sibling) = &prev_sib {
|
||||
quote_spanned! {
|
||||
span => //log::debug!("-> next sibling");
|
||||
let #name = #sibling.next_sibling().unwrap_throw();
|
||||
let #name = #sibling.next_sibling().unwrap_or_else(|| ::leptos::__leptos_renderer_error("{block}", "nextSibling"));
|
||||
//log::debug!("\tnext sibling = {}", #name.node_name());
|
||||
}
|
||||
} else {
|
||||
quote_spanned! {
|
||||
span => //log::debug!("\\|/ first child on {}", #parent.node_name());
|
||||
let #name = #parent.first_child().unwrap_throw();
|
||||
let #name = #parent.first_child().unwrap_or_else(|| ::leptos::__leptos_renderer_error("{block}", "firstChild"));
|
||||
//log::debug!("\tfirst child = {}", #name.node_name());
|
||||
}
|
||||
};
|
||||
|
@ -860,6 +938,8 @@ fn component_to_tokens(
|
|||
mode: Mode,
|
||||
is_first_child: bool,
|
||||
) -> PrevSibChange {
|
||||
let component_name = ident_from_tag_name(&node.name);
|
||||
let component_name = format!("<{component_name}/>");
|
||||
let create_component = create_component(cx, node, mode);
|
||||
let span = node.name.span();
|
||||
|
||||
|
@ -896,13 +976,13 @@ fn component_to_tokens(
|
|||
let starts_at = if let Some(prev_sib) = prev_sib {
|
||||
quote::quote! {{
|
||||
//log::debug!("starts_at = next_sibling");
|
||||
#prev_sib.next_sibling().unwrap_throw()
|
||||
#prev_sib.next_sibling().unwrap_or_else(|| ::leptos::__leptos_renderer_error(#component_name, "nextSibling"))
|
||||
//log::debug!("ok starts_at");
|
||||
}}
|
||||
} else {
|
||||
quote::quote! {{
|
||||
//log::debug!("starts_at first_child");
|
||||
#parent.first_child().unwrap_throw()
|
||||
#parent.first_child().unwrap_or_else(|| ::leptos::__leptos_renderer_error(#component_name, "firstChild"))
|
||||
//log::debug!("starts_at ok");
|
||||
}}
|
||||
};
|
||||
|
@ -1033,9 +1113,13 @@ fn create_component(cx: &Ident, node: &NodeElement, mode: Mode) -> TokenStream {
|
|||
Some(quote_spanned! {
|
||||
span => ::leptos::add_event_listener_undelegated(#component_name.unchecked_ref(), #event_name, #handler);
|
||||
})
|
||||
} else if let Some(event_type) = EVENTS.get(event_name).map(|&e| e.parse::<TokenStream>().unwrap_or_default()) {
|
||||
Some(quote_spanned! {
|
||||
span => ::leptos::add_event_listener::<#event_type>(#component_name.unchecked_ref(), #event_name, #handler);
|
||||
})
|
||||
} else {
|
||||
Some(quote_spanned! {
|
||||
span => ::leptos::add_event_listener(#component_name.unchecked_ref(), #event_name, #handler)
|
||||
span => ::leptos::add_event_listener::<web_sys::Event>(#component_name.unchecked_ref(), #event_name, #handler)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "leptos_reactive"
|
||||
version = "0.0.17"
|
||||
version = "0.0.18"
|
||||
edition = "2021"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
|
|
|
@ -3,7 +3,7 @@ use std::{
|
|||
collections::HashMap,
|
||||
};
|
||||
|
||||
use crate::Scope;
|
||||
use crate::{runtime::with_runtime, Scope};
|
||||
|
||||
/// Provides a context value of type `T` to the current reactive [Scope](crate::Scope)
|
||||
/// and all of its descendants. This can be consumed using [use_context](crate::use_context).
|
||||
|
@ -14,7 +14,7 @@ use crate::Scope;
|
|||
///
|
||||
/// ```
|
||||
/// # use leptos_reactive::*;
|
||||
/// # create_scope(|cx| {
|
||||
/// # create_scope(create_runtime(), |cx| {
|
||||
///
|
||||
/// // Note: this example doesn’t use Leptos’s DOM model or component structure,
|
||||
/// // so it ends up being a little silly.
|
||||
|
@ -48,9 +48,11 @@ where
|
|||
T: Clone + 'static,
|
||||
{
|
||||
let id = value.type_id();
|
||||
let mut contexts = cx.runtime.scope_contexts.borrow_mut();
|
||||
let context = contexts.entry(cx.id).unwrap().or_insert_with(HashMap::new);
|
||||
context.insert(id, Box::new(value) as Box<dyn Any>);
|
||||
with_runtime(cx.runtime, |runtime| {
|
||||
let mut contexts = runtime.scope_contexts.borrow_mut();
|
||||
let context = contexts.entry(cx.id).unwrap().or_insert_with(HashMap::new);
|
||||
context.insert(id, Box::new(value) as Box<dyn Any>);
|
||||
});
|
||||
}
|
||||
|
||||
/// Extracts a context value of type `T` from the reactive system by traversing
|
||||
|
@ -64,7 +66,7 @@ where
|
|||
///
|
||||
/// ```
|
||||
/// # use leptos_reactive::*;
|
||||
/// # create_scope(|cx| {
|
||||
/// # create_scope(create_runtime(), |cx| {
|
||||
///
|
||||
/// // Note: this example doesn’t use Leptos’s DOM model or component structure,
|
||||
/// // so it ends up being a little silly.
|
||||
|
@ -98,25 +100,26 @@ where
|
|||
T: Clone + 'static,
|
||||
{
|
||||
let id = TypeId::of::<T>();
|
||||
let local_value = {
|
||||
let contexts = cx.runtime.scope_contexts.borrow();
|
||||
let context = contexts.get(cx.id);
|
||||
context
|
||||
.and_then(|context| context.get(&id).and_then(|val| val.downcast_ref::<T>()))
|
||||
.cloned()
|
||||
};
|
||||
match local_value {
|
||||
Some(val) => Some(val),
|
||||
None => cx
|
||||
.runtime
|
||||
.scope_parents
|
||||
.borrow()
|
||||
.get(cx.id)
|
||||
.and_then(|parent| {
|
||||
use_context::<T>(Scope {
|
||||
runtime: cx.runtime,
|
||||
id: *parent,
|
||||
})
|
||||
}),
|
||||
}
|
||||
with_runtime(cx.runtime, |runtime| {
|
||||
let local_value = {
|
||||
let contexts = runtime.scope_contexts.borrow();
|
||||
let context = contexts.get(cx.id);
|
||||
context
|
||||
.and_then(|context| context.get(&id).and_then(|val| val.downcast_ref::<T>()))
|
||||
.cloned()
|
||||
};
|
||||
match local_value {
|
||||
Some(val) => Some(val),
|
||||
None => runtime
|
||||
.scope_parents
|
||||
.borrow()
|
||||
.get(cx.id)
|
||||
.and_then(|parent| {
|
||||
use_context::<T>(Scope {
|
||||
runtime: cx.runtime,
|
||||
id: *parent,
|
||||
})
|
||||
}),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
use crate::runtime::{with_runtime, RuntimeId};
|
||||
use crate::{debug_warn, Runtime, Scope, ScopeProperty};
|
||||
use cfg_if::cfg_if;
|
||||
use std::cell::RefCell;
|
||||
|
@ -22,7 +23,7 @@ use std::fmt::Debug;
|
|||
/// ```
|
||||
/// # use leptos_reactive::*;
|
||||
/// # use log::*;
|
||||
/// # create_scope(|cx| {
|
||||
/// # create_scope(create_runtime(), |cx| {
|
||||
/// let (a, set_a) = create_signal(cx, 0);
|
||||
/// let (b, set_b) = create_signal(cx, 0);
|
||||
///
|
||||
|
@ -66,7 +67,7 @@ where
|
|||
/// ```
|
||||
/// # use leptos_reactive::*;
|
||||
/// # use log::*;
|
||||
/// # create_scope(|cx| {
|
||||
/// # create_scope(create_runtime(), |cx| {
|
||||
/// let (a, set_a) = create_signal(cx, 0);
|
||||
/// let (b, set_b) = create_signal(cx, 0);
|
||||
///
|
||||
|
@ -118,7 +119,7 @@ where
|
|||
}
|
||||
|
||||
pub(crate) trait AnyEffect {
|
||||
fn run(&self, id: EffectId, runtime: &Runtime);
|
||||
fn run(&self, id: EffectId, runtime: RuntimeId);
|
||||
}
|
||||
|
||||
impl<T, F> AnyEffect for Effect<T, F>
|
||||
|
@ -126,35 +127,39 @@ where
|
|||
T: 'static,
|
||||
F: Fn(Option<T>) -> T,
|
||||
{
|
||||
fn run(&self, id: EffectId, runtime: &Runtime) {
|
||||
// clear previous dependencies
|
||||
id.cleanup(runtime);
|
||||
fn run(&self, id: EffectId, runtime: RuntimeId) {
|
||||
with_runtime(runtime, |runtime| {
|
||||
// clear previous dependencies
|
||||
id.cleanup(runtime);
|
||||
|
||||
// set this as the current observer
|
||||
let prev_observer = runtime.observer.take();
|
||||
runtime.observer.set(Some(id));
|
||||
// set this as the current observer
|
||||
let prev_observer = runtime.observer.take();
|
||||
runtime.observer.set(Some(id));
|
||||
|
||||
// run the effect
|
||||
let value = self.value.take();
|
||||
let new_value = (self.f)(value);
|
||||
*self.value.borrow_mut() = Some(new_value);
|
||||
// run the effect
|
||||
let value = self.value.take();
|
||||
let new_value = (self.f)(value);
|
||||
*self.value.borrow_mut() = Some(new_value);
|
||||
|
||||
// restore the previous observer
|
||||
runtime.observer.set(prev_observer);
|
||||
// restore the previous observer
|
||||
runtime.observer.set(prev_observer);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl EffectId {
|
||||
pub(crate) fn run<T>(&self, runtime: &Runtime) {
|
||||
let effect = {
|
||||
let effects = runtime.effects.borrow();
|
||||
effects.get(*self).cloned()
|
||||
};
|
||||
if let Some(effect) = effect {
|
||||
effect.run(*self, runtime);
|
||||
} else {
|
||||
debug_warn!("[Effect] Trying to run an Effect that has been disposed. This is probably either a logic error in a component that creates and disposes of scopes, or a Resource resolving after its scope has been dropped without having been cleaned up.")
|
||||
}
|
||||
pub(crate) fn run<T>(&self, runtime_id: RuntimeId) {
|
||||
with_runtime(runtime_id, |runtime| {
|
||||
let effect = {
|
||||
let effects = runtime.effects.borrow();
|
||||
effects.get(*self).cloned()
|
||||
};
|
||||
if let Some(effect) = effect {
|
||||
effect.run(*self, runtime_id);
|
||||
} else {
|
||||
debug_warn!("[Effect] Trying to run an Effect that has been disposed. This is probably either a logic error in a component that creates and disposes of scopes, or a Resource resolving after its scope has been dropped without having been cleaned up.")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn cleanup(&self, runtime: &Runtime) {
|
||||
|
|
|
@ -37,7 +37,7 @@
|
|||
//! // creates a new reactive Scope
|
||||
//! // this is omitted from most of the examples in the docs
|
||||
//! // you usually won't need to call it yourself
|
||||
//! create_scope(|cx| {
|
||||
//! create_scope(create_runtime(), |cx| {
|
||||
//! // a signal: returns a (getter, setter) pair
|
||||
//! let (count, set_count) = create_signal(cx, 0);
|
||||
//!
|
||||
|
@ -85,6 +85,7 @@ pub use effect::*;
|
|||
pub use memo::*;
|
||||
pub use resource::*;
|
||||
use runtime::*;
|
||||
pub use runtime::{create_runtime, RuntimeId};
|
||||
pub use scope::*;
|
||||
pub use selector::*;
|
||||
pub use serialization::*;
|
||||
|
|
|
@ -20,7 +20,7 @@ use std::fmt::Debug;
|
|||
/// ```
|
||||
/// # use leptos_reactive::*;
|
||||
/// # fn really_expensive_computation(value: i32) -> i32 { value };
|
||||
/// # create_scope(|cx| {
|
||||
/// # create_scope(create_runtime(), |cx| {
|
||||
/// let (value, set_value) = create_signal(cx, 0);
|
||||
///
|
||||
/// // 🆗 we could create a derived signal with a simple function
|
||||
|
@ -80,7 +80,7 @@ where
|
|||
/// ```
|
||||
/// # use leptos_reactive::*;
|
||||
/// # fn really_expensive_computation(value: i32) -> i32 { value };
|
||||
/// # create_scope(|cx| {
|
||||
/// # create_scope(create_runtime(), |cx| {
|
||||
/// let (value, set_value) = create_signal(cx, 0);
|
||||
///
|
||||
/// // 🆗 we could create a derived signal with a simple function
|
||||
|
@ -155,7 +155,7 @@ where
|
|||
/// the running effect to the memo.
|
||||
/// ```
|
||||
/// # use leptos_reactive::*;
|
||||
/// # create_scope(|cx| {
|
||||
/// # create_scope(create_runtime(), |cx| {
|
||||
/// let (count, set_count) = create_signal(cx, 0);
|
||||
/// let double_count = create_memo(cx, move |_| count() * 2);
|
||||
///
|
||||
|
@ -178,7 +178,7 @@ where
|
|||
/// the running effect to this memo.
|
||||
/// ```
|
||||
/// # use leptos_reactive::*;
|
||||
/// # create_scope(|cx| {
|
||||
/// # create_scope(create_runtime(), |cx| {
|
||||
/// let (name, set_name) = create_signal(cx, "Alice".to_string());
|
||||
/// let name_upper = create_memo(cx, move |_| name().to_uppercase());
|
||||
///
|
||||
|
|
|
@ -11,8 +11,10 @@ use std::{
|
|||
|
||||
use crate::{
|
||||
create_effect, create_isomorphic_effect, create_memo, create_signal, queue_microtask,
|
||||
runtime::Runtime, serialization::Serializable, spawn::spawn_local, use_context, Memo,
|
||||
ReadSignal, Scope, ScopeProperty, SuspenseContext, WriteSignal,
|
||||
runtime::{with_runtime, RuntimeId},
|
||||
serialization::Serializable,
|
||||
spawn::spawn_local,
|
||||
use_context, Memo, ReadSignal, Scope, ScopeProperty, SuspenseContext, WriteSignal,
|
||||
};
|
||||
|
||||
/// Creates [Resource](crate::Resource), which is a signal that reflects the
|
||||
|
@ -31,7 +33,7 @@ use crate::{
|
|||
///
|
||||
/// ```
|
||||
/// # use leptos_reactive::*;
|
||||
/// # create_scope(|cx| {
|
||||
/// # create_scope(create_runtime(), |cx| {
|
||||
/// // any old async function; maybe this is calling a REST API or something
|
||||
/// async fn fetch_cat_picture_urls(how_many: i32) -> Vec<String> {
|
||||
/// // pretend we're fetching cat pics
|
||||
|
@ -121,7 +123,9 @@ where
|
|||
suspense_contexts: Default::default(),
|
||||
});
|
||||
|
||||
let id = cx.runtime.create_serializable_resource(Rc::clone(&r));
|
||||
let id = with_runtime(cx.runtime, |runtime| {
|
||||
runtime.create_serializable_resource(Rc::clone(&r))
|
||||
});
|
||||
|
||||
create_isomorphic_effect(cx, {
|
||||
let r = Rc::clone(&r);
|
||||
|
@ -153,7 +157,7 @@ where
|
|||
///
|
||||
/// ```
|
||||
/// # use leptos_reactive::*;
|
||||
/// # create_scope(|cx| {
|
||||
/// # create_scope(create_runtime(), |cx| {
|
||||
/// #[derive(Debug, Clone)] // doesn't implement Serialize, Deserialize
|
||||
/// struct ComplicatedUnserializableStruct {
|
||||
/// // something here that can't be serialized
|
||||
|
@ -223,7 +227,9 @@ where
|
|||
suspense_contexts: Default::default(),
|
||||
});
|
||||
|
||||
let id = cx.runtime.create_unserializable_resource(Rc::clone(&r));
|
||||
let id = with_runtime(cx.runtime, |runtime| {
|
||||
runtime.create_unserializable_resource(Rc::clone(&r))
|
||||
});
|
||||
|
||||
create_effect(cx, {
|
||||
let r = Rc::clone(&r);
|
||||
|
@ -259,61 +265,63 @@ where
|
|||
{
|
||||
use wasm_bindgen::{JsCast, UnwrapThrowExt};
|
||||
|
||||
if let Some(ref mut context) = *cx.runtime.shared_context.borrow_mut() {
|
||||
if let Some(data) = context.resolved_resources.remove(&id) {
|
||||
// The server already sent us the serialized resource value, so
|
||||
// deserialize & set it now
|
||||
context.pending_resources.remove(&id); // no longer pending
|
||||
r.resolved.set(true);
|
||||
with_runtime(cx.runtime, |runtime| {
|
||||
if let Some(ref mut context) = *runtime.shared_context.borrow_mut() {
|
||||
if let Some(data) = context.resolved_resources.remove(&id) {
|
||||
// The server already sent us the serialized resource value, so
|
||||
// deserialize & set it now
|
||||
context.pending_resources.remove(&id); // no longer pending
|
||||
r.resolved.set(true);
|
||||
|
||||
let res = T::from_json(&data).expect_throw("could not deserialize Resource JSON");
|
||||
r.set_value.update(|n| *n = Some(res));
|
||||
r.set_loading.update(|n| *n = false);
|
||||
let res = T::from_json(&data).expect_throw("could not deserialize Resource JSON");
|
||||
r.set_value.update(|n| *n = Some(res));
|
||||
r.set_loading.update(|n| *n = false);
|
||||
|
||||
// for reactivity
|
||||
r.source.subscribe();
|
||||
} else if context.pending_resources.remove(&id) {
|
||||
// We're still waiting for the resource, add a "resolver" closure so
|
||||
// that it will be set as soon as the server sends the serialized
|
||||
// value
|
||||
r.set_loading.update(|n| *n = true);
|
||||
// for reactivity
|
||||
r.source.subscribe();
|
||||
} else if context.pending_resources.remove(&id) {
|
||||
// We're still waiting for the resource, add a "resolver" closure so
|
||||
// that it will be set as soon as the server sends the serialized
|
||||
// value
|
||||
r.set_loading.update(|n| *n = true);
|
||||
|
||||
let resolve = {
|
||||
let resolved = r.resolved.clone();
|
||||
let set_value = r.set_value;
|
||||
let set_loading = r.set_loading;
|
||||
move |res: String| {
|
||||
let res =
|
||||
T::from_json(&res).expect_throw("could not deserialize Resource JSON");
|
||||
resolved.set(true);
|
||||
set_value.update(|n| *n = Some(res));
|
||||
set_loading.update(|n| *n = false);
|
||||
}
|
||||
};
|
||||
let resolve =
|
||||
wasm_bindgen::closure::Closure::wrap(Box::new(resolve) as Box<dyn Fn(String)>);
|
||||
let resource_resolvers = js_sys::Reflect::get(
|
||||
&web_sys::window().unwrap(),
|
||||
&wasm_bindgen::JsValue::from_str("__LEPTOS_RESOURCE_RESOLVERS"),
|
||||
)
|
||||
.expect_throw("no __LEPTOS_RESOURCE_RESOLVERS found in the JS global scope");
|
||||
let id = serde_json::to_string(&id).expect_throw("could not serialize Resource ID");
|
||||
_ = js_sys::Reflect::set(
|
||||
&resource_resolvers,
|
||||
&wasm_bindgen::JsValue::from_str(&id),
|
||||
resolve.as_ref().unchecked_ref(),
|
||||
);
|
||||
let resolve = {
|
||||
let resolved = r.resolved.clone();
|
||||
let set_value = r.set_value;
|
||||
let set_loading = r.set_loading;
|
||||
move |res: String| {
|
||||
let res =
|
||||
T::from_json(&res).expect_throw("could not deserialize Resource JSON");
|
||||
resolved.set(true);
|
||||
set_value.update(|n| *n = Some(res));
|
||||
set_loading.update(|n| *n = false);
|
||||
}
|
||||
};
|
||||
let resolve =
|
||||
wasm_bindgen::closure::Closure::wrap(Box::new(resolve) as Box<dyn Fn(String)>);
|
||||
let resource_resolvers = js_sys::Reflect::get(
|
||||
&web_sys::window().unwrap(),
|
||||
&wasm_bindgen::JsValue::from_str("__LEPTOS_RESOURCE_RESOLVERS"),
|
||||
)
|
||||
.expect_throw("no __LEPTOS_RESOURCE_RESOLVERS found in the JS global scope");
|
||||
let id = serde_json::to_string(&id).expect_throw("could not serialize Resource ID");
|
||||
_ = js_sys::Reflect::set(
|
||||
&resource_resolvers,
|
||||
&wasm_bindgen::JsValue::from_str(&id),
|
||||
resolve.as_ref().unchecked_ref(),
|
||||
);
|
||||
|
||||
// for reactivity
|
||||
r.source.subscribe()
|
||||
// for reactivity
|
||||
r.source.subscribe()
|
||||
} else {
|
||||
// Server didn't mark the resource as pending, so load it on the
|
||||
// client
|
||||
r.load(false);
|
||||
}
|
||||
} else {
|
||||
// Server didn't mark the resource as pending, so load it on the
|
||||
// client
|
||||
r.load(false);
|
||||
r.load(false)
|
||||
}
|
||||
} else {
|
||||
r.load(false)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
impl<S, T> Resource<S, T>
|
||||
|
@ -331,8 +339,9 @@ where
|
|||
where
|
||||
T: Clone,
|
||||
{
|
||||
self.runtime
|
||||
.resource(self.id, |resource: &ResourceState<S, T>| resource.read())
|
||||
with_runtime(self.runtime, |runtime| {
|
||||
runtime.resource(self.id, |resource: &ResourceState<S, T>| resource.read())
|
||||
})
|
||||
}
|
||||
|
||||
/// Applies a function to the current value of the resource, and subscribes
|
||||
|
@ -343,20 +352,23 @@ where
|
|||
/// If you want to get the value by cloning it, you can use
|
||||
/// [Resource::read].
|
||||
pub fn with<U>(&self, f: impl FnOnce(&T) -> U) -> Option<U> {
|
||||
self.runtime
|
||||
.resource(self.id, |resource: &ResourceState<S, T>| resource.with(f))
|
||||
with_runtime(self.runtime, |runtime| {
|
||||
runtime.resource(self.id, |resource: &ResourceState<S, T>| resource.with(f))
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns a signal that indicates whether the resource is currently loading.
|
||||
pub fn loading(&self) -> ReadSignal<bool> {
|
||||
self.runtime
|
||||
.resource(self.id, |resource: &ResourceState<S, T>| resource.loading)
|
||||
with_runtime(self.runtime, |runtime| {
|
||||
runtime.resource(self.id, |resource: &ResourceState<S, T>| resource.loading)
|
||||
})
|
||||
}
|
||||
|
||||
/// Re-runs the async function with the current source data.
|
||||
pub fn refetch(&self) {
|
||||
self.runtime
|
||||
.resource(self.id, |resource: &ResourceState<S, T>| resource.refetch())
|
||||
with_runtime(self.runtime, |runtime| {
|
||||
runtime.resource(self.id, |resource: &ResourceState<S, T>| resource.refetch())
|
||||
});
|
||||
}
|
||||
|
||||
/// Returns a [std::future::Future] that will resolve when the resource has loaded,
|
||||
|
@ -366,11 +378,12 @@ where
|
|||
where
|
||||
T: Serializable,
|
||||
{
|
||||
self.runtime
|
||||
.resource(self.id, |resource: &ResourceState<S, T>| {
|
||||
with_runtime(self.runtime, |runtime| {
|
||||
runtime.resource(self.id, |resource: &ResourceState<S, T>| {
|
||||
resource.to_serialization_resolver(self.id)
|
||||
})
|
||||
.await
|
||||
})
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -390,7 +403,7 @@ where
|
|||
///
|
||||
/// ```
|
||||
/// # use leptos_reactive::*;
|
||||
/// # create_scope(|cx| {
|
||||
/// # create_scope(create_runtime(), |cx| {
|
||||
/// // any old async function; maybe this is calling a REST API or something
|
||||
/// async fn fetch_cat_picture_urls(how_many: i32) -> Vec<String> {
|
||||
/// // pretend we're fetching cat pics
|
||||
|
@ -423,7 +436,7 @@ where
|
|||
S: Debug + 'static,
|
||||
T: Debug + 'static,
|
||||
{
|
||||
runtime: &'static Runtime,
|
||||
runtime: RuntimeId,
|
||||
pub(crate) id: ResourceId,
|
||||
pub(crate) source_ty: PhantomData<S>,
|
||||
pub(crate) out_ty: PhantomData<T>,
|
||||
|
|
|
@ -3,6 +3,7 @@ use crate::{
|
|||
EffectId, Memo, ReadSignal, ResourceId, ResourceState, RwSignal, Scope, ScopeDisposer, ScopeId,
|
||||
ScopeProperty, SignalId, WriteSignal,
|
||||
};
|
||||
use cfg_if::cfg_if;
|
||||
use futures::stream::FuturesUnordered;
|
||||
use slotmap::{SecondaryMap, SlotMap, SparseSecondaryMap};
|
||||
use std::{
|
||||
|
@ -18,6 +19,185 @@ use std::{
|
|||
|
||||
pub(crate) type PinnedFuture<T> = Pin<Box<dyn Future<Output = T>>>;
|
||||
|
||||
cfg_if! {
|
||||
if #[cfg(any(feature = "csr", feature = "hydrate"))] {
|
||||
thread_local! {
|
||||
pub(crate) static RUNTIME: Runtime = Runtime::new();
|
||||
}
|
||||
} else {
|
||||
thread_local! {
|
||||
pub(crate) static RUNTIMES: RefCell<SlotMap<RuntimeId, Runtime>> = Default::default();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the selected runtime from the thread-local set of runtimes. On the server,
|
||||
/// this will return the correct runtime. In the browser, there should only be one runtime.
|
||||
pub(crate) fn with_runtime<T>(id: RuntimeId, f: impl FnOnce(&Runtime) -> T) -> T {
|
||||
// in the browser, everything should exist under one runtime
|
||||
cfg_if! {
|
||||
if #[cfg(any(feature = "csr", feature = "hydrate"))] {
|
||||
_ = id;
|
||||
RUNTIME.with(|runtime| f(runtime))
|
||||
} else {
|
||||
RUNTIMES.with(|runtimes| {
|
||||
let runtimes = runtimes.borrow();
|
||||
let runtime = runtimes
|
||||
.get(id)
|
||||
.expect("Tried to access a Runtime that no longer exists.");
|
||||
f(runtime)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[must_use = "Runtime will leak memory if Runtime::dispose() is never called."]
|
||||
/// Creates a new reactive [Runtime]. This should almost always be handled by the framework.
|
||||
pub fn create_runtime() -> RuntimeId {
|
||||
cfg_if! {
|
||||
if #[cfg(any(feature = "csr", feature = "hydrate"))] {
|
||||
Default::default()
|
||||
} else {
|
||||
RUNTIMES.with(|runtimes| runtimes.borrow_mut().insert(Runtime::new()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
slotmap::new_key_type! {
|
||||
/// Unique ID assigned to a [Runtime](crate::Runtime).
|
||||
pub struct RuntimeId;
|
||||
}
|
||||
|
||||
impl RuntimeId {
|
||||
/// Removes the runtime, disposing all its child [Scope](crate::Scope)s.
|
||||
pub fn dispose(self) {
|
||||
cfg_if! {
|
||||
if #[cfg(not(any(feature = "csr", feature = "hydrate")))] {
|
||||
let runtime = RUNTIMES.with(move |runtimes| runtimes.borrow_mut().remove(self));
|
||||
if let Some(runtime) = runtime {
|
||||
for (scope_id, _) in runtime.scopes.borrow().iter() {
|
||||
let scope = Scope {
|
||||
runtime: self,
|
||||
id: scope_id,
|
||||
};
|
||||
scope.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn raw_scope_and_disposer(self) -> (Scope, ScopeDisposer) {
|
||||
with_runtime(self, |runtime| {
|
||||
let id = { runtime.scopes.borrow_mut().insert(Default::default()) };
|
||||
let scope = Scope { runtime: self, id };
|
||||
let disposer = ScopeDisposer(Box::new(move || scope.dispose()));
|
||||
(scope, disposer)
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn run_scope_undisposed<T>(
|
||||
self,
|
||||
f: impl FnOnce(Scope) -> T,
|
||||
parent: Option<Scope>,
|
||||
) -> (T, ScopeId, ScopeDisposer) {
|
||||
with_runtime(self, |runtime| {
|
||||
let id = { runtime.scopes.borrow_mut().insert(Default::default()) };
|
||||
if let Some(parent) = parent {
|
||||
runtime.scope_parents.borrow_mut().insert(id, parent.id);
|
||||
}
|
||||
let scope = Scope { runtime: self, id };
|
||||
let val = f(scope);
|
||||
let disposer = ScopeDisposer(Box::new(move || scope.dispose()));
|
||||
(val, id, disposer)
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn run_scope<T>(self, f: impl FnOnce(Scope) -> T, parent: Option<Scope>) -> T {
|
||||
let (ret, _, disposer) = self.run_scope_undisposed(f, parent);
|
||||
disposer.dispose();
|
||||
ret
|
||||
}
|
||||
|
||||
pub(crate) fn create_signal<T>(self, value: T) -> (ReadSignal<T>, WriteSignal<T>)
|
||||
where
|
||||
T: Any + 'static,
|
||||
{
|
||||
let id = with_runtime(self, |runtime| {
|
||||
runtime
|
||||
.signals
|
||||
.borrow_mut()
|
||||
.insert(Rc::new(RefCell::new(value)))
|
||||
});
|
||||
(
|
||||
ReadSignal {
|
||||
runtime: self,
|
||||
id,
|
||||
ty: PhantomData,
|
||||
},
|
||||
WriteSignal {
|
||||
runtime: self,
|
||||
id,
|
||||
ty: PhantomData,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn create_rw_signal<T>(self, value: T) -> RwSignal<T>
|
||||
where
|
||||
T: Any + 'static,
|
||||
{
|
||||
let id = with_runtime(self, |runtime| {
|
||||
runtime
|
||||
.signals
|
||||
.borrow_mut()
|
||||
.insert(Rc::new(RefCell::new(value)))
|
||||
});
|
||||
RwSignal {
|
||||
runtime: self,
|
||||
id,
|
||||
ty: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn create_effect<T>(self, f: impl Fn(Option<T>) -> T + 'static) -> EffectId
|
||||
where
|
||||
T: Any + 'static,
|
||||
{
|
||||
with_runtime(self, |runtime| {
|
||||
let effect = Effect {
|
||||
f,
|
||||
value: RefCell::new(None),
|
||||
};
|
||||
let id = { runtime.effects.borrow_mut().insert(Rc::new(effect)) };
|
||||
id.run::<T>(self);
|
||||
id
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn create_memo<T>(self, f: impl Fn(Option<&T>) -> T + 'static) -> Memo<T>
|
||||
where
|
||||
T: PartialEq + Any + 'static,
|
||||
{
|
||||
let (read, write) = self.create_signal(None);
|
||||
|
||||
self.create_effect(move |_| {
|
||||
let (new, changed) = read.with_no_subscription(|p| {
|
||||
let new = f(p.as_ref());
|
||||
let changed = Some(&new) != p.as_ref();
|
||||
(new, changed)
|
||||
});
|
||||
|
||||
if changed {
|
||||
write.update(|n| *n = Some(new));
|
||||
}
|
||||
});
|
||||
|
||||
Memo(read)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub(crate) struct Runtime {
|
||||
pub shared_context: RefCell<Option<SharedContext>>,
|
||||
|
@ -57,105 +237,6 @@ impl Runtime {
|
|||
Self::default()
|
||||
}
|
||||
|
||||
pub fn raw_scope_and_disposer(&'static self) -> (Scope, ScopeDisposer) {
|
||||
let id = { self.scopes.borrow_mut().insert(Default::default()) };
|
||||
let scope = Scope { runtime: self, id };
|
||||
let disposer = ScopeDisposer(Box::new(move || scope.dispose()));
|
||||
(scope, disposer)
|
||||
}
|
||||
|
||||
pub fn run_scope_undisposed<T>(
|
||||
&'static self,
|
||||
f: impl FnOnce(Scope) -> T,
|
||||
parent: Option<Scope>,
|
||||
) -> (T, ScopeId, ScopeDisposer) {
|
||||
let id = { self.scopes.borrow_mut().insert(Default::default()) };
|
||||
if let Some(parent) = parent {
|
||||
self.scope_parents.borrow_mut().insert(id, parent.id);
|
||||
}
|
||||
let scope = Scope { runtime: self, id };
|
||||
let val = f(scope);
|
||||
let disposer = ScopeDisposer(Box::new(move || scope.dispose()));
|
||||
(val, id, disposer)
|
||||
}
|
||||
|
||||
pub fn run_scope<T>(&'static self, f: impl FnOnce(Scope) -> T, parent: Option<Scope>) -> T {
|
||||
let (ret, _, disposer) = self.run_scope_undisposed(f, parent);
|
||||
disposer.dispose();
|
||||
ret
|
||||
}
|
||||
|
||||
pub(crate) fn create_signal<T>(&'static self, value: T) -> (ReadSignal<T>, WriteSignal<T>)
|
||||
where
|
||||
T: Any + 'static,
|
||||
{
|
||||
let id = self
|
||||
.signals
|
||||
.borrow_mut()
|
||||
.insert(Rc::new(RefCell::new(value)));
|
||||
(
|
||||
ReadSignal {
|
||||
runtime: self,
|
||||
id,
|
||||
ty: PhantomData,
|
||||
},
|
||||
WriteSignal {
|
||||
runtime: self,
|
||||
id,
|
||||
ty: PhantomData,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn create_rw_signal<T>(&'static self, value: T) -> RwSignal<T>
|
||||
where
|
||||
T: Any + 'static,
|
||||
{
|
||||
let id = self
|
||||
.signals
|
||||
.borrow_mut()
|
||||
.insert(Rc::new(RefCell::new(value)));
|
||||
RwSignal {
|
||||
runtime: self,
|
||||
id,
|
||||
ty: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn create_effect<T>(&'static self, f: impl Fn(Option<T>) -> T + 'static) -> EffectId
|
||||
where
|
||||
T: Any + 'static,
|
||||
{
|
||||
let effect = Effect {
|
||||
f,
|
||||
value: RefCell::new(None),
|
||||
};
|
||||
let id = { self.effects.borrow_mut().insert(Rc::new(effect)) };
|
||||
id.run::<T>(self);
|
||||
id
|
||||
}
|
||||
|
||||
pub(crate) fn create_memo<T>(&'static self, f: impl Fn(Option<&T>) -> T + 'static) -> Memo<T>
|
||||
where
|
||||
T: PartialEq + Any + 'static,
|
||||
{
|
||||
let (read, write) = self.create_signal(None);
|
||||
|
||||
self.create_effect(move |_| {
|
||||
let (new, changed) = read.with_no_subscription(|p| {
|
||||
let new = f(p.as_ref());
|
||||
let changed = Some(&new) != p.as_ref();
|
||||
(new, changed)
|
||||
});
|
||||
|
||||
if changed {
|
||||
write.update(|n| *n = Some(new));
|
||||
}
|
||||
});
|
||||
|
||||
Memo(read)
|
||||
}
|
||||
|
||||
pub(crate) fn create_unserializable_resource<S, T>(
|
||||
&self,
|
||||
state: Rc<ResourceState<S, T>>,
|
||||
|
|
|
@ -1,52 +1,58 @@
|
|||
use cfg_if::cfg_if;
|
||||
|
||||
use crate::{hydration::SharedContext, EffectId, ResourceId, Runtime, SignalId};
|
||||
use crate::runtime::{with_runtime, RuntimeId};
|
||||
use crate::{hydration::SharedContext, EffectId, ResourceId, SignalId};
|
||||
use crate::{PinnedFuture, SuspenseContext};
|
||||
use futures::stream::FuturesUnordered;
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::Debug;
|
||||
use std::{future::Future, pin::Pin};
|
||||
|
||||
#[doc(hidden)]
|
||||
#[must_use = "Scope will leak memory if the disposer function is never called"]
|
||||
/// Creates a new reactive system and root reactive scope and runs the function within it.
|
||||
///
|
||||
/// This should usually only be used once, at the root of an application, because its reactive
|
||||
/// values will not have access to values created under another `create_scope`.
|
||||
pub fn create_scope(f: impl FnOnce(Scope) + 'static) -> ScopeDisposer {
|
||||
// TODO leak
|
||||
let runtime = Box::leak(Box::new(Runtime::new()));
|
||||
///
|
||||
/// You usually don't need to call this manually.
|
||||
pub fn create_scope(runtime: RuntimeId, f: impl FnOnce(Scope) + 'static) -> ScopeDisposer {
|
||||
runtime.run_scope_undisposed(f, None).2
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[must_use = "Scope will leak memory if the disposer function is never called"]
|
||||
/// Creates a new reactive system and root reactive scope, and returns them.
|
||||
///
|
||||
/// This should usually only be used once, at the root of an application, because its reactive
|
||||
/// values will not have access to values created under another `create_scope`.
|
||||
pub fn raw_scope_and_disposer() -> (Scope, ScopeDisposer) {
|
||||
// TODO leak
|
||||
let runtime = Box::leak(Box::new(Runtime::new()));
|
||||
///
|
||||
/// You usually don't need to call this manually.
|
||||
pub fn raw_scope_and_disposer(runtime: RuntimeId) -> (Scope, ScopeDisposer) {
|
||||
runtime.raw_scope_and_disposer()
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
/// Creates a temporary scope, runs the given function, disposes of the scope,
|
||||
/// and returns the value returned from the function. This is very useful for short-lived
|
||||
/// applications like SSR, where actual reactivity is not required beyond the end
|
||||
/// of the synchronous operation.
|
||||
pub fn run_scope<T>(f: impl FnOnce(Scope) -> T + 'static) -> T {
|
||||
// TODO this leaks the runtime — should unsafely upgrade the lifetime, and then drop it after the scope is run
|
||||
let runtime = Box::leak(Box::new(Runtime::new()));
|
||||
///
|
||||
/// You usually don't need to call this manually.
|
||||
pub fn run_scope<T>(runtime: RuntimeId, f: impl FnOnce(Scope) -> T + 'static) -> T {
|
||||
runtime.run_scope(f, None)
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[must_use = "Scope will leak memory if the disposer function is never called"]
|
||||
/// Creates a temporary scope and run the given function without disposing of the scope.
|
||||
/// If you do not dispose of the scope on your own, memory will leak.
|
||||
///
|
||||
/// You usually don't need to call this manually.
|
||||
pub fn run_scope_undisposed<T>(
|
||||
runtime: RuntimeId,
|
||||
f: impl FnOnce(Scope) -> T + 'static,
|
||||
) -> (T, ScopeId, ScopeDisposer) {
|
||||
// TODO this leaks the runtime — should unsafely upgrade the lifetime, and then drop it after the scope is run
|
||||
let runtime = Box::leak(Box::new(Runtime::new()));
|
||||
runtime.run_scope_undisposed(f, None)
|
||||
}
|
||||
|
||||
|
@ -67,7 +73,7 @@ pub fn run_scope_undisposed<T>(
|
|||
/// is [Copy] and `'static` this does not add much overhead or lifetime complexity.
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Scope {
|
||||
pub(crate) runtime: &'static Runtime,
|
||||
pub(crate) runtime: RuntimeId,
|
||||
pub(crate) id: ScopeId,
|
||||
}
|
||||
|
||||
|
@ -77,7 +83,7 @@ impl Scope {
|
|||
self.id
|
||||
}
|
||||
|
||||
/// Creates a child scope and runs the given function within it.
|
||||
/// Creates a child scope and runs the given function within it, returning a handle to dispose of it.
|
||||
///
|
||||
/// The child scope has its own lifetime and disposer, but will be disposed when the parent is
|
||||
/// disposed, if it has not been already.
|
||||
|
@ -86,23 +92,39 @@ impl Scope {
|
|||
/// dispose of them when they are no longer needed (e.g., a list item has been destroyed or the user
|
||||
/// has navigated away from the route.)
|
||||
pub fn child_scope(self, f: impl FnOnce(Scope)) -> ScopeDisposer {
|
||||
let (_, child_id, disposer) = self.runtime.run_scope_undisposed(f, Some(self));
|
||||
let mut children = self.runtime.scope_children.borrow_mut();
|
||||
children
|
||||
.entry(self.id)
|
||||
.expect("trying to add a child to a Scope that has already been disposed")
|
||||
.or_default()
|
||||
.push(child_id);
|
||||
let (_, disposer) = self.run_child_scope(f);
|
||||
disposer
|
||||
}
|
||||
|
||||
/// Creates a child scope and runs the given function within it, returning the function's return
|
||||
/// type and a handle to dispose of it.
|
||||
///
|
||||
/// The child scope has its own lifetime and disposer, but will be disposed when the parent is
|
||||
/// disposed, if it has not been already.
|
||||
///
|
||||
/// This is useful for applications like a list or a router, which may want to create child scopes and
|
||||
/// dispose of them when they are no longer needed (e.g., a list item has been destroyed or the user
|
||||
/// has navigated away from the route.)
|
||||
pub fn run_child_scope<T>(self, f: impl FnOnce(Scope) -> T) -> (T, ScopeDisposer) {
|
||||
let (res, child_id, disposer) = self.runtime.run_scope_undisposed(f, Some(self));
|
||||
with_runtime(self.runtime, |runtime| {
|
||||
let mut children = runtime.scope_children.borrow_mut();
|
||||
children
|
||||
.entry(self.id)
|
||||
.expect("trying to add a child to a Scope that has already been disposed")
|
||||
.or_default()
|
||||
.push(child_id);
|
||||
});
|
||||
(res, disposer)
|
||||
}
|
||||
|
||||
/// Suspends reactive tracking while running the given function.
|
||||
///
|
||||
/// This can be used to isolate parts of the reactive graph from one another.
|
||||
///
|
||||
/// ```
|
||||
/// # use leptos_reactive::*;
|
||||
/// # run_scope(|cx| {
|
||||
/// # run_scope(create_runtime(), |cx| {
|
||||
/// let (a, set_a) = create_signal(cx, 0);
|
||||
/// let (b, set_b) = create_signal(cx, 0);
|
||||
/// let c = create_memo(cx, move |_| {
|
||||
|
@ -122,10 +144,12 @@ impl Scope {
|
|||
/// # });
|
||||
/// ```
|
||||
pub fn untrack<T>(&self, f: impl FnOnce() -> T) -> T {
|
||||
let prev_observer = self.runtime.observer.take();
|
||||
let untracked_result = f();
|
||||
self.runtime.observer.set(prev_observer);
|
||||
untracked_result
|
||||
with_runtime(self.runtime, |runtime| {
|
||||
let prev_observer = runtime.observer.take();
|
||||
let untracked_result = f();
|
||||
runtime.observer.set(prev_observer);
|
||||
untracked_result
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -133,71 +157,75 @@ impl Scope {
|
|||
|
||||
impl Scope {
|
||||
pub(crate) fn dispose(self) {
|
||||
// dispose of all child scopes
|
||||
let children = {
|
||||
let mut children = self.runtime.scope_children.borrow_mut();
|
||||
children.remove(self.id)
|
||||
};
|
||||
with_runtime(self.runtime, |runtime| {
|
||||
// dispose of all child scopes
|
||||
let children = {
|
||||
let mut children = runtime.scope_children.borrow_mut();
|
||||
children.remove(self.id)
|
||||
};
|
||||
|
||||
if let Some(children) = children {
|
||||
for id in children {
|
||||
Scope {
|
||||
runtime: self.runtime,
|
||||
id,
|
||||
if let Some(children) = children {
|
||||
for id in children {
|
||||
Scope {
|
||||
runtime: self.runtime,
|
||||
id,
|
||||
}
|
||||
.dispose();
|
||||
}
|
||||
.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
// run cleanups
|
||||
if let Some(cleanups) = self.runtime.scope_cleanups.borrow_mut().remove(self.id) {
|
||||
for cleanup in cleanups {
|
||||
cleanup();
|
||||
// run cleanups
|
||||
if let Some(cleanups) = runtime.scope_cleanups.borrow_mut().remove(self.id) {
|
||||
for cleanup in cleanups {
|
||||
cleanup();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// remove everything we own and run cleanups
|
||||
let owned = {
|
||||
let owned = self.runtime.scopes.borrow_mut().remove(self.id);
|
||||
owned.map(|owned| owned.take())
|
||||
};
|
||||
if let Some(owned) = owned {
|
||||
for property in owned {
|
||||
match property {
|
||||
ScopeProperty::Signal(id) => {
|
||||
// remove the signal
|
||||
self.runtime.signals.borrow_mut().remove(id);
|
||||
let subs = self.runtime.signal_subscribers.borrow_mut().remove(id);
|
||||
// remove everything we own and run cleanups
|
||||
let owned = {
|
||||
let owned = runtime.scopes.borrow_mut().remove(self.id);
|
||||
owned.map(|owned| owned.take())
|
||||
};
|
||||
if let Some(owned) = owned {
|
||||
for property in owned {
|
||||
match property {
|
||||
ScopeProperty::Signal(id) => {
|
||||
// remove the signal
|
||||
runtime.signals.borrow_mut().remove(id);
|
||||
let subs = runtime.signal_subscribers.borrow_mut().remove(id);
|
||||
|
||||
// each of the subs needs to remove the signal from its dependencies
|
||||
// so that it doesn't try to read the (now disposed) signal
|
||||
if let Some(subs) = subs {
|
||||
let source_map = self.runtime.effect_sources.borrow();
|
||||
for effect in subs.borrow().iter() {
|
||||
if let Some(effect_sources) = source_map.get(*effect) {
|
||||
effect_sources.borrow_mut().remove(&id);
|
||||
// each of the subs needs to remove the signal from its dependencies
|
||||
// so that it doesn't try to read the (now disposed) signal
|
||||
if let Some(subs) = subs {
|
||||
let source_map = runtime.effect_sources.borrow();
|
||||
for effect in subs.borrow().iter() {
|
||||
if let Some(effect_sources) = source_map.get(*effect) {
|
||||
effect_sources.borrow_mut().remove(&id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ScopeProperty::Effect(id) => {
|
||||
self.runtime.effects.borrow_mut().remove(id);
|
||||
self.runtime.effect_sources.borrow_mut().remove(id);
|
||||
}
|
||||
ScopeProperty::Resource(id) => {
|
||||
self.runtime.resources.borrow_mut().remove(id);
|
||||
ScopeProperty::Effect(id) => {
|
||||
runtime.effects.borrow_mut().remove(id);
|
||||
runtime.effect_sources.borrow_mut().remove(id);
|
||||
}
|
||||
ScopeProperty::Resource(id) => {
|
||||
runtime.resources.borrow_mut().remove(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn with_scope_property(&self, f: impl FnOnce(&mut Vec<ScopeProperty>)) {
|
||||
let scopes = self.runtime.scopes.borrow();
|
||||
let scope = scopes
|
||||
.get(self.id)
|
||||
.expect("tried to add property to a scope that has been disposed");
|
||||
f(&mut scope.borrow_mut());
|
||||
with_runtime(self.runtime, |runtime| {
|
||||
let scopes = runtime.scopes.borrow();
|
||||
let scope = scopes
|
||||
.get(self.id)
|
||||
.expect("tried to add property to a scope that has been disposed");
|
||||
f(&mut scope.borrow_mut());
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -206,12 +234,14 @@ impl Scope {
|
|||
/// It runs after child scopes have been disposed, but before signals, effects, and resources
|
||||
/// are invalidated.
|
||||
pub fn on_cleanup(cx: Scope, cleanup_fn: impl FnOnce() + 'static) {
|
||||
let mut cleanups = cx.runtime.scope_cleanups.borrow_mut();
|
||||
let cleanups = cleanups
|
||||
.entry(cx.id)
|
||||
.expect("trying to clean up a Scope that has already been disposed")
|
||||
.or_insert_with(Default::default);
|
||||
cleanups.push(Box::new(cleanup_fn));
|
||||
with_runtime(cx.runtime, |runtime| {
|
||||
let mut cleanups = runtime.scope_cleanups.borrow_mut();
|
||||
let cleanups = cleanups
|
||||
.entry(cx.id)
|
||||
.expect("trying to clean up a Scope that has already been disposed")
|
||||
.or_insert_with(Default::default);
|
||||
cleanups.push(Box::new(cleanup_fn));
|
||||
})
|
||||
}
|
||||
|
||||
slotmap::new_key_type! {
|
||||
|
@ -253,17 +283,23 @@ impl Scope {
|
|||
if #[cfg(any(feature = "hydrate", doc))] {
|
||||
/// `hydrate` only: Whether we're currently hydrating the page.
|
||||
pub fn is_hydrating(&self) -> bool {
|
||||
self.runtime.shared_context.borrow().is_some()
|
||||
with_runtime(self.runtime, |runtime| {
|
||||
runtime.shared_context.borrow().is_some()
|
||||
})
|
||||
}
|
||||
|
||||
/// `hydrate` only: Begins the hydration process.
|
||||
pub fn start_hydration(&self, element: &web_sys::Element) {
|
||||
self.runtime.start_hydration(element);
|
||||
with_runtime(self.runtime, |runtime| {
|
||||
runtime.start_hydration(element);
|
||||
})
|
||||
}
|
||||
|
||||
/// `hydrate` only: Ends the hydration process.
|
||||
pub fn end_hydration(&self) {
|
||||
self.runtime.end_hydration();
|
||||
with_runtime(self.runtime, |runtime| {
|
||||
runtime.end_hydration();
|
||||
})
|
||||
}
|
||||
|
||||
/// `hydrate` only: Gets the next element in the hydration queue, either from the
|
||||
|
@ -283,27 +319,29 @@ impl Scope {
|
|||
t
|
||||
};
|
||||
|
||||
if let Some(ref mut shared_context) = &mut *self.runtime.shared_context.borrow_mut() {
|
||||
if shared_context.context.is_some() {
|
||||
let key = shared_context.next_hydration_key();
|
||||
let node = shared_context.registry.remove(&key);
|
||||
with_runtime(self.runtime, |runtime| {
|
||||
if let Some(ref mut shared_context) = &mut *runtime.shared_context.borrow_mut() {
|
||||
if shared_context.context.is_some() {
|
||||
let key = shared_context.next_hydration_key();
|
||||
let node = shared_context.registry.remove(&key);
|
||||
|
||||
//log::debug!("(hy) searching for {key}");
|
||||
//log::debug!("(hy) searching for {key}");
|
||||
|
||||
if let Some(node) = node {
|
||||
//log::debug!("(hy) found {key}");
|
||||
shared_context.completed.push(node.clone());
|
||||
node
|
||||
if let Some(node) = node {
|
||||
//log::debug!("(hy) found {key}");
|
||||
shared_context.completed.push(node.clone());
|
||||
node
|
||||
} else {
|
||||
//log::debug!("(hy) did NOT find {key}");
|
||||
cloned_template(template)
|
||||
}
|
||||
} else {
|
||||
//log::debug!("(hy) did NOT find {key}");
|
||||
cloned_template(template)
|
||||
}
|
||||
} else {
|
||||
cloned_template(template)
|
||||
}
|
||||
} else {
|
||||
cloned_template(template)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -317,102 +355,108 @@ impl Scope {
|
|||
let mut current = Vec::new();
|
||||
let mut start = start.clone();
|
||||
|
||||
if self
|
||||
.runtime
|
||||
.shared_context
|
||||
.borrow()
|
||||
.as_ref()
|
||||
.map(|sc| sc.context.as_ref())
|
||||
.is_some()
|
||||
{
|
||||
while let Some(curr) = end {
|
||||
start = curr.clone();
|
||||
if curr.node_type() == 8 {
|
||||
// COMMENT
|
||||
let v = curr.node_value();
|
||||
if v == Some("#".to_string()) {
|
||||
count += 1;
|
||||
} else if v == Some("/".to_string()) {
|
||||
count -= 1;
|
||||
if count == 0 {
|
||||
current.push(curr.clone());
|
||||
return (curr, current);
|
||||
with_runtime(self.runtime, |runtime| {
|
||||
if runtime
|
||||
.shared_context
|
||||
.borrow()
|
||||
.as_ref()
|
||||
.map(|sc| sc.context.as_ref())
|
||||
.is_some()
|
||||
{
|
||||
while let Some(curr) = end {
|
||||
start = curr.clone();
|
||||
if curr.node_type() == 8 {
|
||||
// COMMENT
|
||||
let v = curr.node_value();
|
||||
if v == Some("#".to_string()) {
|
||||
count += 1;
|
||||
} else if v == Some("/".to_string()) {
|
||||
count -= 1;
|
||||
if count == 0 {
|
||||
current.push(curr.clone());
|
||||
return (curr, current);
|
||||
}
|
||||
}
|
||||
}
|
||||
current.push(curr.clone());
|
||||
end = curr.next_sibling();
|
||||
}
|
||||
current.push(curr.clone());
|
||||
end = curr.next_sibling();
|
||||
}
|
||||
}
|
||||
|
||||
(start, current)
|
||||
(start, current)
|
||||
})
|
||||
}
|
||||
|
||||
/// On either the server side or the browser side, generates the next key in the hydration process.
|
||||
pub fn next_hydration_key(&self) -> String {
|
||||
let mut sc = self.runtime.shared_context.borrow_mut();
|
||||
if let Some(ref mut sc) = *sc {
|
||||
sc.next_hydration_key()
|
||||
} else {
|
||||
let mut new_sc = SharedContext::default();
|
||||
let id = new_sc.next_hydration_key();
|
||||
*sc = Some(new_sc);
|
||||
id
|
||||
}
|
||||
with_runtime(self.runtime, |runtime| {
|
||||
let mut sc = runtime.shared_context.borrow_mut();
|
||||
if let Some(ref mut sc) = *sc {
|
||||
sc.next_hydration_key()
|
||||
} else {
|
||||
let mut new_sc = SharedContext::default();
|
||||
let id = new_sc.next_hydration_key();
|
||||
*sc = Some(new_sc);
|
||||
id
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Runs the given function with the next hydration context.
|
||||
pub fn with_next_context<T>(&self, f: impl FnOnce() -> T) -> T {
|
||||
if self
|
||||
.runtime
|
||||
.shared_context
|
||||
.borrow()
|
||||
.as_ref()
|
||||
.and_then(|sc| sc.context.as_ref())
|
||||
.is_some()
|
||||
{
|
||||
let c = {
|
||||
if let Some(ref mut sc) = *self.runtime.shared_context.borrow_mut() {
|
||||
if let Some(ref mut context) = sc.context {
|
||||
let next = context.next_hydration_context();
|
||||
Some(std::mem::replace(context, next))
|
||||
with_runtime(self.runtime, |runtime| {
|
||||
if runtime
|
||||
.shared_context
|
||||
.borrow()
|
||||
.as_ref()
|
||||
.and_then(|sc| sc.context.as_ref())
|
||||
.is_some()
|
||||
{
|
||||
let c = {
|
||||
if let Some(ref mut sc) = *runtime.shared_context.borrow_mut() {
|
||||
if let Some(ref mut context) = sc.context {
|
||||
let next = context.next_hydration_context();
|
||||
Some(std::mem::replace(context, next))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let res = self.untrack(f);
|
||||
|
||||
if let Some(ref mut sc) = *runtime.shared_context.borrow_mut() {
|
||||
sc.context = c;
|
||||
}
|
||||
};
|
||||
|
||||
let res = self.untrack(f);
|
||||
|
||||
if let Some(ref mut sc) = *self.runtime.shared_context.borrow_mut() {
|
||||
sc.context = c;
|
||||
res
|
||||
} else {
|
||||
self.untrack(f)
|
||||
}
|
||||
res
|
||||
} else {
|
||||
self.untrack(f)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns IDs for all [Resource](crate::Resource)s found on any scope.
|
||||
pub fn all_resources(&self) -> Vec<ResourceId> {
|
||||
self.runtime.all_resources()
|
||||
with_runtime(self.runtime, |runtime| runtime.all_resources())
|
||||
}
|
||||
|
||||
/// The current key for an HTML fragment created by server-rendering a `<Suspense/>` component.
|
||||
pub fn current_fragment_key(&self) -> String {
|
||||
self.runtime
|
||||
.shared_context
|
||||
.borrow()
|
||||
.as_ref()
|
||||
.map(|context| context.current_fragment_key())
|
||||
.unwrap_or_else(|| String::from("0f"))
|
||||
with_runtime(self.runtime, |runtime| {
|
||||
runtime
|
||||
.shared_context
|
||||
.borrow()
|
||||
.as_ref()
|
||||
.map(|context| context.current_fragment_key())
|
||||
.unwrap_or_else(|| String::from("0f"))
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns IDs for all [Resource](crate::Resource)s found on any scope.
|
||||
pub fn serialization_resolvers(&self) -> FuturesUnordered<PinnedFuture<(ResourceId, String)>> {
|
||||
self.runtime.serialization_resolvers()
|
||||
with_runtime(self.runtime, |runtime| runtime.serialization_resolvers())
|
||||
}
|
||||
|
||||
/// Registers the given [SuspenseContext](crate::SuspenseContext) with the current scope,
|
||||
|
@ -426,33 +470,37 @@ impl Scope {
|
|||
use crate::create_isomorphic_effect;
|
||||
use futures::StreamExt;
|
||||
|
||||
if let Some(ref mut shared_context) = *self.runtime.shared_context.borrow_mut() {
|
||||
let (tx, mut rx) = futures::channel::mpsc::unbounded();
|
||||
with_runtime(self.runtime, |runtime| {
|
||||
if let Some(ref mut shared_context) = *runtime.shared_context.borrow_mut() {
|
||||
let (tx, mut rx) = futures::channel::mpsc::unbounded();
|
||||
|
||||
create_isomorphic_effect(*self, move |_| {
|
||||
let pending = context.pending_resources.try_with(|n| *n).unwrap_or(0);
|
||||
if pending == 0 {
|
||||
_ = tx.unbounded_send(());
|
||||
}
|
||||
});
|
||||
create_isomorphic_effect(*self, move |_| {
|
||||
let pending = context.pending_resources.try_with(|n| *n).unwrap_or(0);
|
||||
if pending == 0 {
|
||||
_ = tx.unbounded_send(());
|
||||
}
|
||||
});
|
||||
|
||||
shared_context.pending_fragments.insert(
|
||||
key.to_string(),
|
||||
Box::pin(async move {
|
||||
rx.next().await;
|
||||
resolver()
|
||||
}),
|
||||
);
|
||||
}
|
||||
shared_context.pending_fragments.insert(
|
||||
key.to_string(),
|
||||
Box::pin(async move {
|
||||
rx.next().await;
|
||||
resolver()
|
||||
}),
|
||||
);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// The set of all HTML fragments current pending, by their keys (see [Self::current_fragment_key]).
|
||||
pub fn pending_fragments(&self) -> HashMap<String, Pin<Box<dyn Future<Output = String>>>> {
|
||||
if let Some(ref mut shared_context) = *self.runtime.shared_context.borrow_mut() {
|
||||
std::mem::take(&mut shared_context.pending_fragments)
|
||||
} else {
|
||||
HashMap::new()
|
||||
}
|
||||
with_runtime(self.runtime, |runtime| {
|
||||
if let Some(ref mut shared_context) = *runtime.shared_context.borrow_mut() {
|
||||
std::mem::take(&mut shared_context.pending_fragments)
|
||||
} else {
|
||||
HashMap::new()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -11,10 +11,10 @@ use crate::{create_isomorphic_effect, create_signal, ReadSignal, Scope, WriteSig
|
|||
/// because it reduces them from `O(n)` to `O(1)`.
|
||||
///
|
||||
/// ```
|
||||
/// # use leptos_reactive::{create_isomorphic_effect, create_scope, create_selector, create_signal};
|
||||
/// # use leptos_reactive::*;
|
||||
/// # use std::rc::Rc;
|
||||
/// # use std::cell::RefCell;
|
||||
/// # create_scope(|cx| {
|
||||
/// # create_scope(create_runtime(), |cx| {
|
||||
/// let (a, set_a) = create_signal(cx, 0);
|
||||
/// let is_selected = create_selector(cx, a);
|
||||
/// let total_notifications = Rc::new(RefCell::new(0));
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use crate::{
|
||||
debug_warn, spawn_local, Runtime, Scope, ScopeProperty, UntrackedGettableSignal,
|
||||
UntrackedSettableSignal,
|
||||
debug_warn,
|
||||
runtime::{with_runtime, RuntimeId},
|
||||
spawn_local, Runtime, Scope, ScopeProperty, UntrackedGettableSignal, UntrackedSettableSignal,
|
||||
};
|
||||
use futures::Stream;
|
||||
use std::{fmt::Debug, marker::PhantomData};
|
||||
|
@ -18,7 +19,7 @@ use thiserror::Error;
|
|||
///
|
||||
/// ```
|
||||
/// # use leptos_reactive::*;
|
||||
/// # create_scope(|cx| {
|
||||
/// # create_scope(create_runtime(), |cx| {
|
||||
/// let (count, set_count) = create_signal(cx, 0);
|
||||
///
|
||||
/// // ✅ calling the getter clones and returns the value
|
||||
|
@ -85,7 +86,7 @@ pub fn create_signal_from_stream<T>(
|
|||
///
|
||||
/// ```
|
||||
/// # use leptos_reactive::*;
|
||||
/// # create_scope(|cx| {
|
||||
/// # create_scope(create_runtime(), |cx| {
|
||||
/// let (count, set_count) = create_signal(cx, 0);
|
||||
///
|
||||
/// // ✅ calling the getter clones and returns the value
|
||||
|
@ -116,7 +117,7 @@ pub struct ReadSignal<T>
|
|||
where
|
||||
T: 'static,
|
||||
{
|
||||
pub(crate) runtime: &'static Runtime,
|
||||
pub(crate) runtime: RuntimeId,
|
||||
pub(crate) id: SignalId,
|
||||
pub(crate) ty: PhantomData<T>,
|
||||
}
|
||||
|
@ -142,7 +143,7 @@ where
|
|||
/// the running effect to this signal.
|
||||
/// ```
|
||||
/// # use leptos_reactive::*;
|
||||
/// # create_scope(|cx| {
|
||||
/// # create_scope(create_runtime(), |cx| {
|
||||
/// let (name, set_name) = create_signal(cx, "Alice".to_string());
|
||||
///
|
||||
/// // ❌ unnecessarily clones the string
|
||||
|
@ -166,7 +167,7 @@ where
|
|||
|
||||
#[cfg(feature = "hydrate")]
|
||||
pub(crate) fn subscribe(&self) {
|
||||
self.id.subscribe(self.runtime);
|
||||
with_runtime(self.runtime, |runtime| self.id.subscribe(runtime))
|
||||
}
|
||||
|
||||
/// Clones and returns the current value of the signal, and subscribes
|
||||
|
@ -176,7 +177,7 @@ where
|
|||
/// (`value.get()` is equivalent to `value.with(T::clone)`.)
|
||||
/// ```
|
||||
/// # use leptos_reactive::*;
|
||||
/// # create_scope(|cx| {
|
||||
/// # create_scope(create_runtime(), |cx| {
|
||||
/// let (count, set_count) = create_signal(cx, 0);
|
||||
///
|
||||
/// // calling the getter clones and returns the value
|
||||
|
@ -193,7 +194,7 @@ where
|
|||
/// Applies the function to the current Signal, if it exists, and subscribes
|
||||
/// the running effect.
|
||||
pub(crate) fn try_with<U>(&self, f: impl FnOnce(&T) -> U) -> Result<U, SignalError> {
|
||||
self.id.try_with(self.runtime, f)
|
||||
with_runtime(self.runtime, |runtime| self.id.try_with(runtime, f))
|
||||
}
|
||||
|
||||
/// Generates a [Stream] that emits the new value of the signal whenever it changes.
|
||||
|
@ -273,7 +274,7 @@ where
|
|||
///
|
||||
/// ```
|
||||
/// # use leptos_reactive::*;
|
||||
/// # create_scope(|cx| {
|
||||
/// # create_scope(create_runtime(), |cx| {
|
||||
/// let (count, set_count) = create_signal(cx, 0);
|
||||
///
|
||||
/// // ✅ calling the setter sets the value
|
||||
|
@ -294,7 +295,7 @@ pub struct WriteSignal<T>
|
|||
where
|
||||
T: 'static,
|
||||
{
|
||||
pub(crate) runtime: &'static Runtime,
|
||||
pub(crate) runtime: RuntimeId,
|
||||
pub(crate) id: SignalId,
|
||||
pub(crate) ty: PhantomData<T>,
|
||||
}
|
||||
|
@ -324,7 +325,7 @@ where
|
|||
/// even if the value has not actually changed.
|
||||
/// ```
|
||||
/// # use leptos_reactive::*;
|
||||
/// # create_scope(|cx| {
|
||||
/// # create_scope(create_runtime(), |cx| {
|
||||
/// let (count, set_count) = create_signal(cx, 0);
|
||||
///
|
||||
/// // notifies subscribers
|
||||
|
@ -347,7 +348,7 @@ where
|
|||
/// even if the value has not actually changed.
|
||||
/// ```
|
||||
/// # use leptos_reactive::*;
|
||||
/// # create_scope(|cx| {
|
||||
/// # create_scope(create_runtime(), |cx| {
|
||||
/// let (count, set_count) = create_signal(cx, 0);
|
||||
///
|
||||
/// // notifies subscribers
|
||||
|
@ -414,7 +415,7 @@ where
|
|||
/// or as a function argument.
|
||||
/// ```
|
||||
/// # use leptos_reactive::*;
|
||||
/// # create_scope(|cx| {
|
||||
/// # create_scope(create_runtime(), |cx| {
|
||||
/// let count = create_rw_signal(cx, 0);
|
||||
///
|
||||
/// // ✅ set the value
|
||||
|
@ -441,7 +442,7 @@ pub fn create_rw_signal<T>(cx: Scope, value: T) -> RwSignal<T> {
|
|||
/// its style, or it may be easier to pass around in a context or as a function argument.
|
||||
/// ```
|
||||
/// # use leptos_reactive::*;
|
||||
/// # create_scope(|cx| {
|
||||
/// # create_scope(create_runtime(), |cx| {
|
||||
/// let count = create_rw_signal(cx, 0);
|
||||
///
|
||||
/// // ✅ set the value
|
||||
|
@ -462,7 +463,7 @@ pub struct RwSignal<T>
|
|||
where
|
||||
T: 'static,
|
||||
{
|
||||
pub(crate) runtime: &'static Runtime,
|
||||
pub(crate) runtime: RuntimeId,
|
||||
pub(crate) id: SignalId,
|
||||
pub(crate) ty: PhantomData<T>,
|
||||
}
|
||||
|
@ -496,11 +497,11 @@ impl<T> UntrackedGettableSignal<T> for RwSignal<T> {
|
|||
impl<T> UntrackedSettableSignal<T> for RwSignal<T> {
|
||||
fn set_untracked(&self, new_value: T) {
|
||||
self.id
|
||||
.update_with_no_effect(self.runtime, |v| *v = new_value);
|
||||
.update_with_no_effect(self.runtime, |v| *v = new_value)
|
||||
}
|
||||
|
||||
fn update_untracked(&self, f: impl FnOnce(&mut T)) {
|
||||
self.id.update_with_no_effect(self.runtime, f);
|
||||
self.id.update_with_no_effect(self.runtime, f)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -512,7 +513,7 @@ where
|
|||
/// the running effect to this signal.
|
||||
/// ```
|
||||
/// # use leptos_reactive::*;
|
||||
/// # create_scope(|cx| {
|
||||
/// # create_scope(create_runtime(), |cx| {
|
||||
/// let name = create_rw_signal(cx, "Alice".to_string());
|
||||
///
|
||||
/// // ❌ unnecessarily clones the string
|
||||
|
@ -535,7 +536,7 @@ where
|
|||
/// the running effect to this signal.
|
||||
/// ```
|
||||
/// # use leptos_reactive::*;
|
||||
/// # create_scope(|cx| {
|
||||
/// # create_scope(create_runtime(), |cx| {
|
||||
/// let count = create_rw_signal(cx, 0);
|
||||
///
|
||||
/// assert_eq!(count.get(), 0);
|
||||
|
@ -556,7 +557,7 @@ where
|
|||
/// and notifies subscribers that the signal has changed.
|
||||
/// ```
|
||||
/// # use leptos_reactive::*;
|
||||
/// # create_scope(|cx| {
|
||||
/// # create_scope(create_runtime(), |cx| {
|
||||
/// let count = create_rw_signal(cx, 0);
|
||||
///
|
||||
/// // notifies subscribers
|
||||
|
@ -579,7 +580,7 @@ where
|
|||
/// even if the value has not actually changed.
|
||||
/// ```
|
||||
/// # use leptos_reactive::*;
|
||||
/// # create_scope(|cx| {
|
||||
/// # create_scope(create_runtime(), |cx| {
|
||||
/// let count = create_rw_signal(cx, 0);
|
||||
///
|
||||
/// assert_eq!(count(), 0);
|
||||
|
@ -597,7 +598,7 @@ where
|
|||
/// to the signal and cause other parts of the DOM to update.
|
||||
/// ```
|
||||
/// # use leptos_reactive::*;
|
||||
/// # create_scope(|cx| {
|
||||
/// # create_scope(create_runtime(), |cx| {
|
||||
/// let count = create_rw_signal(cx, 0);
|
||||
/// let read_count = count.read_only();
|
||||
/// assert_eq!(count(), 0);
|
||||
|
@ -726,78 +727,88 @@ impl SignalId {
|
|||
self.try_with_no_subscription(runtime, f)
|
||||
}
|
||||
|
||||
pub(crate) fn with_no_subscription<T, U>(&self, runtime: &Runtime, f: impl FnOnce(&T) -> U) -> U
|
||||
pub(crate) fn with_no_subscription<T, U>(
|
||||
&self,
|
||||
runtime: RuntimeId,
|
||||
f: impl FnOnce(&T) -> U,
|
||||
) -> U
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
self.try_with_no_subscription(runtime, f).unwrap()
|
||||
with_runtime(runtime, |runtime| {
|
||||
self.try_with_no_subscription(runtime, f).unwrap()
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn with<T, U>(&self, runtime: &Runtime, f: impl FnOnce(&T) -> U) -> U
|
||||
pub(crate) fn with<T, U>(&self, runtime: RuntimeId, f: impl FnOnce(&T) -> U) -> U
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
self.try_with(runtime, f).unwrap()
|
||||
with_runtime(runtime, |runtime| self.try_with(runtime, f).unwrap())
|
||||
}
|
||||
|
||||
fn update_value<T>(&self, runtime: &Runtime, f: impl FnOnce(&mut T)) -> bool
|
||||
fn update_value<T>(&self, runtime: RuntimeId, f: impl FnOnce(&mut T)) -> bool
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
let value = {
|
||||
let signals = runtime.signals.borrow();
|
||||
signals.get(*self).cloned()
|
||||
};
|
||||
if let Some(value) = value {
|
||||
let mut value = value.borrow_mut();
|
||||
if let Some(value) = value.downcast_mut::<T>() {
|
||||
f(value);
|
||||
true
|
||||
with_runtime(runtime, |runtime| {
|
||||
let value = {
|
||||
let signals = runtime.signals.borrow();
|
||||
signals.get(*self).cloned()
|
||||
};
|
||||
if let Some(value) = value {
|
||||
let mut value = value.borrow_mut();
|
||||
if let Some(value) = value.downcast_mut::<T>() {
|
||||
f(value);
|
||||
true
|
||||
} else {
|
||||
debug_warn!(
|
||||
"[Signal::update] failed when downcasting to Signal<{}>",
|
||||
std::any::type_name::<T>()
|
||||
);
|
||||
false
|
||||
}
|
||||
} else {
|
||||
debug_warn!(
|
||||
"[Signal::update] failed when downcasting to Signal<{}>",
|
||||
"[Signal::update] You’re trying to update a Signal<{}> that has already been disposed of. This is probably either a logic error in a component that creates and disposes of scopes, or a Resource resolving after its scope has been dropped without having been cleaned up.",
|
||||
std::any::type_name::<T>()
|
||||
);
|
||||
false
|
||||
}
|
||||
} else {
|
||||
debug_warn!(
|
||||
"[Signal::update] You’re trying to update a Signal<{}> that has already been disposed of. This is probably either a logic error in a component that creates and disposes of scopes, or a Resource resolving after its scope has been dropped without having been cleaned up.",
|
||||
std::any::type_name::<T>()
|
||||
);
|
||||
false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn update<T>(&self, runtime: &Runtime, f: impl FnOnce(&mut T))
|
||||
pub(crate) fn update<T>(&self, runtime_id: RuntimeId, f: impl FnOnce(&mut T))
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
// update the value
|
||||
let updated = self.update_value(runtime, f);
|
||||
with_runtime(runtime_id, |runtime| {
|
||||
// update the value
|
||||
let updated = self.update_value(runtime_id, f);
|
||||
|
||||
// notify subscribers
|
||||
if updated {
|
||||
let subs = {
|
||||
let subs = runtime.signal_subscribers.borrow();
|
||||
let subs = subs.get(*self);
|
||||
subs.map(|subs| subs.borrow().clone())
|
||||
};
|
||||
if let Some(subs) = subs {
|
||||
for sub in subs {
|
||||
let effect = {
|
||||
let effects = runtime.effects.borrow();
|
||||
effects.get(sub).cloned()
|
||||
};
|
||||
if let Some(effect) = effect {
|
||||
effect.run(sub, runtime);
|
||||
// notify subscribers
|
||||
if updated {
|
||||
let subs = {
|
||||
let subs = runtime.signal_subscribers.borrow();
|
||||
let subs = subs.get(*self);
|
||||
subs.map(|subs| subs.borrow().clone())
|
||||
};
|
||||
if let Some(subs) = subs {
|
||||
for sub in subs {
|
||||
let effect = {
|
||||
let effects = runtime.effects.borrow();
|
||||
effects.get(sub).cloned()
|
||||
};
|
||||
if let Some(effect) = effect {
|
||||
effect.run(sub, runtime_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn update_with_no_effect<T>(&self, runtime: &Runtime, f: impl FnOnce(&mut T))
|
||||
pub(crate) fn update_with_no_effect<T>(&self, runtime: RuntimeId, f: impl FnOnce(&mut T))
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
|
|
|
@ -10,8 +10,8 @@ use crate::{Memo, ReadSignal, RwSignal, Scope, UntrackedGettableSignal};
|
|||
/// function call, `with()`, and `get()` APIs as over signals.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use leptos_reactive::{create_scope, create_signal, create_rw_signal, create_memo, Signal};
|
||||
/// # create_scope(|cx| {
|
||||
/// # use leptos_reactive::*;
|
||||
/// # create_scope(create_runtime(), |cx| {
|
||||
/// let (count, set_count) = create_signal(cx, 2);
|
||||
/// let double_count = Signal::derive(cx, move || count() * 2);
|
||||
/// let memoized_double_count = create_memo(cx, move |_| count() * 2);
|
||||
|
@ -73,8 +73,8 @@ where
|
|||
/// Wraps a derived signal, i.e., any computation that accesses one or more
|
||||
/// reactive signals.
|
||||
/// ```rust
|
||||
/// # use leptos_reactive::{create_scope, create_signal, create_rw_signal, create_memo, Signal};
|
||||
/// # create_scope(|cx| {
|
||||
/// # use leptos_reactive::*;
|
||||
/// # create_scope(create_runtime(), |cx| {
|
||||
/// let (count, set_count) = create_signal(cx, 2);
|
||||
/// let double_count = Signal::derive(cx, move || count() * 2);
|
||||
///
|
||||
|
@ -95,7 +95,7 @@ where
|
|||
/// the running effect to this signal.
|
||||
/// ```
|
||||
/// # use leptos_reactive::*;
|
||||
/// # create_scope(|cx| {
|
||||
/// # create_scope(create_runtime(), |cx| {
|
||||
/// let (name, set_name) = create_signal(cx, "Alice".to_string());
|
||||
/// let name_upper = Signal::derive(cx, move || name.with(|n| n.to_uppercase()));
|
||||
/// let memoized_lower = create_memo(cx, move |_| name.with(|n| n.to_lowercase()));
|
||||
|
@ -134,8 +134,8 @@ where
|
|||
/// If you want to get the value without cloning it, use [ReadSignal::with].
|
||||
/// (There’s no difference in behavior for derived signals: they re-run in any case.)
|
||||
/// ```
|
||||
/// # use leptos_reactive::{create_scope, create_signal, create_rw_signal, create_memo, Signal};
|
||||
/// # create_scope(|cx| {
|
||||
/// # use leptos_reactive::*;
|
||||
/// # create_scope(create_runtime(), |cx| {
|
||||
/// let (count, set_count) = create_signal(cx, 2);
|
||||
/// let double_count = Signal::derive(cx, move || count() * 2);
|
||||
/// let memoized_double_count = create_memo(cx, move |_| count() * 2);
|
||||
|
@ -258,7 +258,7 @@ where
|
|||
///
|
||||
/// ```rust
|
||||
/// # use leptos_reactive::*;
|
||||
/// # create_scope(|cx| {
|
||||
/// # create_scope(create_runtime(), |cx| {
|
||||
/// let (count, set_count) = create_signal(cx, 2);
|
||||
/// let double_count = MaybeSignal::derive(cx, move || count() * 2);
|
||||
/// let memoized_double_count = create_memo(cx, move |_| count() * 2);
|
||||
|
@ -317,8 +317,8 @@ where
|
|||
/// Wraps a derived signal, i.e., any computation that accesses one or more
|
||||
/// reactive signals.
|
||||
/// ```rust
|
||||
/// # use leptos_reactive::{create_scope, create_signal, create_rw_signal, create_memo, Signal};
|
||||
/// # create_scope(|cx| {
|
||||
/// # use leptos_reactive::*;
|
||||
/// # create_scope(create_runtime(), |cx| {
|
||||
/// let (count, set_count) = create_signal(cx, 2);
|
||||
/// let double_count = Signal::derive(cx, move || count() * 2);
|
||||
///
|
||||
|
@ -339,7 +339,7 @@ where
|
|||
/// the running effect to this signal.
|
||||
/// ```
|
||||
/// # use leptos_reactive::*;
|
||||
/// # create_scope(|cx| {
|
||||
/// # create_scope(create_runtime(), |cx| {
|
||||
/// let (name, set_name) = create_signal(cx, "Alice".to_string());
|
||||
/// let name_upper = MaybeSignal::derive(cx, move || name.with(|n| n.to_uppercase()));
|
||||
/// let memoized_lower = create_memo(cx, move |_| name.with(|n| n.to_lowercase()));
|
||||
|
@ -381,7 +381,7 @@ where
|
|||
/// (There’s no difference in behavior for derived signals: they re-run in any case.)
|
||||
/// ```
|
||||
/// # use leptos_reactive::*;
|
||||
/// # create_scope(|cx| {
|
||||
/// # create_scope(create_runtime(), |cx| {
|
||||
/// let (count, set_count) = create_signal(cx, 2);
|
||||
/// let double_count = MaybeSignal::derive(cx, move || count() * 2);
|
||||
/// let memoized_double_count = create_memo(cx, move |_| count() * 2);
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
#[cfg(not(feature = "stable"))]
|
||||
use leptos_reactive::{create_isomorphic_effect, create_memo, create_scope, create_signal};
|
||||
use leptos_reactive::{
|
||||
create_isomorphic_effect, create_memo, create_runtime, create_scope, create_signal,
|
||||
};
|
||||
|
||||
#[cfg(not(feature = "stable"))]
|
||||
#[test]
|
||||
|
@ -7,7 +9,7 @@ fn effect_runs() {
|
|||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
create_scope(|cx| {
|
||||
create_scope(create_runtime(), |cx| {
|
||||
let (a, set_a) = create_signal(cx, -1);
|
||||
|
||||
// simulate an arbitrary side effect
|
||||
|
@ -36,7 +38,7 @@ fn effect_tracks_memo() {
|
|||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
create_scope(|cx| {
|
||||
create_scope(create_runtime(), |cx| {
|
||||
let (a, set_a) = create_signal(cx, -1);
|
||||
let b = create_memo(cx, move |_| format!("Value is {}", a()));
|
||||
|
||||
|
@ -67,7 +69,7 @@ fn untrack_mutes_effect() {
|
|||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
create_scope(|cx| {
|
||||
create_scope(create_runtime(), |cx| {
|
||||
let (a, set_a) = create_signal(cx, -1);
|
||||
|
||||
// simulate an arbitrary side effect
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
#[cfg(not(feature = "stable"))]
|
||||
use leptos_reactive::{create_memo, create_scope, create_signal};
|
||||
use leptos_reactive::{create_memo, create_runtime, create_scope, create_signal};
|
||||
|
||||
#[cfg(not(feature = "stable"))]
|
||||
#[test]
|
||||
fn basic_memo() {
|
||||
create_scope(|cx| {
|
||||
create_scope(create_runtime(), |cx| {
|
||||
let a = create_memo(cx, |_| 5);
|
||||
assert_eq!(a(), 5);
|
||||
})
|
||||
|
@ -14,7 +14,7 @@ fn basic_memo() {
|
|||
#[cfg(not(feature = "stable"))]
|
||||
#[test]
|
||||
fn memo_with_computed_value() {
|
||||
create_scope(|cx| {
|
||||
create_scope(create_runtime(), |cx| {
|
||||
let (a, set_a) = create_signal(cx, 0);
|
||||
let (b, set_b) = create_signal(cx, 0);
|
||||
let c = create_memo(cx, move |_| a() + b());
|
||||
|
@ -30,7 +30,7 @@ fn memo_with_computed_value() {
|
|||
#[cfg(not(feature = "stable"))]
|
||||
#[test]
|
||||
fn nested_memos() {
|
||||
create_scope(|cx| {
|
||||
create_scope(create_runtime(), |cx| {
|
||||
let (a, set_a) = create_signal(cx, 0);
|
||||
let (b, set_b) = create_signal(cx, 0);
|
||||
let c = create_memo(cx, move |_| a() + b());
|
||||
|
@ -54,7 +54,7 @@ fn nested_memos() {
|
|||
fn memo_runs_only_when_inputs_change() {
|
||||
use std::{cell::Cell, rc::Rc};
|
||||
|
||||
create_scope(|cx| {
|
||||
create_scope(create_runtime(), |cx| {
|
||||
let call_count = Rc::new(Cell::new(0));
|
||||
let (a, set_a) = create_signal(cx, 0);
|
||||
let (b, _) = create_signal(cx, 0);
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
#[cfg(not(feature = "stable"))]
|
||||
use leptos_reactive::{create_scope, create_signal};
|
||||
use leptos_reactive::{create_runtime, create_scope, create_signal};
|
||||
|
||||
#[cfg(not(feature = "stable"))]
|
||||
#[test]
|
||||
fn basic_signal() {
|
||||
create_scope(|cx| {
|
||||
create_scope(create_runtime(), |cx| {
|
||||
let (a, set_a) = create_signal(cx, 0);
|
||||
assert_eq!(a(), 0);
|
||||
set_a(5);
|
||||
|
@ -16,7 +16,7 @@ fn basic_signal() {
|
|||
#[cfg(not(feature = "stable"))]
|
||||
#[test]
|
||||
fn derived_signals() {
|
||||
create_scope(|cx| {
|
||||
create_scope(create_runtime(), |cx| {
|
||||
let (a, set_a) = create_signal(cx, 0);
|
||||
let (b, set_b) = create_signal(cx, 0);
|
||||
let c = move || a() + b();
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
//#[cfg(not(feature = "stable"))]
|
||||
use leptos_reactive::{
|
||||
create_isomorphic_effect, create_scope, create_signal, UntrackedGettableSignal,
|
||||
create_isomorphic_effect, create_runtime, create_scope, create_signal, UntrackedGettableSignal,
|
||||
UntrackedSettableSignal,
|
||||
};
|
||||
|
||||
|
@ -10,7 +10,7 @@ fn untracked_set_doesnt_trigger_effect() {
|
|||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
create_scope(|cx| {
|
||||
create_scope(create_runtime(), |cx| {
|
||||
let (a, set_a) = create_signal(cx, -1);
|
||||
|
||||
// simulate an arbitrary side effect
|
||||
|
@ -42,7 +42,7 @@ fn untracked_get_doesnt_trigger_effect() {
|
|||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
create_scope(|cx| {
|
||||
create_scope(create_runtime(), |cx| {
|
||||
let (a, set_a) = create_signal(cx, -1);
|
||||
let (a2, set_a2) = create_signal(cx, 1);
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "leptos_server"
|
||||
version = "0.0.17"
|
||||
version = "0.0.18"
|
||||
edition = "2021"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
|
@ -8,8 +8,8 @@ repository = "https://github.com/gbj/leptos"
|
|||
description = "RPC for the Leptos web framework."
|
||||
|
||||
[dependencies]
|
||||
leptos_dom = { path = "../leptos_dom", default-features = false, version = "0.0.17" }
|
||||
leptos_reactive = { path = "../leptos_reactive", default-features = false, version = "0.0.17" }
|
||||
leptos_dom = { path = "../leptos_dom", default-features = false, version = "0.0.18" }
|
||||
leptos_reactive = { path = "../leptos_reactive", default-features = false, version = "0.0.18" }
|
||||
form_urlencoded = "1"
|
||||
gloo-net = "0.2"
|
||||
lazy_static = "1"
|
||||
|
|
|
@ -9,9 +9,9 @@ use std::{future::Future, pin::Pin, rc::Rc};
|
|||
/// run an `async` function in response to something like a user clicking a button, you're in the right place.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use leptos_reactive::run_scope;
|
||||
/// # use leptos_reactive::*;
|
||||
/// # use leptos_server::create_action;
|
||||
/// # run_scope(|cx| {
|
||||
/// # run_scope(create_runtime(), |cx| {
|
||||
/// async fn send_new_todo_to_api(task: String) -> usize {
|
||||
/// // do something...
|
||||
/// // return a task id
|
||||
|
@ -60,9 +60,9 @@ use std::{future::Future, pin::Pin, rc::Rc};
|
|||
/// function, because it is stored in [Action::input] as well.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use leptos_reactive::run_scope;
|
||||
/// # use leptos_reactive::*;
|
||||
/// # use leptos_server::create_action;
|
||||
/// # run_scope(|cx| {
|
||||
/// # run_scope(create_runtime(), |cx| {
|
||||
/// // if there's a single argument, just use that
|
||||
/// let action1 = create_action(cx, |input: &String| {
|
||||
/// let input = input.clone();
|
||||
|
@ -150,9 +150,9 @@ where
|
|||
/// you're in the right place.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use leptos_reactive::run_scope;
|
||||
/// # use leptos_reactive::*;
|
||||
/// # use leptos_server::create_action;
|
||||
/// # run_scope(|cx| {
|
||||
/// # run_scope(create_runtime(), |cx| {
|
||||
/// async fn send_new_todo_to_api(task: String) -> usize {
|
||||
/// // do something...
|
||||
/// // return a task id
|
||||
|
@ -201,9 +201,9 @@ where
|
|||
/// function, because it is stored in [Action::input] as well.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use leptos_reactive::run_scope;
|
||||
/// # use leptos_reactive::*;
|
||||
/// # use leptos_server::create_action;
|
||||
/// # run_scope(|cx| {
|
||||
/// # run_scope(create_runtime(), |cx| {
|
||||
/// // if there's a single argument, just use that
|
||||
/// let action1 = create_action(cx, |input: &String| {
|
||||
/// let input = input.clone();
|
||||
|
@ -246,7 +246,7 @@ where
|
|||
/// Creates an [Action] that can be used to call a server function.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use leptos_reactive::run_scope;
|
||||
/// # use leptos_reactive::*;
|
||||
/// # use leptos_server::{create_server_action, ServerFnError, ServerFn};
|
||||
/// # use leptos_macro::server;
|
||||
///
|
||||
|
@ -255,7 +255,7 @@ where
|
|||
/// todo!()
|
||||
/// }
|
||||
///
|
||||
/// # run_scope(|cx| {
|
||||
/// # run_scope(create_runtime(), |cx| {
|
||||
/// let my_server_action = create_server_action::<MyServerFn>(cx);
|
||||
/// # });
|
||||
/// ```
|
||||
|
|
|
@ -40,7 +40,7 @@
|
|||
//! }
|
||||
//!
|
||||
//! // call the function
|
||||
//! # run_scope(|cx| {
|
||||
//! # run_scope(create_runtime(), |cx| {
|
||||
//! spawn_local(async {
|
||||
//! let posts = read_posts(3, "my search".to_string()).await;
|
||||
//! log::debug!("posts = {posts{:#?}");
|
||||
|
|
|
@ -14,9 +14,9 @@ use std::{future::Future, pin::Pin, rc::Rc};
|
|||
/// you’re in the right place.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use leptos_reactive::run_scope;
|
||||
/// # use leptos_reactive::*;
|
||||
/// # use leptos_server::create_multi_action;
|
||||
/// # run_scope(|cx| {
|
||||
/// # run_scope(create_runtime(), |cx| {
|
||||
/// async fn send_new_todo_to_api(task: String) -> usize {
|
||||
/// // do something...
|
||||
/// // return a task id
|
||||
|
@ -41,9 +41,9 @@ use std::{future::Future, pin::Pin, rc::Rc};
|
|||
/// function, because it is stored in [Submission::input] as well.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use leptos_reactive::run_scope;
|
||||
/// # use leptos_reactive::*;
|
||||
/// # use leptos_server::create_multi_action;
|
||||
/// # run_scope(|cx| {
|
||||
/// # run_scope(create_runtime(), |cx| {
|
||||
/// // if there's a single argument, just use that
|
||||
/// let action1 = create_multi_action(cx, |input: &String| {
|
||||
/// let input = input.clone();
|
||||
|
@ -186,9 +186,9 @@ where
|
|||
/// you're in the right place.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use leptos_reactive::run_scope;
|
||||
/// # use leptos_reactive::*;
|
||||
/// # use leptos_server::create_multi_action;
|
||||
/// # run_scope(|cx| {
|
||||
/// # run_scope(create_runtime(), |cx| {
|
||||
/// async fn send_new_todo_to_api(task: String) -> usize {
|
||||
/// // do something...
|
||||
/// // return a task id
|
||||
|
@ -214,9 +214,9 @@ where
|
|||
/// function, because it is stored in [Submission::input] as well.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use leptos_reactive::run_scope;
|
||||
/// # use leptos_reactive::*;
|
||||
/// # use leptos_server::create_multi_action;
|
||||
/// # run_scope(|cx| {
|
||||
/// # run_scope(create_runtime(), |cx| {
|
||||
/// // if there's a single argument, just use that
|
||||
/// let action1 = create_multi_action(cx, |input: &String| {
|
||||
/// let input = input.clone();
|
||||
|
@ -256,7 +256,7 @@ where
|
|||
/// Creates an [MultiAction] that can be used to call a server function.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use leptos_reactive::run_scope;
|
||||
/// # use leptos_reactive::*;
|
||||
/// # use leptos_server::{create_server_multi_action, ServerFnError, ServerFn};
|
||||
/// # use leptos_macro::server;
|
||||
///
|
||||
|
@ -265,7 +265,7 @@ where
|
|||
/// todo!()
|
||||
/// }
|
||||
///
|
||||
/// # run_scope(|cx| {
|
||||
/// # run_scope(create_runtime(), |cx| {
|
||||
/// let my_server_multi_action = create_server_multi_action::<MyServerFn>(cx);
|
||||
/// # });
|
||||
/// ```
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "leptos_meta"
|
||||
version = "0.0.1"
|
||||
version = "0.0.2"
|
||||
edition = "2021"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
|
|
|
@ -92,7 +92,7 @@ impl MetaContext {
|
|||
/// use leptos_meta::*;
|
||||
///
|
||||
/// # #[cfg(not(any(feature = "csr", feature = "hydrate")))] {
|
||||
/// run_scope(|cx| {
|
||||
/// run_scope(create_runtime(), |cx| {
|
||||
/// provide_context(cx, MetaContext::new());
|
||||
///
|
||||
/// let app = view! { cx,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "leptos_router"
|
||||
version = "0.0.3"
|
||||
version = "0.0.4"
|
||||
edition = "2021"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
|
|
|
@ -64,7 +64,7 @@ where
|
|||
let action_version = version;
|
||||
let action = use_resolved_path(cx, move || action.to_href()());
|
||||
|
||||
let on_submit = move |ev: web_sys::Event| {
|
||||
let on_submit = move |ev: web_sys::SubmitEvent| {
|
||||
if ev.default_prevented() {
|
||||
return;
|
||||
}
|
||||
|
@ -262,7 +262,7 @@ where
|
|||
""
|
||||
}.to_string();
|
||||
|
||||
let on_submit = move |ev: web_sys::Event| {
|
||||
let on_submit = move |ev: web_sys::SubmitEvent| {
|
||||
if ev.default_prevented() {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -119,7 +119,7 @@ impl History for BrowserIntegration {
|
|||
/// ```
|
||||
/// # use leptos_router::*;
|
||||
/// # use leptos::*;
|
||||
/// # run_scope(|cx| {
|
||||
/// # run_scope(create_runtime(), |cx| {
|
||||
/// let integration = ServerIntegration { path: "insert/current/path/here".to_string() };
|
||||
/// provide_context(cx, RouterIntegrationContext::new(integration));
|
||||
/// # });
|
||||
|
|
Loading…
Reference in a new issue