mirror of
https://github.com/leptos-rs/leptos.git
synced 2025-12-28 16:02:33 -05:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
565a901c08 | ||
|
|
f626ac26e0 | ||
|
|
fb3159d093 | ||
|
|
67845be161 | ||
|
|
2bf1f46b88 | ||
|
|
70ae3a0abb | ||
|
|
96e2b5cba1 | ||
|
|
ef27a198d9 | ||
|
|
47299926bb |
@@ -38,7 +38,7 @@
|
||||
if (islandFn) {
|
||||
islandFn(el);
|
||||
} else {
|
||||
console.warn(`Could not find WASM function for the island ${l}.`);
|
||||
console.warn(`Could not find WASM function for the island ${id}.`);
|
||||
}
|
||||
}
|
||||
function hydrateIslands(entry, mod) {
|
||||
|
||||
@@ -293,6 +293,10 @@ pub mod spawn {
|
||||
pub async fn tick() {
|
||||
Executor::tick().await
|
||||
}
|
||||
|
||||
pub use reactive_graph::{
|
||||
spawn_local_scoped, spawn_local_scoped_with_cancellation,
|
||||
};
|
||||
}
|
||||
|
||||
// these reexports are used in islands
|
||||
|
||||
@@ -87,6 +87,7 @@ pub mod traits;
|
||||
pub mod transition;
|
||||
pub mod wrappers;
|
||||
|
||||
use computed::ScopedFuture;
|
||||
pub use graph::untrack;
|
||||
|
||||
#[cfg(feature = "nightly")]
|
||||
@@ -130,3 +131,36 @@ pub(crate) fn spawn(task: impl Future<Output = ()> + Send + 'static) {
|
||||
|
||||
any_spawner::Executor::spawn(task);
|
||||
}
|
||||
|
||||
/// Calls [`Executor::spawn_local`], but ensures that the task runs under the current reactive [`Owner`]
|
||||
/// and [`Observed`]. Does not cancel the task if the owner is cleaned up.
|
||||
pub fn spawn_local_scoped(task: impl Future<Output = ()> + 'static) {
|
||||
let task = ScopedFuture::new(task);
|
||||
|
||||
#[cfg(feature = "sandboxed-arenas")]
|
||||
let task = owner::Sandboxed::new(task);
|
||||
|
||||
any_spawner::Executor::spawn_local(task);
|
||||
}
|
||||
|
||||
/// Calls [`Executor::spawn_local`], but ensures that the task runs under the current reactive [`Owner`]
|
||||
/// and [`Observed`]. Cancels the task if the owner is cleaned up.
|
||||
pub fn spawn_local_scoped_with_cancellation(
|
||||
task: impl Future<Output = ()> + 'static,
|
||||
) {
|
||||
use crate::owner::on_cleanup;
|
||||
use futures::future::{AbortHandle, Abortable};
|
||||
|
||||
let (abort_handle, abort_registration) = AbortHandle::new_pair();
|
||||
on_cleanup(move || abort_handle.abort());
|
||||
|
||||
let task = Abortable::new(task, abort_registration);
|
||||
let task = ScopedFuture::new(task);
|
||||
|
||||
#[cfg(feature = "sandboxed-arenas")]
|
||||
let task = owner::Sandboxed::new(task);
|
||||
|
||||
any_spawner::Executor::spawn_local(async move {
|
||||
_ = task.await;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use super::{ArenaItem, LocalStorage, Storage, SyncStorage};
|
||||
use crate::{
|
||||
signal::guards::{Plain, ReadGuard, UntrackedWriteGuard},
|
||||
traits::{DefinedAt, Dispose, IsDisposed},
|
||||
unwrap_signal,
|
||||
};
|
||||
@@ -193,6 +194,44 @@ impl<T, S: Storage<Arc<RwLock<T>>>> StoredValue<T, S> {
|
||||
.unwrap_or_else(unwrap_signal!(self))
|
||||
}
|
||||
|
||||
/// Returns a read guard to the stored data, or `None` if the owner of the reactive node has been disposed.
|
||||
#[track_caller]
|
||||
pub fn try_read_value(&self) -> Option<ReadGuard<T, Plain<T>>> {
|
||||
self.value
|
||||
.try_get_value()
|
||||
.and_then(|inner| Plain::try_new(inner).map(ReadGuard::new))
|
||||
}
|
||||
|
||||
/// Returns a read guard to the stored data.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This function panics when called after the owner of the reactive node has been disposed.
|
||||
/// See [`StoredValue::try_read_value`] for a version without panic.
|
||||
#[track_caller]
|
||||
pub fn read_value(&self) -> ReadGuard<T, Plain<T>> {
|
||||
self.try_read_value().unwrap_or_else(unwrap_signal!(self))
|
||||
}
|
||||
|
||||
/// Returns a write guard to the stored data, or `None` if the owner of the reactive node has been disposed.
|
||||
#[track_caller]
|
||||
pub fn try_write_value(&self) -> Option<UntrackedWriteGuard<T>> {
|
||||
self.value
|
||||
.try_get_value()
|
||||
.and_then(|inner| UntrackedWriteGuard::try_new(inner))
|
||||
}
|
||||
|
||||
/// Returns a write guard to the stored data.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This function panics when called after the owner of the reactive node has been disposed.
|
||||
/// See [`StoredValue::try_write_value`] for a version without panic.
|
||||
#[track_caller]
|
||||
pub fn write_value(&self) -> UntrackedWriteGuard<T> {
|
||||
self.try_write_value().unwrap_or_else(unwrap_signal!(self))
|
||||
}
|
||||
|
||||
/// Updates the current value by applying the given closure, returning the return value of the
|
||||
/// closure, or `None` if the value has already been disposed.
|
||||
pub fn try_update_value<U>(
|
||||
|
||||
@@ -43,7 +43,7 @@ where
|
||||
Fal: Render + 'static,
|
||||
{
|
||||
#[allow(clippy::type_complexity)]
|
||||
view: <EitherOf3<(), Fal, <Defs::Match as MatchInterface>::View> as Render>::State,
|
||||
view: <EitherOf3<(), Fal, OwnedView<<Defs::Match as MatchInterface>::View>> as Render>::State,
|
||||
id: Option<RouteMatchId>,
|
||||
owner: Owner,
|
||||
params: ArcRwSignal<ParamsMap>,
|
||||
@@ -147,7 +147,7 @@ where
|
||||
provide_context(params_memo);
|
||||
provide_context(url);
|
||||
provide_context(Matched(ArcMemo::from(matched)));
|
||||
view.choose().await
|
||||
OwnedView::new(view.choose().await)
|
||||
}
|
||||
})
|
||||
}));
|
||||
@@ -292,7 +292,7 @@ where
|
||||
provide_context(Matched(ArcMemo::from(
|
||||
new_matched,
|
||||
)));
|
||||
let view =
|
||||
let view = OwnedView::new(
|
||||
if let Some(set_is_routing) = set_is_routing {
|
||||
set_is_routing.set(true);
|
||||
let value =
|
||||
@@ -302,7 +302,8 @@ where
|
||||
value
|
||||
} else {
|
||||
view.choose().await
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
// only update the route if it's still the current path
|
||||
// i.e., if we've navigated away before this has loaded, do nothing
|
||||
@@ -572,7 +573,7 @@ where
|
||||
provide_context(params_memo);
|
||||
provide_context(url);
|
||||
provide_context(Matched(ArcMemo::from(matched)));
|
||||
view.choose().await
|
||||
OwnedView::new(view.choose().await)
|
||||
}
|
||||
})
|
||||
}));
|
||||
|
||||
@@ -29,6 +29,7 @@ where
|
||||
const SELF_CLOSING: bool = false;
|
||||
const ESCAPE_CHILDREN: bool = true;
|
||||
const TAG: &'static str = "";
|
||||
const NAMESPACE: Option<&'static str> = None;
|
||||
|
||||
fn tag(&self) -> &str {
|
||||
self.0.as_ref()
|
||||
|
||||
@@ -72,6 +72,7 @@ macro_rules! html_element_inner {
|
||||
const TAG: &'static str = stringify!($tag);
|
||||
const SELF_CLOSING: bool = false;
|
||||
const ESCAPE_CHILDREN: bool = $escape;
|
||||
const NAMESPACE: Option<&'static str> = None;
|
||||
|
||||
#[inline(always)]
|
||||
fn tag(&self) -> &str {
|
||||
@@ -170,6 +171,7 @@ macro_rules! html_self_closing_elements {
|
||||
const TAG: &'static str = stringify!($tag);
|
||||
const SELF_CLOSING: bool = true;
|
||||
const ESCAPE_CHILDREN: bool = $escape;
|
||||
const NAMESPACE: Option<&'static str> = None;
|
||||
|
||||
#[inline(always)]
|
||||
fn tag(&self) -> &str {
|
||||
|
||||
@@ -138,6 +138,8 @@ pub trait ElementType: Send {
|
||||
/// like `<style>` and `<script>`, which include other languages that should not use HTML
|
||||
/// entity escaping.
|
||||
const ESCAPE_CHILDREN: bool;
|
||||
/// The element's namespace, if it is not HTML.
|
||||
const NAMESPACE: Option<&'static str>;
|
||||
|
||||
/// The element's tag.
|
||||
fn tag(&self) -> &str;
|
||||
@@ -177,7 +179,7 @@ where
|
||||
}
|
||||
|
||||
fn build(self) -> Self::State {
|
||||
let el = Rndr::create_element(self.tag.tag());
|
||||
let el = Rndr::create_element(self.tag.tag(), E::NAMESPACE);
|
||||
|
||||
let attrs = self.attributes.build(&el);
|
||||
let children = if E::SELF_CLOSING {
|
||||
|
||||
@@ -101,6 +101,7 @@ macro_rules! mathml_elements {
|
||||
const TAG: &'static str = stringify!($tag);
|
||||
const SELF_CLOSING: bool = false;
|
||||
const ESCAPE_CHILDREN: bool = true;
|
||||
const NAMESPACE: Option<&'static str> = Some("http://www.w3.org/1998/Math/MathML");
|
||||
|
||||
#[inline(always)]
|
||||
fn tag(&self) -> &str {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use super::{ReactiveFunction, SharedReactiveFunction, Suspend};
|
||||
use crate::{html::class::IntoClass, renderer::Rndr};
|
||||
use any_spawner::Executor;
|
||||
use futures::FutureExt;
|
||||
use reactive_graph::{effect::RenderEffect, signal::guards::ReadGuard};
|
||||
use std::{
|
||||
@@ -738,7 +737,7 @@ where
|
||||
) -> Self::State {
|
||||
let el = el.to_owned();
|
||||
let state = Rc::new(RefCell::new(None));
|
||||
Executor::spawn_local({
|
||||
reactive_graph::spawn_local_scoped({
|
||||
let state = Rc::clone(&state);
|
||||
async move {
|
||||
*state.borrow_mut() =
|
||||
@@ -752,7 +751,7 @@ where
|
||||
fn build(self, el: &crate::renderer::types::Element) -> Self::State {
|
||||
let el = el.to_owned();
|
||||
let state = Rc::new(RefCell::new(None));
|
||||
Executor::spawn_local({
|
||||
reactive_graph::spawn_local_scoped({
|
||||
let state = Rc::clone(&state);
|
||||
async move {
|
||||
*state.borrow_mut() = Some(self.inner.await.build(&el));
|
||||
@@ -763,7 +762,7 @@ where
|
||||
}
|
||||
|
||||
fn rebuild(self, state: &mut Self::State) {
|
||||
Executor::spawn_local({
|
||||
reactive_graph::spawn_local_scoped({
|
||||
let state = Rc::clone(state);
|
||||
async move {
|
||||
let value = self.inner.await;
|
||||
|
||||
@@ -8,7 +8,6 @@ use crate::{
|
||||
RenderHtml, ToTemplate,
|
||||
},
|
||||
};
|
||||
use any_spawner::Executor;
|
||||
use reactive_graph::effect::RenderEffect;
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
@@ -391,7 +390,7 @@ where
|
||||
let key = key.to_owned();
|
||||
let el = el.to_owned();
|
||||
let state = Rc::new(RefCell::new(None));
|
||||
Executor::spawn_local({
|
||||
reactive_graph::spawn_local_scoped({
|
||||
let state = Rc::clone(&state);
|
||||
async move {
|
||||
*state.borrow_mut() =
|
||||
@@ -410,7 +409,7 @@ where
|
||||
let key = key.to_owned();
|
||||
let el = el.to_owned();
|
||||
let state = Rc::new(RefCell::new(None));
|
||||
Executor::spawn_local({
|
||||
reactive_graph::spawn_local_scoped({
|
||||
let state = Rc::clone(&state);
|
||||
async move {
|
||||
*state.borrow_mut() = Some(self.inner.await.build(&el, &key));
|
||||
@@ -422,7 +421,7 @@ where
|
||||
|
||||
fn rebuild(self, key: &str, state: &mut Self::State) {
|
||||
let key = key.to_owned();
|
||||
Executor::spawn_local({
|
||||
reactive_graph::spawn_local_scoped({
|
||||
let state = Rc::clone(state);
|
||||
async move {
|
||||
let value = self.inner.await;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use super::{ReactiveFunction, SharedReactiveFunction, Suspend};
|
||||
use crate::{html::style::IntoStyle, renderer::Rndr};
|
||||
use any_spawner::Executor;
|
||||
use futures::FutureExt;
|
||||
use reactive_graph::effect::RenderEffect;
|
||||
use std::{borrow::Cow, cell::RefCell, future::Future, rc::Rc};
|
||||
@@ -463,7 +462,7 @@ where
|
||||
) -> Self::State {
|
||||
let el = el.to_owned();
|
||||
let state = Rc::new(RefCell::new(None));
|
||||
Executor::spawn_local({
|
||||
reactive_graph::spawn_local_scoped({
|
||||
let state = Rc::clone(&state);
|
||||
async move {
|
||||
*state.borrow_mut() =
|
||||
@@ -477,7 +476,7 @@ where
|
||||
fn build(self, el: &crate::renderer::types::Element) -> Self::State {
|
||||
let el = el.to_owned();
|
||||
let state = Rc::new(RefCell::new(None));
|
||||
Executor::spawn_local({
|
||||
reactive_graph::spawn_local_scoped({
|
||||
let state = Rc::clone(&state);
|
||||
async move {
|
||||
*state.borrow_mut() = Some(self.inner.await.build(&el));
|
||||
@@ -488,7 +487,7 @@ where
|
||||
}
|
||||
|
||||
fn rebuild(self, state: &mut Self::State) {
|
||||
Executor::spawn_local({
|
||||
reactive_graph::spawn_local_scoped({
|
||||
let state = Rc::clone(state);
|
||||
async move {
|
||||
let value = self.inner.await;
|
||||
|
||||
@@ -8,7 +8,10 @@ use crate::{
|
||||
},
|
||||
};
|
||||
use any_spawner::Executor;
|
||||
use futures::{select, FutureExt};
|
||||
use futures::{
|
||||
future::{AbortHandle, Abortable},
|
||||
select, FutureExt,
|
||||
};
|
||||
use or_poisoned::OrPoisoned;
|
||||
use reactive_graph::{
|
||||
computed::{
|
||||
@@ -19,7 +22,7 @@ use reactive_graph::{
|
||||
AnySource, AnySubscriber, Observer, ReactiveNode, Source, Subscriber,
|
||||
ToAnySubscriber, WithObserver,
|
||||
},
|
||||
owner::{provide_context, use_context},
|
||||
owner::{on_cleanup, provide_context, use_context},
|
||||
};
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
@@ -164,15 +167,20 @@ where
|
||||
{
|
||||
type State = SuspendState<Fut::Output>;
|
||||
|
||||
// TODO cancelation if it fires multiple times
|
||||
fn build(self) -> Self::State {
|
||||
let Self { subscriber, inner } = self;
|
||||
|
||||
// create a Future that will be aborted on on_cleanup
|
||||
// this prevents trying to access signals or other resources inside the Suspend, after the
|
||||
// await, if they have already been cleaned up
|
||||
let (abort_handle, abort_registration) = AbortHandle::new_pair();
|
||||
let mut fut = Box::pin(Abortable::new(inner, abort_registration));
|
||||
on_cleanup(move || abort_handle.abort());
|
||||
|
||||
// poll the future once immediately
|
||||
// if it's already available, start in the ready state
|
||||
// otherwise, start with the fallback
|
||||
let mut fut = Box::pin(inner);
|
||||
let initial = fut.as_mut().now_or_never();
|
||||
let initial = fut.as_mut().now_or_never().and_then(Result::ok);
|
||||
let initially_pending = initial.is_none();
|
||||
let inner = Rc::new(RefCell::new(initial.build()));
|
||||
|
||||
@@ -184,7 +192,7 @@ where
|
||||
// spawning immediately means that our now_or_never poll result isn't lost
|
||||
// if it wasn't pending at first, we don't need to poll the Future again
|
||||
if initially_pending {
|
||||
Executor::spawn_local({
|
||||
reactive_graph::spawn_local_scoped({
|
||||
let state = Rc::clone(&inner);
|
||||
async move {
|
||||
let _guard = error_hook.as_ref().map(|hook| {
|
||||
@@ -193,7 +201,10 @@ where
|
||||
|
||||
let value = fut.as_mut().await;
|
||||
drop(id);
|
||||
Some(value).rebuild(&mut *state.borrow_mut());
|
||||
|
||||
if let Ok(value) = value {
|
||||
Some(value).rebuild(&mut *state.borrow_mut());
|
||||
}
|
||||
|
||||
subscriber.forward();
|
||||
}
|
||||
@@ -208,13 +219,19 @@ where
|
||||
fn rebuild(self, state: &mut Self::State) {
|
||||
let Self { subscriber, inner } = self;
|
||||
|
||||
// create a Future that will be aborted on on_cleanup
|
||||
// this prevents trying to access signals or other resources inside the Suspend, after the
|
||||
// await, if they have already been cleaned up
|
||||
let (abort_handle, abort_registration) = AbortHandle::new_pair();
|
||||
let fut = Abortable::new(inner, abort_registration);
|
||||
on_cleanup(move || abort_handle.abort());
|
||||
|
||||
// get a unique ID if there's a SuspenseContext
|
||||
let fut = inner;
|
||||
let id = use_context::<SuspenseContext>().map(|sc| sc.task_id());
|
||||
let error_hook = use_context::<Arc<dyn ErrorHook>>();
|
||||
|
||||
// spawn the future, and rebuild the state when it resolves
|
||||
Executor::spawn_local({
|
||||
reactive_graph::spawn_local_scoped({
|
||||
let state = Rc::clone(&state.inner);
|
||||
async move {
|
||||
let _guard = error_hook
|
||||
@@ -223,11 +240,14 @@ where
|
||||
|
||||
let value = fut.await;
|
||||
drop(id);
|
||||
|
||||
// waiting a tick here allows Suspense to remount if necessary, which prevents some
|
||||
// edge cases in which a rebuild can't happen while unmounted because the DOM node
|
||||
// has no parent
|
||||
any_spawner::Executor::tick().await;
|
||||
Some(value).rebuild(&mut *state.borrow_mut());
|
||||
Executor::tick().await;
|
||||
if let Ok(value) = value {
|
||||
Some(value).rebuild(&mut *state.borrow_mut());
|
||||
}
|
||||
|
||||
subscriber.forward();
|
||||
}
|
||||
@@ -367,11 +387,17 @@ where
|
||||
) -> Self::State {
|
||||
let Self { subscriber, inner } = self;
|
||||
|
||||
// create a Future that will be aborted on on_cleanup
|
||||
// this prevents trying to access signals or other resources inside the Suspend, after the
|
||||
// await, if they have already been cleaned up
|
||||
let (abort_handle, abort_registration) = AbortHandle::new_pair();
|
||||
let mut fut = Box::pin(Abortable::new(inner, abort_registration));
|
||||
on_cleanup(move || abort_handle.abort());
|
||||
|
||||
// poll the future once immediately
|
||||
// if it's already available, start in the ready state
|
||||
// otherwise, start with the fallback
|
||||
let mut fut = Box::pin(inner);
|
||||
let initial = fut.as_mut().now_or_never();
|
||||
let initial = fut.as_mut().now_or_never().and_then(Result::ok);
|
||||
let initially_pending = initial.is_none();
|
||||
let inner = Rc::new(RefCell::new(
|
||||
initial.hydrate::<FROM_SERVER>(cursor, position),
|
||||
@@ -385,7 +411,7 @@ where
|
||||
// spawning immediately means that our now_or_never poll result isn't lost
|
||||
// if it wasn't pending at first, we don't need to poll the Future again
|
||||
if initially_pending {
|
||||
Executor::spawn_local({
|
||||
reactive_graph::spawn_local_scoped({
|
||||
let state = Rc::clone(&inner);
|
||||
async move {
|
||||
let _guard = error_hook.as_ref().map(|hook| {
|
||||
@@ -394,7 +420,10 @@ where
|
||||
|
||||
let value = fut.as_mut().await;
|
||||
drop(id);
|
||||
Some(value).rebuild(&mut *state.borrow_mut());
|
||||
|
||||
if let Ok(value) = value {
|
||||
Some(value).rebuild(&mut *state.borrow_mut());
|
||||
}
|
||||
|
||||
subscriber.forward();
|
||||
}
|
||||
|
||||
@@ -33,8 +33,17 @@ impl Dom {
|
||||
intern(text)
|
||||
}
|
||||
|
||||
pub fn create_element(tag: &str) -> Element {
|
||||
document().create_element(tag).unwrap()
|
||||
pub fn create_element(tag: &str, namespace: Option<&str>) -> Element {
|
||||
if let Some(namespace) = namespace {
|
||||
document()
|
||||
.create_element_ns(
|
||||
Some(Self::intern(namespace)),
|
||||
Self::intern(tag),
|
||||
)
|
||||
.unwrap()
|
||||
} else {
|
||||
document().create_element(Self::intern(tag)).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(level = "trace"))]
|
||||
|
||||
@@ -69,6 +69,7 @@ macro_rules! svg_elements {
|
||||
const TAG: &'static str = stringify!($tag);
|
||||
const SELF_CLOSING: bool = false;
|
||||
const ESCAPE_CHILDREN: bool = true;
|
||||
const NAMESPACE: Option<&'static str> = Some("http://www.w3.org/2000/svg");
|
||||
|
||||
#[inline(always)]
|
||||
fn tag(&self) -> &str {
|
||||
|
||||
Reference in New Issue
Block a user