mirror of
https://github.com/leptos-rs/leptos
synced 2024-11-10 06:44:17 +00:00
begin migrating to leptos
and leptos_dom
packages
This commit is contained in:
parent
0fddfb4823
commit
d726b56b71
16 changed files with 381 additions and 257 deletions
|
@ -39,6 +39,7 @@ rust-version = "1.75"
|
|||
|
||||
[workspace.dependencies]
|
||||
const_str_slice_concat = { path = "./const_str_slice_concat" }
|
||||
hydration_context = { path = "./hydration_context" }
|
||||
leptos = { path = "./leptos", version = "0.6.5" }
|
||||
leptos_config = { path = "./leptos_config", version = "0.6.5" }
|
||||
leptos_dom = { path = "./leptos_dom", version = "0.6.5" }
|
||||
|
|
44
hydration_context/src/csr.rs
Normal file
44
hydration_context/src/csr.rs
Normal file
|
@ -0,0 +1,44 @@
|
|||
use super::{SerializedDataId, SharedContext};
|
||||
use crate::{PinnedFuture, PinnedStream};
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
/// The shared context that should be used in the browser while hydrating.
|
||||
pub struct CsrSharedContext;
|
||||
|
||||
impl SharedContext for CsrSharedContext {
|
||||
#[inline(always)]
|
||||
fn is_browser(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn next_id(&self) -> SerializedDataId {
|
||||
SerializedDataId(0)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn write_async(&self, _id: SerializedDataId, _fut: PinnedFuture<String>) {}
|
||||
|
||||
#[inline(always)]
|
||||
fn read_data(&self, _id: &SerializedDataId) -> Option<String> {
|
||||
None
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn await_data(&self, _id: &SerializedDataId) -> Option<String> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn pending_data(&self) -> Option<PinnedStream<String>> {
|
||||
None
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn get_is_hydrating(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn set_is_hydrating(&self, _is_hydrating: bool) {}
|
||||
}
|
|
@ -45,6 +45,10 @@ impl Debug for HydrateSharedContext {
|
|||
}
|
||||
|
||||
impl SharedContext for HydrateSharedContext {
|
||||
fn is_browser(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn next_id(&self) -> SerializedDataId {
|
||||
let id = self.id.fetch_add(1, Ordering::Relaxed);
|
||||
SerializedDataId(id)
|
||||
|
|
|
@ -11,16 +11,21 @@
|
|||
#![deny(missing_docs)]
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
#[cfg(feature = "browser")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "browser")))]
|
||||
mod csr;
|
||||
#[cfg(feature = "browser")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "browser")))]
|
||||
mod hydrate;
|
||||
mod ssr;
|
||||
#[cfg(feature = "browser")]
|
||||
pub use csr::*;
|
||||
use futures::Stream;
|
||||
#[cfg(feature = "browser")]
|
||||
pub use hydrate::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
pub use ssr::*;
|
||||
use std::{fmt::Debug, future::Future, pin::Pin};
|
||||
use std::{fmt::Debug, future::Future, pin::Pin, sync::OnceLock};
|
||||
|
||||
/// Type alias for a boxed [`Future`].
|
||||
pub type PinnedFuture<T> = Pin<Box<dyn Future<Output = T> + Send + Sync>>;
|
||||
|
@ -39,6 +44,9 @@ pub struct SerializedDataId(usize);
|
|||
|
||||
/// Information that will be shared between the server and the client.
|
||||
pub trait SharedContext: Debug {
|
||||
/// Whether the application is running in the browser.
|
||||
fn is_browser(&self) -> bool;
|
||||
|
||||
/// Returns the next in a series of IDs that is unique to a particular request and response.
|
||||
///
|
||||
/// This should not be used as a global unique ID mechanism. It is specific to the process
|
||||
|
|
|
@ -56,6 +56,10 @@ impl Debug for SsrSharedContext {
|
|||
}
|
||||
|
||||
impl SharedContext for SsrSharedContext {
|
||||
fn is_browser(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn next_id(&self) -> SerializedDataId {
|
||||
let id = self.id.fetch_add(1, Ordering::Relaxed);
|
||||
SerializedDataId(id)
|
||||
|
|
|
@ -18,7 +18,7 @@ leptos_server = { workspace = true }
|
|||
leptos_config = { workspace = true }
|
||||
leptos-spin-macro = { version = "0.2", optional = true }
|
||||
tracing = "0.1"
|
||||
reactive_graph = { workspace = true }
|
||||
reactive_graph = { workspace = true, features = ["wasm-bindgen"] }
|
||||
tachys = { workspace = true, features = ["reactive_graph"] }
|
||||
typed-builder = "0.18"
|
||||
typed-builder-macro = "0.18"
|
||||
|
@ -39,16 +39,13 @@ wasm-bindgen = { version = "0.2", optional = true }
|
|||
|
||||
[features]
|
||||
default = ["serde"]
|
||||
template_macro = ["leptos_dom/web", "dep:wasm-bindgen"]
|
||||
csr = [
|
||||
"leptos_dom/csr",
|
||||
"leptos_macro/csr",
|
||||
"leptos_reactive/csr",
|
||||
"leptos_server/csr",
|
||||
"dep:wasm-bindgen",
|
||||
]
|
||||
hydrate = [
|
||||
"leptos_dom/hydrate",
|
||||
"leptos_macro/hydrate",
|
||||
"leptos_reactive/hydrate",
|
||||
"leptos_server/hydrate",
|
||||
|
@ -57,7 +54,6 @@ hydrate = [
|
|||
default-tls = ["leptos_server/default-tls", "server_fn/default-tls"]
|
||||
rustls = ["leptos_server/rustls", "server_fn/rustls"]
|
||||
ssr = [
|
||||
"leptos_dom/ssr",
|
||||
"leptos_macro/ssr",
|
||||
"leptos_reactive/ssr",
|
||||
"leptos_server/ssr",
|
||||
|
@ -74,7 +70,7 @@ serde = ["leptos_reactive/serde"]
|
|||
serde-lite = ["leptos_reactive/serde-lite"]
|
||||
miniserde = ["leptos_reactive/miniserde"]
|
||||
rkyv = ["leptos_reactive/rkyv", "server_fn/rkyv"]
|
||||
tracing = ["leptos_macro/tracing"]
|
||||
tracing = ["leptos_macro/tracing", "leptos_dom/tracing"]
|
||||
nonce = ["leptos_dom/nonce"]
|
||||
spin = ["leptos_reactive/spin", "leptos-spin-macro"]
|
||||
experimental-islands = [
|
||||
|
|
|
@ -7,7 +7,7 @@ use tachys::{
|
|||
|
||||
pub struct View<T>(T);
|
||||
|
||||
pub trait IntoView: Sized {
|
||||
pub trait IntoView: Sized + Render<Dom> + RenderHtml<Dom> {
|
||||
fn into_view(self) -> View<Self>;
|
||||
}
|
||||
|
||||
|
|
|
@ -165,7 +165,10 @@ pub use typed_builder;
|
|||
pub use typed_builder_macro;
|
||||
mod into_view;
|
||||
pub use into_view::IntoView;
|
||||
pub use tachys;
|
||||
|
||||
mod mount;
|
||||
pub use mount::*;
|
||||
/*mod additional_attributes;
|
||||
pub use additional_attributes::*;
|
||||
mod await_;
|
||||
|
|
85
leptos/src/mount.rs
Normal file
85
leptos/src/mount.rs
Normal file
|
@ -0,0 +1,85 @@
|
|||
use crate::IntoView;
|
||||
use reactive_graph::{executor::Executor, owner::Owner};
|
||||
use std::marker::PhantomData;
|
||||
use tachys::{
|
||||
dom::body,
|
||||
renderer::{dom::Dom, Renderer},
|
||||
view::{Mountable, Render},
|
||||
};
|
||||
use web_sys::HtmlElement;
|
||||
|
||||
/// Runs the provided closure and mounts the result to the `<body>`.
|
||||
pub fn mount_to_body<F, N>(f: F)
|
||||
where
|
||||
F: FnOnce() -> N + 'static,
|
||||
N: IntoView,
|
||||
{
|
||||
let owner = mount_to(body(), f);
|
||||
owner.forget();
|
||||
}
|
||||
|
||||
/// Runs the provided closure and mounts the result to the provided element.
|
||||
pub fn mount_to<F, N>(parent: HtmlElement, f: F) -> UnmountHandle<N::State, Dom>
|
||||
where
|
||||
F: FnOnce() -> N + 'static,
|
||||
N: IntoView,
|
||||
{
|
||||
// use wasm-bindgen-futures to drive the reactive system
|
||||
Executor::init_wasm_bindgen();
|
||||
|
||||
// create a new reactive owner and use it as the root node to run the app
|
||||
let owner = Owner::new();
|
||||
let mountable = owner.with(move || {
|
||||
let view = f().into_view();
|
||||
let mut mountable = view.build();
|
||||
mountable.mount(&parent, None);
|
||||
mountable
|
||||
});
|
||||
|
||||
// returns a handle that owns the owner
|
||||
// when this is dropped, it will clean up the reactive system and unmount the view
|
||||
UnmountHandle {
|
||||
owner,
|
||||
mountable,
|
||||
rndr: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// On drop, this will clean up the reactive [`Owner`] and unmount the view created by
|
||||
/// [`mount_to`].
|
||||
///
|
||||
/// If you are using it to create the root of an application, you should use
|
||||
/// [`UnmountHandle::forget`] to leak it.
|
||||
#[must_use]
|
||||
pub struct UnmountHandle<M, R>
|
||||
where
|
||||
M: Mountable<R>,
|
||||
R: Renderer,
|
||||
{
|
||||
owner: Owner,
|
||||
mountable: M,
|
||||
rndr: PhantomData<R>,
|
||||
}
|
||||
|
||||
impl<M, R> UnmountHandle<M, R>
|
||||
where
|
||||
M: Mountable<R>,
|
||||
R: Renderer,
|
||||
{
|
||||
/// Leaks the handle, preventing the reactive system from being cleaned up and the view from
|
||||
/// being unmounted. This should always be called when [`mount_to`] is used for the root of an
|
||||
/// application that should live for the long term.
|
||||
pub fn forget(self) {
|
||||
std::mem::forget(self);
|
||||
}
|
||||
}
|
||||
|
||||
impl<M, R> Drop for UnmountHandle<M, R>
|
||||
where
|
||||
M: Mountable<R>,
|
||||
R: Renderer,
|
||||
{
|
||||
fn drop(&mut self) {
|
||||
self.mountable.unmount();
|
||||
}
|
||||
}
|
|
@ -11,149 +11,30 @@ rust-version.workspace = true
|
|||
[dependencies]
|
||||
tachys = { workspace = true }
|
||||
reactive_graph = { workspace = true }
|
||||
hydration_context = { workspace = true }
|
||||
or_poisoned = { workspace = true }
|
||||
base64 = { version = "0.21", optional = true }
|
||||
getrandom = { version = "0.2", optional = true }
|
||||
js-sys = "0.3"
|
||||
rand = { version = "0.8", optional = true }
|
||||
tracing = "0.1"
|
||||
wasm-bindgen = "0.2"
|
||||
|
||||
[dev-dependencies]
|
||||
leptos = { path = "../leptos" }
|
||||
|
||||
[dependencies.web-sys]
|
||||
version = "0.3"
|
||||
features = [
|
||||
"DocumentFragment",
|
||||
"Element",
|
||||
"HtmlTemplateElement",
|
||||
"NodeList",
|
||||
"Window",
|
||||
"console",
|
||||
"Comment",
|
||||
"Document",
|
||||
"DomTokenList",
|
||||
"CssStyleDeclaration",
|
||||
"Location",
|
||||
"Range",
|
||||
"Text",
|
||||
"HtmlCollection",
|
||||
"ShadowRoot",
|
||||
"TreeWalker",
|
||||
|
||||
# Events we cast to in leptos_macro -- added here so we don't force users to import them
|
||||
"AddEventListenerOptions",
|
||||
"AnimationEvent",
|
||||
"BeforeUnloadEvent",
|
||||
"ClipboardEvent",
|
||||
"CompositionEvent",
|
||||
"CustomEvent",
|
||||
"DeviceMotionEvent",
|
||||
"DeviceOrientationEvent",
|
||||
"DomStringMap",
|
||||
"DragEvent",
|
||||
"ErrorEvent",
|
||||
"Event",
|
||||
"FocusEvent",
|
||||
"GamepadEvent",
|
||||
"HashChangeEvent",
|
||||
"InputEvent",
|
||||
"KeyboardEvent",
|
||||
"MessageEvent",
|
||||
"MouseEvent",
|
||||
"PageTransitionEvent",
|
||||
"PointerEvent",
|
||||
"PopStateEvent",
|
||||
"ProgressEvent",
|
||||
"PromiseRejectionEvent",
|
||||
"SecurityPolicyViolationEvent",
|
||||
"StorageEvent",
|
||||
"SubmitEvent",
|
||||
"TouchEvent",
|
||||
"TransitionEvent",
|
||||
"UiEvent",
|
||||
"WheelEvent",
|
||||
|
||||
# HTML Element Types
|
||||
"HtmlHtmlElement",
|
||||
"HtmlBaseElement",
|
||||
"HtmlHeadElement",
|
||||
"HtmlLinkElement",
|
||||
"HtmlMetaElement",
|
||||
"HtmlStyleElement",
|
||||
"HtmlTitleElement",
|
||||
"HtmlBodyElement",
|
||||
"HtmlHeadingElement",
|
||||
"HtmlQuoteElement",
|
||||
"HtmlDivElement",
|
||||
"HtmlDListElement",
|
||||
"HtmlHrElement",
|
||||
"HtmlLiElement",
|
||||
"HtmlOListElement",
|
||||
"HtmlParagraphElement",
|
||||
"HtmlPreElement",
|
||||
"HtmlUListElement",
|
||||
"HtmlAnchorElement",
|
||||
"HtmlBrElement",
|
||||
"HtmlDataElement",
|
||||
"HtmlQuoteElement",
|
||||
"HtmlSpanElement",
|
||||
"HtmlTimeElement",
|
||||
"HtmlAreaElement",
|
||||
"HtmlAudioElement",
|
||||
"HtmlImageElement",
|
||||
"HtmlMapElement",
|
||||
"HtmlTrackElement",
|
||||
"HtmlVideoElement",
|
||||
"HtmlEmbedElement",
|
||||
"HtmlIFrameElement",
|
||||
"HtmlObjectElement",
|
||||
"HtmlParamElement",
|
||||
"HtmlPictureElement",
|
||||
"HtmlSourceElement",
|
||||
"SvgElement",
|
||||
"HtmlCanvasElement",
|
||||
"HtmlScriptElement",
|
||||
"HtmlModElement",
|
||||
"HtmlTableCaptionElement",
|
||||
"HtmlTableColElement",
|
||||
"HtmlTableColElement",
|
||||
"HtmlTableElement",
|
||||
"HtmlTableSectionElement",
|
||||
"HtmlTableCellElement",
|
||||
"HtmlTableSectionElement",
|
||||
"HtmlTableCellElement",
|
||||
"HtmlTableSectionElement",
|
||||
"HtmlTableRowElement",
|
||||
"HtmlButtonElement",
|
||||
"HtmlDataListElement",
|
||||
"HtmlFieldSetElement",
|
||||
"HtmlFormElement",
|
||||
"HtmlInputElement",
|
||||
"HtmlLabelElement",
|
||||
"HtmlLegendElement",
|
||||
"HtmlMeterElement",
|
||||
"HtmlOptGroupElement",
|
||||
"HtmlOutputElement",
|
||||
"HtmlProgressElement",
|
||||
"HtmlSelectElement",
|
||||
"HtmlTextAreaElement",
|
||||
"HtmlDetailsElement",
|
||||
"HtmlDialogElement",
|
||||
"HtmlMenuElement",
|
||||
"HtmlSlotElement",
|
||||
"HtmlTemplateElement",
|
||||
"HtmlOptionElement",
|
||||
]
|
||||
features = ["Location"]
|
||||
|
||||
[features]
|
||||
default = []
|
||||
web = []
|
||||
csr = ["leptos_reactive/csr", "web"]
|
||||
hydrate = ["leptos_reactive/hydrate", "web"]
|
||||
ssr = ["leptos_reactive/ssr"]
|
||||
nightly = ["leptos_reactive/nightly"]
|
||||
nightly = ["reactive_graph/nightly"]
|
||||
# TODO implement nonces
|
||||
nonce = ["dep:base64", "dep:getrandom", "dep:rand"]
|
||||
experimental-islands = ["leptos_reactive/experimental-islands"]
|
||||
trace-component-props = []
|
||||
|
||||
[package.metadata.cargo-all-features]
|
||||
denylist = ["nightly", "trace-component-props"]
|
||||
skip_feature_sets = [["web", "ssr"]]
|
||||
experimental-islands = []
|
||||
trace-component-props = ["tracing"]
|
||||
tracing = []
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
rustdoc-args = ["--generate-link-to-definition"]
|
||||
|
|
|
@ -1,10 +1,37 @@
|
|||
//! A variety of DOM utility functions.
|
||||
|
||||
use crate::{events::typed as ev, is_server, window};
|
||||
use leptos_reactive::on_cleanup;
|
||||
use or_poisoned::OrPoisoned;
|
||||
#[cfg(debug_assertions)]
|
||||
use reactive_graph::diagnostics::SpecialNonReactiveZone;
|
||||
use reactive_graph::owner::Owner;
|
||||
use std::time::Duration;
|
||||
use tachys::html::event::EventDescriptor;
|
||||
#[cfg(feature = "tracing")]
|
||||
use tracing::instrument;
|
||||
use wasm_bindgen::{prelude::Closure, JsCast, JsValue, UnwrapThrowExt};
|
||||
|
||||
thread_local! {
|
||||
pub(crate) static WINDOW: web_sys::Window = web_sys::window().unwrap_throw();
|
||||
|
||||
pub(crate) static DOCUMENT: web_sys::Document = web_sys::window().unwrap_throw().document().unwrap_throw();
|
||||
}
|
||||
|
||||
/// Returns the [`Window`](https://developer.mozilla.org/en-US/docs/Web/API/Window).
|
||||
///
|
||||
/// This is cached as a thread-local variable, so calling `window()` multiple times
|
||||
/// requires only one call out to JavaScript.
|
||||
pub fn window() -> web_sys::Window {
|
||||
WINDOW.with(Clone::clone)
|
||||
}
|
||||
|
||||
/// Returns the [`Document`](https://developer.mozilla.org/en-US/docs/Web/API/Document).
|
||||
///
|
||||
/// This is cached as a thread-local variable, so calling `document()` multiple times
|
||||
/// requires only one call out to JavaScript.
|
||||
pub fn document() -> web_sys::Document {
|
||||
DOCUMENT.with(Clone::clone)
|
||||
}
|
||||
|
||||
/// Sets a property on a DOM element.
|
||||
pub fn set_property(
|
||||
el: &web_sys::Element,
|
||||
|
@ -36,17 +63,18 @@ pub fn location() -> web_sys::Location {
|
|||
/// Current [`window.location.hash`](https://developer.mozilla.org/en-US/docs/Web/API/Window/location)
|
||||
/// without the beginning #.
|
||||
pub fn location_hash() -> Option<String> {
|
||||
if is_server() {
|
||||
// TODO use shared context for is_server
|
||||
/*if is_server() {
|
||||
None
|
||||
} else {
|
||||
location()
|
||||
.hash()
|
||||
.ok()
|
||||
.map(|hash| match hash.chars().next() {
|
||||
Some('#') => hash[1..].to_string(),
|
||||
_ => hash,
|
||||
})
|
||||
}
|
||||
} else {*/
|
||||
location()
|
||||
.hash()
|
||||
.ok()
|
||||
.map(|hash| match hash.chars().next() {
|
||||
Some('#') => hash[1..].to_string(),
|
||||
_ => hash,
|
||||
})
|
||||
//}
|
||||
}
|
||||
|
||||
/// Current [`window.location.pathname`](https://developer.mozilla.org/en-US/docs/Web/API/Window/location).
|
||||
|
@ -103,7 +131,7 @@ impl AnimationFrameRequestHandle {
|
|||
|
||||
/// Runs the given function between the next repaint using
|
||||
/// [`Window.requestAnimationFrame`](https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame).
|
||||
#[cfg_attr(debug_assertions, instrument(level = "trace", skip_all))]
|
||||
#[cfg_attr(feature = "tracing", instrument(level = "trace", skip_all))]
|
||||
#[inline(always)]
|
||||
pub fn request_animation_frame(cb: impl FnOnce() + 'static) {
|
||||
_ = request_animation_frame_with_handle(cb);
|
||||
|
@ -131,20 +159,18 @@ fn closure_once(cb: impl FnOnce() + 'static) -> JsValue {
|
|||
/// Runs the given function between the next repaint using
|
||||
/// [`Window.requestAnimationFrame`](https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame),
|
||||
/// returning a cancelable handle.
|
||||
#[cfg_attr(debug_assertions, instrument(level = "trace", skip_all))]
|
||||
#[cfg_attr(feature = "tracing", instrument(level = "trace", skip_all))]
|
||||
#[inline(always)]
|
||||
pub fn request_animation_frame_with_handle(
|
||||
cb: impl FnOnce() + 'static,
|
||||
) -> Result<AnimationFrameRequestHandle, JsValue> {
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(debug_assertions)] {
|
||||
let span = ::tracing::Span::current();
|
||||
let cb = move || {
|
||||
let _guard = span.enter();
|
||||
cb();
|
||||
};
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "tracing")]
|
||||
let span = ::tracing::Span::current();
|
||||
#[cfg(feature = "tracing")]
|
||||
let cb = move || {
|
||||
let _guard = span.enter();
|
||||
cb();
|
||||
};
|
||||
|
||||
#[inline(never)]
|
||||
fn raf(cb: JsValue) -> Result<AnimationFrameRequestHandle, JsValue> {
|
||||
|
@ -171,7 +197,7 @@ impl IdleCallbackHandle {
|
|||
|
||||
/// Queues the given function during an idle period using
|
||||
/// [`Window.requestIdleCallback`](https://developer.mozilla.org/en-US/docs/Web/API/window/requestIdleCallback).
|
||||
#[cfg_attr(debug_assertions, instrument(level = "trace", skip_all))]
|
||||
#[cfg_attr(feature = "tracing", instrument(level = "trace", skip_all))]
|
||||
#[inline(always)]
|
||||
pub fn request_idle_callback(cb: impl Fn() + 'static) {
|
||||
_ = request_idle_callback_with_handle(cb);
|
||||
|
@ -180,19 +206,18 @@ pub fn request_idle_callback(cb: impl Fn() + 'static) {
|
|||
/// Queues the given function during an idle period using
|
||||
/// [`Window.requestIdleCallback`](https://developer.mozilla.org/en-US/docs/Web/API/window/requestIdleCallback),
|
||||
/// returning a cancelable handle.
|
||||
#[cfg_attr(debug_assertions, instrument(level = "trace", skip_all))]
|
||||
#[cfg_attr(feature = "tracing", instrument(level = "trace", skip_all))]
|
||||
#[inline(always)]
|
||||
pub fn request_idle_callback_with_handle(
|
||||
cb: impl Fn() + 'static,
|
||||
) -> Result<IdleCallbackHandle, JsValue> {
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(debug_assertions)] {
|
||||
#[cfg(feature = "tracing")]
|
||||
{
|
||||
let span = ::tracing::Span::current();
|
||||
let cb = move || {
|
||||
let _guard = span.enter();
|
||||
cb();
|
||||
let _guard = span.enter();
|
||||
cb();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(never)]
|
||||
|
@ -222,7 +247,7 @@ impl TimeoutHandle {
|
|||
/// Executes the given function after the given duration of time has passed.
|
||||
/// [`setTimeout()`](https://developer.mozilla.org/en-US/docs/Web/API/setTimeout).
|
||||
#[cfg_attr(
|
||||
any(debug_assertions, feature = "ssr"),
|
||||
any(feature = "tracing", feature = "ssr"),
|
||||
instrument(level = "trace", skip_all, fields(duration = ?duration))
|
||||
)]
|
||||
pub fn set_timeout(cb: impl FnOnce() + 'static, duration: Duration) {
|
||||
|
@ -232,7 +257,7 @@ pub fn set_timeout(cb: impl FnOnce() + 'static, duration: Duration) {
|
|||
/// Executes the given function after the given duration of time has passed, returning a cancelable handle.
|
||||
/// [`setTimeout()`](https://developer.mozilla.org/en-US/docs/Web/API/setTimeout).
|
||||
#[cfg_attr(
|
||||
any(debug_assertions, feature = "ssr"),
|
||||
any(feature = "tracing", feature = "ssr"),
|
||||
instrument(level = "trace", skip_all, fields(duration = ?duration))
|
||||
)]
|
||||
#[inline(always)]
|
||||
|
@ -240,17 +265,19 @@ pub fn set_timeout_with_handle(
|
|||
cb: impl FnOnce() + 'static,
|
||||
duration: Duration,
|
||||
) -> Result<TimeoutHandle, JsValue> {
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(debug_assertions)] {
|
||||
let span = ::tracing::Span::current();
|
||||
let cb = move || {
|
||||
let prev = leptos_reactive::SpecialNonReactiveZone::enter();
|
||||
let _guard = span.enter();
|
||||
cb();
|
||||
leptos_reactive::SpecialNonReactiveZone::exit(prev);
|
||||
};
|
||||
}
|
||||
}
|
||||
#[cfg(debug_assertions)]
|
||||
let cb = || {
|
||||
let _z = SpecialNonReactiveZone::enter();
|
||||
cb();
|
||||
};
|
||||
|
||||
#[cfg(feature = "tracing")]
|
||||
let span = ::tracing::Span::current();
|
||||
#[cfg(feature = "tracing")]
|
||||
let cb = move || {
|
||||
let _guard = span.enter();
|
||||
cb();
|
||||
};
|
||||
|
||||
#[inline(never)]
|
||||
fn st(cb: JsValue, duration: Duration) -> Result<TimeoutHandle, JsValue> {
|
||||
|
@ -287,53 +314,53 @@ pub fn set_timeout_with_handle(
|
|||
/// ```
|
||||
pub fn debounce<T: 'static>(
|
||||
delay: Duration,
|
||||
#[cfg(debug_assertions)] mut cb: impl FnMut(T) + 'static,
|
||||
#[cfg(not(debug_assertions))] cb: impl FnMut(T) + 'static,
|
||||
mut cb: impl FnMut(T) + 'static,
|
||||
) -> impl FnMut(T) {
|
||||
use std::{
|
||||
cell::{Cell, RefCell},
|
||||
rc::Rc,
|
||||
use std::sync::{Arc, RwLock};
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
#[allow(unused_mut)]
|
||||
let mut cb = move |value| {
|
||||
let _z = SpecialNonReactiveZone::enter();
|
||||
cb(value);
|
||||
};
|
||||
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(debug_assertions)] {
|
||||
let span = ::tracing::Span::current();
|
||||
let cb = move |value| {
|
||||
let prev = leptos_reactive::SpecialNonReactiveZone::enter();
|
||||
let _guard = span.enter();
|
||||
cb(value);
|
||||
leptos_reactive::SpecialNonReactiveZone::exit(prev);
|
||||
};
|
||||
}
|
||||
}
|
||||
let cb = Rc::new(RefCell::new(cb));
|
||||
#[cfg(feature = "tracing")]
|
||||
let span = ::tracing::Span::current();
|
||||
#[cfg(feature = "tracing")]
|
||||
#[allow(unused_mut)]
|
||||
let mut cb = move |value| {
|
||||
let _guard = span.enter();
|
||||
cb(value);
|
||||
};
|
||||
|
||||
let timer = Rc::new(Cell::new(None::<TimeoutHandle>));
|
||||
let cb = Arc::new(RwLock::new(cb));
|
||||
let timer = Arc::new(RwLock::new(None::<TimeoutHandle>));
|
||||
|
||||
on_cleanup({
|
||||
let timer = Rc::clone(&timer);
|
||||
Owner::on_cleanup({
|
||||
let timer = Arc::clone(&timer);
|
||||
move || {
|
||||
if let Some(timer) = timer.take() {
|
||||
if let Some(timer) = timer.write().or_poisoned().take() {
|
||||
timer.clear();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
move |arg| {
|
||||
if let Some(timer) = timer.take() {
|
||||
if let Some(timer) = timer.write().unwrap().take() {
|
||||
timer.clear();
|
||||
}
|
||||
let handle = set_timeout_with_handle(
|
||||
{
|
||||
let cb = Rc::clone(&cb);
|
||||
let cb = Arc::clone(&cb);
|
||||
move || {
|
||||
cb.borrow_mut()(arg);
|
||||
cb.write().unwrap()(arg);
|
||||
}
|
||||
},
|
||||
delay,
|
||||
);
|
||||
if let Ok(handle) = handle {
|
||||
timer.set(Some(handle));
|
||||
*timer.write().or_poisoned() = Some(handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -354,7 +381,7 @@ impl IntervalHandle {
|
|||
/// returning a cancelable handle.
|
||||
/// See [`setInterval()`](https://developer.mozilla.org/en-US/docs/Web/API/setInterval).
|
||||
#[cfg_attr(
|
||||
any(debug_assertions, feature = "ssr"),
|
||||
any(feature = "tracing", feature = "ssr"),
|
||||
instrument(level = "trace", skip_all, fields(duration = ?duration))
|
||||
)]
|
||||
pub fn set_interval(cb: impl Fn() + 'static, duration: Duration) {
|
||||
|
@ -365,7 +392,7 @@ pub fn set_interval(cb: impl Fn() + 'static, duration: Duration) {
|
|||
/// returning a cancelable handle.
|
||||
/// See [`setInterval()`](https://developer.mozilla.org/en-US/docs/Web/API/setInterval).
|
||||
#[cfg_attr(
|
||||
any(debug_assertions, feature = "ssr"),
|
||||
any(feature = "tracing", feature = "ssr"),
|
||||
instrument(level = "trace", skip_all, fields(duration = ?duration))
|
||||
)]
|
||||
#[inline(always)]
|
||||
|
@ -373,17 +400,18 @@ pub fn set_interval_with_handle(
|
|||
cb: impl Fn() + 'static,
|
||||
duration: Duration,
|
||||
) -> Result<IntervalHandle, JsValue> {
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(debug_assertions)] {
|
||||
let span = ::tracing::Span::current();
|
||||
let cb = move || {
|
||||
let prev = leptos_reactive::SpecialNonReactiveZone::enter();
|
||||
let _guard = span.enter();
|
||||
cb();
|
||||
leptos_reactive::SpecialNonReactiveZone::exit(prev);
|
||||
};
|
||||
}
|
||||
}
|
||||
#[cfg(debug_assertions)]
|
||||
let cb = move || {
|
||||
let _z = SpecialNonReactiveZone::enter();
|
||||
cb();
|
||||
};
|
||||
#[cfg(feature = "tracing")]
|
||||
let span = ::tracing::Span::current();
|
||||
#[cfg(feature = "tracing")]
|
||||
let cb = move || {
|
||||
let _guard = span.enter();
|
||||
cb();
|
||||
};
|
||||
|
||||
#[inline(never)]
|
||||
fn si(
|
||||
|
@ -406,7 +434,7 @@ pub fn set_interval_with_handle(
|
|||
/// Adds an event listener to the `Window`, typed as a generic `Event`,
|
||||
/// returning a cancelable handle.
|
||||
#[cfg_attr(
|
||||
debug_assertions,
|
||||
feature = "tracing",
|
||||
instrument(level = "trace", skip_all, fields(event_name = %event_name))
|
||||
)]
|
||||
#[inline(always)]
|
||||
|
@ -414,19 +442,22 @@ pub fn window_event_listener_untyped(
|
|||
event_name: &str,
|
||||
cb: impl Fn(web_sys::Event) + 'static,
|
||||
) -> WindowListenerHandle {
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(debug_assertions)] {
|
||||
let span = ::tracing::Span::current();
|
||||
let cb = move |e| {
|
||||
let prev = leptos_reactive::SpecialNonReactiveZone::enter();
|
||||
let _guard = span.enter();
|
||||
cb(e);
|
||||
leptos_reactive::SpecialNonReactiveZone::exit(prev);
|
||||
};
|
||||
}
|
||||
}
|
||||
#[cfg(debug_assertions)]
|
||||
let cb = move |e| {
|
||||
let _z = SpecialNonReactiveZone::enter();
|
||||
cb(e);
|
||||
};
|
||||
#[cfg(feature = "tracing")]
|
||||
let span = ::tracing::Span::current();
|
||||
#[cfg(feature = "tracing")]
|
||||
let cb = move |e| {
|
||||
let _guard = span.enter();
|
||||
cb(e);
|
||||
};
|
||||
|
||||
if !is_server() {
|
||||
// TODO use shared context for is_server
|
||||
if true {
|
||||
// !is_server() {
|
||||
#[inline(never)]
|
||||
fn wel(
|
||||
cb: Box<dyn FnMut(web_sys::Event)>,
|
||||
|
@ -468,7 +499,7 @@ pub fn window_event_listener_untyped(
|
|||
/// on_cleanup(move || handle.remove());
|
||||
/// }
|
||||
/// ```
|
||||
pub fn window_event_listener<E: ev::EventDescriptor + 'static>(
|
||||
pub fn window_event_listener<E: EventDescriptor + 'static>(
|
||||
event: E,
|
||||
cb: impl Fn(E::EventType) + 'static,
|
||||
) -> WindowListenerHandle
|
||||
|
@ -495,13 +526,3 @@ impl WindowListenerHandle {
|
|||
(self.0)()
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
/// This exists only to enable type inference on event listeners when in SSR mode.
|
||||
pub fn ssr_event_listener<E: crate::ev::EventDescriptor + 'static>(
|
||||
event: E,
|
||||
event_handler: impl FnMut(E::EventType) + 'static,
|
||||
) {
|
||||
_ = event;
|
||||
_ = event_handler;
|
||||
}
|
||||
|
|
|
@ -1,16 +1,18 @@
|
|||
#![deny(missing_docs)]
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
pub use tachys::*;
|
||||
use web_sys::HtmlElement;
|
||||
//! The DOM implementation for `leptos`.
|
||||
|
||||
use reactive_graph::owner::Owner;
|
||||
use tachys::{
|
||||
dom::body,
|
||||
renderer::dom::Dom,
|
||||
view::{Mountable, Render},
|
||||
};
|
||||
use web_sys::HtmlElement;
|
||||
pub mod helpers;
|
||||
pub use tachys::html::event as events;
|
||||
|
||||
pub fn mount_to<F, N>(parent: HtmlElement, f: F)
|
||||
where
|
||||
F: FnOnce() -> N + 'static,
|
||||
N: IntoView,
|
||||
{
|
||||
mount_to_with_stop_hydrating(parent, true, f)
|
||||
}
|
||||
|
||||
/*#![cfg_attr(feature = "nightly", feature(fn_traits))]
|
||||
#![cfg_attr(feature = "nightly", feature(unboxed_closures))]
|
||||
|
|
|
@ -7,13 +7,13 @@ version.workspace = true
|
|||
or_poisoned = { workspace = true }
|
||||
futures = "0.3"
|
||||
glib = { version = "0.19", optional = true }
|
||||
pin-project-lite = "0.2.13"
|
||||
pin-project-lite = "0.2"
|
||||
rustc-hash = "1.1.0"
|
||||
slotmap = "1"
|
||||
thiserror = "1"
|
||||
tokio = { version = "1", optional = true, default-features = false, features = ["rt"] }
|
||||
tracing = { version = "0.1", optional = true }
|
||||
wasm-bindgen-futures = { version = "0.4.40", optional = true }
|
||||
wasm-bindgen-futures = { version = "0.4", optional = true }
|
||||
guardian = "1"
|
||||
|
||||
[dev-dependencies]
|
||||
|
|
73
reactive_graph/src/diagnostics.rs
Normal file
73
reactive_graph/src/diagnostics.rs
Normal file
|
@ -0,0 +1,73 @@
|
|||
// The point of these diagnostics is to give useful error messages when someone
|
||||
// tries to access a reactive variable outside the reactive scope. They track when
|
||||
// you create a signal/memo, and where you access it non-reactively.
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
#[allow(dead_code)] // allowed for SSR
|
||||
#[derive(Copy, Clone)]
|
||||
pub(crate) struct AccessDiagnostics {
|
||||
pub defined_at: &'static std::panic::Location<'static>,
|
||||
pub called_at: &'static std::panic::Location<'static>,
|
||||
}
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
#[derive(Copy, Clone, Default)]
|
||||
pub(crate) struct AccessDiagnostics;
|
||||
|
||||
/// This just tracks whether we're currently in a context in which it really doesn't
|
||||
/// matter whether something is reactive: for example, in an event listener or timeout.
|
||||
/// Entering this zone basically turns off the warnings, and exiting it turns them back on.
|
||||
/// All of this is a no-op in release mode.
|
||||
#[doc(hidden)]
|
||||
#[derive(Debug)]
|
||||
pub struct SpecialNonReactiveZone;
|
||||
|
||||
/// Exits the "special non-reactive zone" when dropped.
|
||||
#[derive(Debug)]
|
||||
pub struct SpecialNonReactiveZoneGuard;
|
||||
|
||||
use std::cell::Cell;
|
||||
|
||||
thread_local! {
|
||||
static IS_SPECIAL_ZONE: Cell<bool> = const { Cell::new(false) };
|
||||
}
|
||||
|
||||
impl SpecialNonReactiveZone {
|
||||
#[inline(always)]
|
||||
pub(crate) fn is_inside() -> bool {
|
||||
if cfg!(debug_assertions) {
|
||||
IS_SPECIAL_ZONE.get()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn enter() -> SpecialNonReactiveZoneGuard {
|
||||
IS_SPECIAL_ZONE.set(true);
|
||||
SpecialNonReactiveZoneGuard
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for SpecialNonReactiveZoneGuard {
|
||||
fn drop(&mut self) {
|
||||
IS_SPECIAL_ZONE.set(false);
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[macro_export]
|
||||
macro_rules! diagnostics {
|
||||
($this:ident) => {{
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
AccessDiagnostics {
|
||||
defined_at: $this.defined_at,
|
||||
called_at: std::panic::Location::caller(),
|
||||
}
|
||||
}
|
||||
#[cfg(not(debug_assertions))]
|
||||
{
|
||||
AccessDiagnostics
|
||||
}
|
||||
}};
|
||||
}
|
|
@ -74,6 +74,7 @@ use std::{future::Future, pin::Pin};
|
|||
|
||||
pub(crate) mod channel;
|
||||
pub mod computed;
|
||||
pub mod diagnostics;
|
||||
pub mod effect;
|
||||
pub mod executor;
|
||||
pub mod graph;
|
||||
|
|
|
@ -15,6 +15,7 @@ pub use arena::{Stored, StoredData};
|
|||
pub use context::*;
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
#[must_use]
|
||||
pub struct Owner {
|
||||
pub(crate) inner: Arc<RwLock<OwnerInner>>,
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue