begin migrating to `leptos` and `leptos_dom` packages

This commit is contained in:
Greg Johnston 2024-02-10 17:17:45 -05:00
parent 0fddfb4823
commit d726b56b71
16 changed files with 381 additions and 257 deletions

View File

@ -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" }

View 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) {}
}

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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 = [

View File

@ -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>;
}

View File

@ -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
View 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();
}
}

View File

@ -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"]

View File

@ -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,9 +63,10 @@ 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 {
} else {*/
location()
.hash()
.ok()
@ -46,7 +74,7 @@ pub fn location_hash() -> Option<String> {
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)] {
#[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,20 +206,19 @@ 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();
};
}
}
#[inline(never)]
fn ric(cb: Box<dyn Fn()>) -> Result<IdleCallbackHandle, JsValue> {
@ -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)] {
#[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 prev = leptos_reactive::SpecialNonReactiveZone::enter();
let _guard = span.enter();
cb();
leptos_reactive::SpecialNonReactiveZone::exit(prev);
};
}
}
#[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)] {
#[cfg(feature = "tracing")]
let span = ::tracing::Span::current();
let cb = move |value| {
let prev = leptos_reactive::SpecialNonReactiveZone::enter();
#[cfg(feature = "tracing")]
#[allow(unused_mut)]
let mut cb = move |value| {
let _guard = span.enter();
cb(value);
leptos_reactive::SpecialNonReactiveZone::exit(prev);
};
}
}
let cb = Rc::new(RefCell::new(cb));
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();
#[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 prev = leptos_reactive::SpecialNonReactiveZone::enter();
let _guard = span.enter();
cb();
leptos_reactive::SpecialNonReactiveZone::exit(prev);
};
}
}
#[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();
#[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 prev = leptos_reactive::SpecialNonReactiveZone::enter();
let _guard = span.enter();
cb(e);
leptos_reactive::SpecialNonReactiveZone::exit(prev);
};
}
}
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;
}

View File

@ -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))]

View File

@ -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]

View 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
}
}};
}

View File

@ -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;

View File

@ -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>>,
}