mirror of
https://github.com/leptos-rs/leptos
synced 2024-11-10 14:54:16 +00:00
Merge pull request #831 from novacrazy/main
Various optimizations, size reductions and stability improvements
This commit is contained in:
commit
4e1f963750
47 changed files with 695 additions and 356 deletions
|
@ -10,8 +10,8 @@ fn leptos_deep_creation(b: &mut Bencher) {
|
|||
create_scope(runtime, |cx| {
|
||||
let signal = create_rw_signal(cx, 0);
|
||||
let mut memos = Vec::<Memo<usize>>::new();
|
||||
for i in 0..1000usize {
|
||||
let prev = memos.get(i.saturating_sub(1)).copied();
|
||||
for _ in 0..1000usize {
|
||||
let prev = memos.last().copied();
|
||||
if let Some(prev) = prev {
|
||||
memos.push(create_memo(cx, move |_| prev.get() + 1));
|
||||
} else {
|
||||
|
@ -34,9 +34,8 @@ fn leptos_deep_update(b: &mut Bencher) {
|
|||
create_scope(runtime, |cx| {
|
||||
let signal = create_rw_signal(cx, 0);
|
||||
let mut memos = Vec::<Memo<usize>>::new();
|
||||
for i in 0..1000usize {
|
||||
let prev = memos.get(i.saturating_sub(1)).copied();
|
||||
if let Some(prev) = prev {
|
||||
for _ in 0..1000usize {
|
||||
if let Some(prev) = memos.last().copied() {
|
||||
memos.push(create_memo(cx, move |_| prev.get() + 1));
|
||||
} else {
|
||||
memos.push(create_memo(cx, move |_| signal.get() + 1));
|
||||
|
@ -242,9 +241,8 @@ fn l021_deep_creation(b: &mut Bencher) {
|
|||
create_scope(runtime, |cx| {
|
||||
let signal = create_rw_signal(cx, 0);
|
||||
let mut memos = Vec::<Memo<usize>>::new();
|
||||
for i in 0..1000usize {
|
||||
let prev = memos.get(i.saturating_sub(1)).copied();
|
||||
if let Some(prev) = prev {
|
||||
for _ in 0..1000usize {
|
||||
if let Some(prev) = memos.last().copied() {
|
||||
memos.push(create_memo(cx, move |_| prev.get() + 1));
|
||||
} else {
|
||||
memos.push(create_memo(cx, move |_| signal.get() + 1));
|
||||
|
@ -266,9 +264,8 @@ fn l021_deep_update(b: &mut Bencher) {
|
|||
create_scope(runtime, |cx| {
|
||||
let signal = create_rw_signal(cx, 0);
|
||||
let mut memos = Vec::<Memo<usize>>::new();
|
||||
for i in 0..1000usize {
|
||||
let prev = memos.get(i.saturating_sub(1)).copied();
|
||||
if let Some(prev) = prev {
|
||||
for _ in 0..1000usize {
|
||||
if let Some(prev) = memos.last().copied() {
|
||||
memos.push(create_memo(cx, move |_| prev.get() + 1));
|
||||
} else {
|
||||
memos.push(create_memo(cx, move |_| signal.get() + 1));
|
||||
|
@ -444,9 +441,8 @@ fn sycamore_deep_creation(b: &mut Bencher) {
|
|||
let d = create_scope(|cx| {
|
||||
let signal = create_signal(cx, 0);
|
||||
let mut memos = Vec::<&ReadSignal<usize>>::new();
|
||||
for i in 0..1000usize {
|
||||
let prev = memos.get(i.saturating_sub(1)).copied();
|
||||
if let Some(prev) = prev {
|
||||
for _ in 0..1000usize {
|
||||
if let Some(prev) = memos.last().copied() {
|
||||
memos.push(create_memo(cx, move || *prev.get() + 1));
|
||||
} else {
|
||||
memos.push(create_memo(cx, move || *signal.get() + 1));
|
||||
|
@ -465,9 +461,8 @@ fn sycamore_deep_update(b: &mut Bencher) {
|
|||
let d = create_scope(|cx| {
|
||||
let signal = create_signal(cx, 0);
|
||||
let mut memos = Vec::<&ReadSignal<usize>>::new();
|
||||
for i in 0..1000usize {
|
||||
let prev = memos.get(i.saturating_sub(1)).copied();
|
||||
if let Some(prev) = prev {
|
||||
for _ in 0..1000usize {
|
||||
if let Some(prev) = memos.last().copied() {
|
||||
memos.push(create_memo(cx, move || *prev.get() + 1));
|
||||
} else {
|
||||
memos.push(create_memo(cx, move || *signal.get() + 1));
|
||||
|
|
|
@ -3,6 +3,10 @@ name = "counter"
|
|||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[profile.release]
|
||||
codegen-units = 1
|
||||
lto = true
|
||||
|
||||
[dependencies]
|
||||
leptos = { path = "../../leptos" }
|
||||
console_log = "1"
|
||||
|
@ -12,5 +16,4 @@ console_error_panic_hook = "0.1.7"
|
|||
[dev-dependencies]
|
||||
wasm-bindgen = "0.2"
|
||||
wasm-bindgen-test = "0.3.0"
|
||||
web-sys ="0.3"
|
||||
|
||||
web-sys = "0.3"
|
||||
|
|
|
@ -6,6 +6,10 @@ edition = "2021"
|
|||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[profile.release]
|
||||
codegen-units = 1
|
||||
lto = true
|
||||
|
||||
[dependencies]
|
||||
actix-files = { version = "0.6", optional = true }
|
||||
actix-web = { version = "4", optional = true, features = ["macros"] }
|
||||
|
|
|
@ -3,6 +3,10 @@ name = "counter_without_macros"
|
|||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[profile.release]
|
||||
codegen-units = 1
|
||||
lto = true
|
||||
|
||||
[dependencies]
|
||||
leptos = { path = "../../leptos", features = ["stable"] }
|
||||
console_log = "1"
|
||||
|
|
|
@ -3,6 +3,10 @@ name = "error_boundary"
|
|||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[profile.release]
|
||||
codegen-units = 1
|
||||
lto = true
|
||||
|
||||
[dependencies]
|
||||
leptos = { path = "../../leptos" }
|
||||
console_log = "1"
|
||||
|
|
|
@ -3,6 +3,10 @@ name = "fetch"
|
|||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[profile.release]
|
||||
codegen-units = 1
|
||||
lto = true
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.58"
|
||||
leptos = { path = "../../leptos" }
|
||||
|
@ -14,4 +18,3 @@ console_error_panic_hook = "0.1.7"
|
|||
|
||||
[dev-dependencies]
|
||||
wasm-bindgen-test = "0.3.0"
|
||||
|
||||
|
|
|
@ -6,6 +6,10 @@ edition = "2021"
|
|||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[profile.release]
|
||||
codegen-units = 1
|
||||
lto = true
|
||||
|
||||
[dependencies]
|
||||
actix-files = { version = "0.6", optional = true }
|
||||
actix-web = { version = "4", optional = true, features = ["macros"] }
|
||||
|
|
|
@ -6,6 +6,10 @@ edition = "2021"
|
|||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[profile.release]
|
||||
codegen-units = 1
|
||||
lto = true
|
||||
|
||||
[dependencies]
|
||||
console_log = "1.0.0"
|
||||
console_error_panic_hook = "0.1.7"
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
[workspace]
|
||||
members = ["client", "api-boundary", "server"]
|
||||
|
||||
[profile.release]
|
||||
codegen-units = 1
|
||||
lto = true
|
||||
|
||||
[patch.crates-io]
|
||||
leptos = { path = "../../leptos" }
|
||||
leptos_router = { path = "../../router" }
|
||||
|
|
|
@ -3,6 +3,10 @@ name = "parent-child"
|
|||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[profile.release]
|
||||
codegen-units = 1
|
||||
lto = true
|
||||
|
||||
[dependencies]
|
||||
leptos = { path = "../../leptos" }
|
||||
console_log = "1"
|
||||
|
|
|
@ -3,6 +3,10 @@ name = "router"
|
|||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[profile.release]
|
||||
codegen-units = 1
|
||||
lto = true
|
||||
|
||||
[dependencies]
|
||||
console_log = "1"
|
||||
log = "0.4"
|
||||
|
|
|
@ -3,6 +3,10 @@ name = "todomvc"
|
|||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[profile.release]
|
||||
codegen-units = 1
|
||||
lto = true
|
||||
|
||||
[dependencies]
|
||||
leptos = { path = "../../leptos", default-features = false }
|
||||
log = "0.4"
|
||||
|
|
|
@ -135,6 +135,7 @@ impl Mountable for ComponentRepr {
|
|||
};
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_closing_node(&self) -> web_sys::Node {
|
||||
self.closing.node.clone()
|
||||
}
|
||||
|
@ -156,17 +157,21 @@ impl IntoView for ComponentRepr {
|
|||
|
||||
impl ComponentRepr {
|
||||
/// Creates a new [`Component`].
|
||||
#[inline(always)]
|
||||
pub fn new(name: impl Into<Cow<'static, str>>) -> Self {
|
||||
Self::new_with_id(name, HydrationCtx::id())
|
||||
Self::new_with_id_concrete(name.into(), HydrationCtx::id())
|
||||
}
|
||||
|
||||
/// Creates a new [`Component`] with the given hydration ID.
|
||||
#[inline(always)]
|
||||
pub fn new_with_id(
|
||||
name: impl Into<Cow<'static, str>>,
|
||||
id: HydrationKey,
|
||||
) -> Self {
|
||||
let name = name.into();
|
||||
Self::new_with_id_concrete(name.into(), id)
|
||||
}
|
||||
|
||||
fn new_with_id_concrete(name: Cow<'static, str>, id: HydrationKey) -> Self {
|
||||
let markers = (
|
||||
Comment::new(Cow::Owned(format!("</{name}>")), &id, true),
|
||||
#[cfg(debug_assertions)]
|
||||
|
|
|
@ -139,13 +139,15 @@ where
|
|||
/// Creates a new dynamic child which will re-render whenever it's
|
||||
/// signal dependencies change.
|
||||
#[track_caller]
|
||||
#[inline(always)]
|
||||
pub fn new(child_fn: CF) -> Self {
|
||||
Self::new_with_id(HydrationCtx::id(), child_fn)
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[track_caller]
|
||||
pub fn new_with_id(id: HydrationKey, child_fn: CF) -> Self {
|
||||
#[inline(always)]
|
||||
pub const fn new_with_id(id: HydrationKey, child_fn: CF) -> Self {
|
||||
Self { id, child_fn }
|
||||
}
|
||||
}
|
||||
|
@ -159,8 +161,10 @@ where
|
|||
debug_assertions,
|
||||
instrument(level = "trace", name = "<DynChild />", skip_all)
|
||||
)]
|
||||
#[inline]
|
||||
fn into_view(self, cx: Scope) -> View {
|
||||
// concrete inner function
|
||||
#[inline(never)]
|
||||
fn create_dyn_view(
|
||||
cx: Scope,
|
||||
component: DynChildRepr,
|
||||
|
@ -379,6 +383,7 @@ cfg_if! {
|
|||
}
|
||||
|
||||
impl NonViewMarkerSibling for web_sys::Node {
|
||||
#[cfg_attr(not(debug_assertions), inline(always))]
|
||||
fn next_non_view_marker_sibling(&self) -> Option<Node> {
|
||||
cfg_if! {
|
||||
if #[cfg(debug_assertions)] {
|
||||
|
@ -395,6 +400,7 @@ cfg_if! {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(not(debug_assertions), inline(always))]
|
||||
fn previous_non_view_marker_sibling(&self) -> Option<Node> {
|
||||
cfg_if! {
|
||||
if #[cfg(debug_assertions)] {
|
||||
|
|
|
@ -155,6 +155,7 @@ impl Mountable for EachRepr {
|
|||
};
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn get_closing_node(&self) -> web_sys::Node {
|
||||
self.closing.node.clone()
|
||||
}
|
||||
|
@ -257,6 +258,7 @@ impl Mountable for EachItem {
|
|||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn get_opening_node(&self) -> web_sys::Node {
|
||||
#[cfg(debug_assertions)]
|
||||
return self.opening.node.clone();
|
||||
|
@ -328,7 +330,8 @@ where
|
|||
T: 'static,
|
||||
{
|
||||
/// Creates a new [`Each`] component.
|
||||
pub fn new(items_fn: IF, key_fn: KF, each_fn: EF) -> Self {
|
||||
#[inline(always)]
|
||||
pub const fn new(items_fn: IF, key_fn: KF, each_fn: EF) -> Self {
|
||||
Self {
|
||||
items_fn,
|
||||
each_fn,
|
||||
|
|
|
@ -1,20 +1,23 @@
|
|||
use crate::{HydrationCtx, IntoView};
|
||||
use cfg_if::cfg_if;
|
||||
use leptos_reactive::{signal_prelude::*, use_context, RwSignal};
|
||||
use std::{collections::HashMap, error::Error, sync::Arc};
|
||||
use std::{borrow::Cow, collections::HashMap, error::Error, sync::Arc};
|
||||
|
||||
/// A struct to hold all the possible errors that could be provided by child Views
|
||||
#[derive(Debug, Clone, Default)]
|
||||
#[repr(transparent)]
|
||||
pub struct Errors(HashMap<ErrorKey, Arc<dyn Error + Send + Sync>>);
|
||||
|
||||
/// A unique key for an error that occurs at a particular location in the user interface.
|
||||
#[derive(Debug, Default, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct ErrorKey(String);
|
||||
#[repr(transparent)]
|
||||
pub struct ErrorKey(Cow<'static, str>);
|
||||
|
||||
impl<T> From<T> for ErrorKey
|
||||
where
|
||||
T: Into<String>,
|
||||
T: Into<Cow<'static, str>>,
|
||||
{
|
||||
#[inline(always)]
|
||||
fn from(key: T) -> ErrorKey {
|
||||
ErrorKey(key.into())
|
||||
}
|
||||
|
@ -24,12 +27,14 @@ impl IntoIterator for Errors {
|
|||
type Item = (ErrorKey, Arc<dyn Error + Send + Sync>);
|
||||
type IntoIter = IntoIter;
|
||||
|
||||
#[inline(always)]
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
IntoIter(self.0.into_iter())
|
||||
}
|
||||
}
|
||||
|
||||
/// An owning iterator over all the errors contained in the [Errors] struct.
|
||||
#[repr(transparent)]
|
||||
pub struct IntoIter(
|
||||
std::collections::hash_map::IntoIter<
|
||||
ErrorKey,
|
||||
|
@ -40,6 +45,7 @@ pub struct IntoIter(
|
|||
impl Iterator for IntoIter {
|
||||
type Item = (ErrorKey, Arc<dyn Error + Send + Sync>);
|
||||
|
||||
#[inline(always)]
|
||||
fn next(
|
||||
&mut self,
|
||||
) -> std::option::Option<<Self as std::iter::Iterator>::Item> {
|
||||
|
@ -48,6 +54,7 @@ impl Iterator for IntoIter {
|
|||
}
|
||||
|
||||
/// An iterator over all the errors contained in the [Errors] struct.
|
||||
#[repr(transparent)]
|
||||
pub struct Iter<'a>(
|
||||
std::collections::hash_map::Iter<
|
||||
'a,
|
||||
|
@ -59,6 +66,7 @@ pub struct Iter<'a>(
|
|||
impl<'a> Iterator for Iter<'a> {
|
||||
type Item = (&'a ErrorKey, &'a Arc<dyn Error + Send + Sync>);
|
||||
|
||||
#[inline(always)]
|
||||
fn next(
|
||||
&mut self,
|
||||
) -> std::option::Option<<Self as std::iter::Iterator>::Item> {
|
||||
|
@ -72,7 +80,7 @@ where
|
|||
E: Error + Send + Sync + 'static,
|
||||
{
|
||||
fn into_view(self, cx: leptos_reactive::Scope) -> crate::View {
|
||||
let id = ErrorKey(HydrationCtx::peek().previous);
|
||||
let id = ErrorKey(HydrationCtx::peek().previous.into());
|
||||
let errors = use_context::<RwSignal<Errors>>(cx);
|
||||
match self {
|
||||
Ok(stuff) => {
|
||||
|
@ -127,6 +135,7 @@ where
|
|||
}
|
||||
impl Errors {
|
||||
/// Returns `true` if there are no errors.
|
||||
#[inline(always)]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.0.is_empty()
|
||||
}
|
||||
|
@ -156,6 +165,7 @@ impl Errors {
|
|||
}
|
||||
|
||||
/// An iterator over all the errors, in arbitrary order.
|
||||
#[inline(always)]
|
||||
pub fn iter(&self) -> Iter<'_> {
|
||||
Iter(self.0.iter())
|
||||
}
|
||||
|
|
|
@ -43,17 +43,20 @@ impl From<View> for Fragment {
|
|||
|
||||
impl Fragment {
|
||||
/// Creates a new [`Fragment`] from a [`Vec<Node>`].
|
||||
#[inline(always)]
|
||||
pub fn new(nodes: Vec<View>) -> Self {
|
||||
Self::new_with_id(HydrationCtx::id(), nodes)
|
||||
}
|
||||
|
||||
/// Creates a new [`Fragment`] from a function that returns [`Vec<Node>`].
|
||||
#[inline(always)]
|
||||
pub fn lazy(nodes: impl FnOnce() -> Vec<View>) -> Self {
|
||||
Self::new_with_id(HydrationCtx::id(), nodes())
|
||||
}
|
||||
|
||||
/// Creates a new [`Fragment`] with the given hydration ID from a [`Vec<Node>`].
|
||||
pub fn new_with_id(id: HydrationKey, nodes: Vec<View>) -> Self {
|
||||
#[inline(always)]
|
||||
pub const fn new_with_id(id: HydrationKey, nodes: Vec<View>) -> Self {
|
||||
Self {
|
||||
id,
|
||||
nodes,
|
||||
|
@ -63,11 +66,13 @@ impl Fragment {
|
|||
}
|
||||
|
||||
/// Gives access to the [View] children contained within the fragment.
|
||||
#[inline(always)]
|
||||
pub fn as_children(&self) -> &[View] {
|
||||
&self.nodes
|
||||
}
|
||||
|
||||
/// Returns the fragment's hydration ID.
|
||||
#[inline(always)]
|
||||
pub fn id(&self) -> &HydrationKey {
|
||||
&self.id
|
||||
}
|
||||
|
|
|
@ -40,14 +40,17 @@ impl Default for UnitRepr {
|
|||
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
impl Mountable for UnitRepr {
|
||||
#[inline(always)]
|
||||
fn get_mountable_node(&self) -> web_sys::Node {
|
||||
self.comment.node.clone().unchecked_into()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn get_opening_node(&self) -> web_sys::Node {
|
||||
self.comment.node.clone().unchecked_into()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn get_closing_node(&self) -> web_sys::Node {
|
||||
self.comment.node.clone().unchecked_into()
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ thread_local! {
|
|||
// Used in template macro
|
||||
#[doc(hidden)]
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
#[inline(always)]
|
||||
pub fn add_event_helper<E: crate::ev::EventDescriptor + 'static>(
|
||||
target: &web_sys::Element,
|
||||
event: E,
|
||||
|
@ -21,8 +22,9 @@ pub fn add_event_helper<E: crate::ev::EventDescriptor + 'static>(
|
|||
mut event_handler: impl FnMut(E::EventType) + 'static,
|
||||
) {
|
||||
let event_name = event.name();
|
||||
let event_handler = Box::new(event_handler);
|
||||
|
||||
if event.bubbles() {
|
||||
if E::BUBBLES {
|
||||
add_event_listener(
|
||||
target,
|
||||
event.event_delegation_key(),
|
||||
|
@ -47,8 +49,8 @@ pub fn add_event_listener<E>(
|
|||
target: &web_sys::Element,
|
||||
key: Cow<'static, str>,
|
||||
event_name: Cow<'static, str>,
|
||||
#[cfg(debug_assertions)] mut cb: impl FnMut(E) + 'static,
|
||||
#[cfg(not(debug_assertions))] cb: impl FnMut(E) + 'static,
|
||||
#[cfg(debug_assertions)] mut cb: Box<dyn FnMut(E)>,
|
||||
#[cfg(not(debug_assertions))] cb: Box<dyn FnMut(E)>,
|
||||
options: &Option<web_sys::AddEventListenerOptions>,
|
||||
) where
|
||||
E: FromWasmAbi + 'static,
|
||||
|
@ -56,16 +58,16 @@ pub fn add_event_listener<E>(
|
|||
cfg_if::cfg_if! {
|
||||
if #[cfg(debug_assertions)] {
|
||||
let span = ::tracing::Span::current();
|
||||
let cb = move |e| {
|
||||
let cb = Box::new(move |e| {
|
||||
leptos_reactive::SpecialNonReactiveZone::enter();
|
||||
let _guard = span.enter();
|
||||
cb(e);
|
||||
leptos_reactive::SpecialNonReactiveZone::exit();
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let cb = Closure::wrap(Box::new(cb) as Box<dyn FnMut(E)>).into_js_value();
|
||||
let cb = Closure::wrap(cb as Box<dyn FnMut(E)>).into_js_value();
|
||||
let key = intern(&key);
|
||||
_ = js_sys::Reflect::set(target, &JsValue::from_str(&key), &cb);
|
||||
add_delegated_event_listener(&key, event_name, options);
|
||||
|
@ -76,8 +78,8 @@ pub fn add_event_listener<E>(
|
|||
pub(crate) fn add_event_listener_undelegated<E>(
|
||||
target: &web_sys::Element,
|
||||
event_name: &str,
|
||||
#[cfg(debug_assertions)] mut cb: impl FnMut(E) + 'static,
|
||||
#[cfg(not(debug_assertions))] cb: impl FnMut(E) + 'static,
|
||||
#[cfg(debug_assertions)] mut cb: Box<dyn FnMut(E)>,
|
||||
#[cfg(not(debug_assertions))] cb: Box<dyn FnMut(E)>,
|
||||
options: &Option<web_sys::AddEventListenerOptions>,
|
||||
) where
|
||||
E: FromWasmAbi + 'static,
|
||||
|
@ -86,16 +88,16 @@ pub(crate) fn add_event_listener_undelegated<E>(
|
|||
if #[cfg(debug_assertions)] {
|
||||
leptos_reactive::SpecialNonReactiveZone::enter();
|
||||
let span = ::tracing::Span::current();
|
||||
let cb = move |e| {
|
||||
let cb = Box::new(move |e| {
|
||||
let _guard = span.enter();
|
||||
cb(e);
|
||||
};
|
||||
});
|
||||
leptos_reactive::SpecialNonReactiveZone::exit();
|
||||
}
|
||||
}
|
||||
|
||||
let event_name = intern(event_name);
|
||||
let cb = Closure::wrap(Box::new(cb) as Box<dyn FnMut(E)>).into_js_value();
|
||||
let cb = Closure::wrap(cb as Box<dyn FnMut(E)>).into_js_value();
|
||||
if let Some(options) = options {
|
||||
_ = target
|
||||
.add_event_listener_with_callback_and_add_event_listener_options(
|
||||
|
|
|
@ -8,23 +8,22 @@ pub trait EventDescriptor: Clone {
|
|||
/// The [`web_sys`] event type, such as [`web_sys::MouseEvent`].
|
||||
type EventType: FromWasmAbi;
|
||||
|
||||
/// Indicates if this event bubbles. For example, `click` bubbles,
|
||||
/// but `focus` does not.
|
||||
///
|
||||
/// If this is true, then the event will be delegated globally,
|
||||
/// otherwise, event listeners will be directly attached to the element.
|
||||
const BUBBLES: bool;
|
||||
|
||||
/// The name of the event, such as `click` or `mouseover`.
|
||||
fn name(&self) -> Cow<'static, str>;
|
||||
|
||||
/// The key used for event delegation.
|
||||
fn event_delegation_key(&self) -> Cow<'static, str>;
|
||||
|
||||
/// Indicates if this event bubbles. For example, `click` bubbles,
|
||||
/// but `focus` does not.
|
||||
///
|
||||
/// If this method returns true, then the event will be delegated globally,
|
||||
/// otherwise, event listeners will be directly attached to the element.
|
||||
fn bubbles(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
/// Return the options for this type. This is only used when you create a [`Custom`] event
|
||||
/// handler.
|
||||
#[inline(always)]
|
||||
fn options(&self) -> &Option<web_sys::AddEventListenerOptions> {
|
||||
&None
|
||||
}
|
||||
|
@ -39,17 +38,17 @@ pub struct undelegated<Ev: EventDescriptor>(pub Ev);
|
|||
impl<Ev: EventDescriptor> EventDescriptor for undelegated<Ev> {
|
||||
type EventType = Ev::EventType;
|
||||
|
||||
#[inline(always)]
|
||||
fn name(&self) -> Cow<'static, str> {
|
||||
self.0.name()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn event_delegation_key(&self) -> Cow<'static, str> {
|
||||
self.0.event_delegation_key()
|
||||
}
|
||||
|
||||
fn bubbles(&self) -> bool {
|
||||
false
|
||||
}
|
||||
const BUBBLES: bool = false;
|
||||
}
|
||||
|
||||
/// A custom event.
|
||||
|
@ -80,10 +79,9 @@ impl<E: FromWasmAbi> EventDescriptor for Custom<E> {
|
|||
format!("$$${}", self.name).into()
|
||||
}
|
||||
|
||||
fn bubbles(&self) -> bool {
|
||||
false
|
||||
}
|
||||
const BUBBLES: bool = false;
|
||||
|
||||
#[inline(always)]
|
||||
fn options(&self) -> &Option<web_sys::AddEventListenerOptions> {
|
||||
&self.options
|
||||
}
|
||||
|
@ -142,24 +140,22 @@ macro_rules! generate_event_types {
|
|||
impl EventDescriptor for $event {
|
||||
type EventType = web_sys::$web_sys_event;
|
||||
|
||||
#[inline(always)]
|
||||
fn name(&self) -> Cow<'static, str> {
|
||||
stringify!($event).into()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn event_delegation_key(&self) -> Cow<'static, str> {
|
||||
concat!("$$$", stringify!($event)).into()
|
||||
}
|
||||
|
||||
$(
|
||||
generate_event_types!($does_not_bubble);
|
||||
)?
|
||||
const BUBBLES: bool = true $(&& generate_event_types!($does_not_bubble))?;
|
||||
}
|
||||
)*
|
||||
};
|
||||
|
||||
(does_not_bubble) => {
|
||||
fn bubbles(&self) -> bool { false }
|
||||
}
|
||||
(does_not_bubble) => { false }
|
||||
}
|
||||
|
||||
generate_event_types! {
|
||||
|
|
|
@ -97,6 +97,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))]
|
||||
#[inline(always)]
|
||||
pub fn request_animation_frame(cb: impl FnOnce() + 'static) {
|
||||
_ = request_animation_frame_with_handle(cb);
|
||||
}
|
||||
|
@ -105,6 +106,7 @@ pub fn request_animation_frame(cb: impl FnOnce() + 'static) {
|
|||
/// [`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))]
|
||||
#[inline(always)]
|
||||
pub fn request_animation_frame_with_handle(
|
||||
cb: impl FnOnce() + 'static,
|
||||
) -> Result<AnimationFrameRequestHandle, JsValue> {
|
||||
|
@ -118,10 +120,14 @@ pub fn request_animation_frame_with_handle(
|
|||
}
|
||||
}
|
||||
|
||||
let cb = Closure::once_into_js(cb);
|
||||
window()
|
||||
.request_animation_frame(cb.as_ref().unchecked_ref())
|
||||
.map(AnimationFrameRequestHandle)
|
||||
#[inline(never)]
|
||||
fn raf(cb: JsValue) -> Result<AnimationFrameRequestHandle, JsValue> {
|
||||
window()
|
||||
.request_animation_frame(cb.as_ref().unchecked_ref())
|
||||
.map(AnimationFrameRequestHandle)
|
||||
}
|
||||
|
||||
raf(Closure::once_into_js(cb))
|
||||
}
|
||||
|
||||
/// Handle that is generated by [request_idle_callback_with_handle] and can be
|
||||
|
@ -140,6 +146,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))]
|
||||
#[inline(always)]
|
||||
pub fn request_idle_callback(cb: impl Fn() + 'static) {
|
||||
_ = request_idle_callback_with_handle(cb);
|
||||
}
|
||||
|
@ -148,6 +155,7 @@ pub fn request_idle_callback(cb: impl Fn() + 'static) {
|
|||
/// [`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))]
|
||||
#[inline(always)]
|
||||
pub fn request_idle_callback_with_handle(
|
||||
cb: impl Fn() + 'static,
|
||||
) -> Result<IdleCallbackHandle, JsValue> {
|
||||
|
@ -161,10 +169,16 @@ pub fn request_idle_callback_with_handle(
|
|||
}
|
||||
}
|
||||
|
||||
let cb = Closure::wrap(Box::new(cb) as Box<dyn Fn()>).into_js_value();
|
||||
window()
|
||||
.request_idle_callback(cb.as_ref().unchecked_ref())
|
||||
.map(IdleCallbackHandle)
|
||||
#[inline(never)]
|
||||
fn ric(cb: Box<dyn Fn()>) -> Result<IdleCallbackHandle, JsValue> {
|
||||
let cb = Closure::wrap(cb).into_js_value();
|
||||
|
||||
window()
|
||||
.request_idle_callback(cb.as_ref().unchecked_ref())
|
||||
.map(IdleCallbackHandle)
|
||||
}
|
||||
|
||||
ric(Box::new(cb))
|
||||
}
|
||||
|
||||
/// Handle that is generated by [set_timeout_with_handle] and can be used to clear the timeout.
|
||||
|
@ -195,6 +209,7 @@ pub fn set_timeout(cb: impl FnOnce() + 'static, duration: Duration) {
|
|||
debug_assertions,
|
||||
instrument(level = "trace", skip_all, fields(duration = ?duration))
|
||||
)]
|
||||
#[inline(always)]
|
||||
pub fn set_timeout_with_handle(
|
||||
cb: impl FnOnce() + 'static,
|
||||
duration: Duration,
|
||||
|
@ -211,13 +226,17 @@ pub fn set_timeout_with_handle(
|
|||
}
|
||||
}
|
||||
|
||||
let cb = Closure::once_into_js(Box::new(cb) as Box<dyn FnOnce()>);
|
||||
window()
|
||||
.set_timeout_with_callback_and_timeout_and_arguments_0(
|
||||
cb.as_ref().unchecked_ref(),
|
||||
duration.as_millis().try_into().unwrap_throw(),
|
||||
)
|
||||
.map(TimeoutHandle)
|
||||
#[inline(never)]
|
||||
fn st(cb: JsValue, duration: Duration) -> Result<TimeoutHandle, JsValue> {
|
||||
window()
|
||||
.set_timeout_with_callback_and_timeout_and_arguments_0(
|
||||
cb.as_ref().unchecked_ref(),
|
||||
duration.as_millis().try_into().unwrap_throw(),
|
||||
)
|
||||
.map(TimeoutHandle)
|
||||
}
|
||||
|
||||
st(Closure::once_into_js(cb), duration)
|
||||
}
|
||||
|
||||
/// "Debounce" a callback function. This will cause it to wait for a period of `delay`
|
||||
|
@ -243,7 +262,8 @@ pub fn set_timeout_with_handle(
|
|||
pub fn debounce<T: 'static>(
|
||||
cx: Scope,
|
||||
delay: Duration,
|
||||
#[allow(unused_mut)] mut cb: impl FnMut(T) + 'static,
|
||||
#[cfg(debug_assertions)] mut cb: impl FnMut(T) + 'static,
|
||||
#[cfg(not(debug_assertions))] cb: impl FnMut(T) + 'static,
|
||||
) -> impl FnMut(T) {
|
||||
use std::{
|
||||
cell::{Cell, RefCell},
|
||||
|
@ -347,6 +367,7 @@ pub fn set_interval(
|
|||
debug_assertions,
|
||||
instrument(level = "trace", skip_all, fields(duration = ?duration))
|
||||
)]
|
||||
#[inline(always)]
|
||||
pub fn set_interval_with_handle(
|
||||
cb: impl Fn() + 'static,
|
||||
duration: Duration,
|
||||
|
@ -363,13 +384,22 @@ pub fn set_interval_with_handle(
|
|||
}
|
||||
}
|
||||
|
||||
let cb = Closure::wrap(Box::new(cb) as Box<dyn Fn()>).into_js_value();
|
||||
let handle = window()
|
||||
.set_interval_with_callback_and_timeout_and_arguments_0(
|
||||
cb.as_ref().unchecked_ref(),
|
||||
duration.as_millis().try_into().unwrap_throw(),
|
||||
)?;
|
||||
Ok(IntervalHandle(handle))
|
||||
#[inline(never)]
|
||||
fn si(
|
||||
cb: Box<dyn Fn()>,
|
||||
duration: Duration,
|
||||
) -> Result<IntervalHandle, JsValue> {
|
||||
let cb = Closure::wrap(cb).into_js_value();
|
||||
|
||||
window()
|
||||
.set_interval_with_callback_and_timeout_and_arguments_0(
|
||||
cb.as_ref().unchecked_ref(),
|
||||
duration.as_millis().try_into().unwrap_throw(),
|
||||
)
|
||||
.map(IntervalHandle)
|
||||
}
|
||||
|
||||
si(Box::new(cb), duration)
|
||||
}
|
||||
|
||||
/// Adds an event listener to the `Window`.
|
||||
|
@ -377,6 +407,7 @@ pub fn set_interval_with_handle(
|
|||
debug_assertions,
|
||||
instrument(level = "trace", skip_all, fields(event_name = %event_name))
|
||||
)]
|
||||
#[inline(always)]
|
||||
pub fn window_event_listener(
|
||||
event_name: &str,
|
||||
cb: impl Fn(web_sys::Event) + 'static,
|
||||
|
@ -394,11 +425,16 @@ pub fn window_event_listener(
|
|||
}
|
||||
|
||||
if !is_server() {
|
||||
let handler = Box::new(cb) as Box<dyn FnMut(web_sys::Event)>;
|
||||
#[inline(never)]
|
||||
fn wel(cb: Box<dyn FnMut(web_sys::Event)>, event_name: &str) {
|
||||
let cb = Closure::wrap(cb).into_js_value();
|
||||
_ = window().add_event_listener_with_callback(
|
||||
event_name,
|
||||
cb.unchecked_ref(),
|
||||
);
|
||||
}
|
||||
|
||||
let cb = Closure::wrap(handler).into_js_value();
|
||||
_ = window()
|
||||
.add_event_listener_with_callback(event_name, cb.unchecked_ref());
|
||||
wel(Box::new(cb), event_name);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -75,6 +75,7 @@ pub trait ElementDescriptor: ElementDescriptorBounds {
|
|||
fn name(&self) -> Cow<'static, str>;
|
||||
|
||||
/// Determines if the tag is void, i.e., `<input>` and `<br>`.
|
||||
#[inline(always)]
|
||||
fn is_void(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
@ -140,6 +141,7 @@ pub struct AnyElement {
|
|||
impl std::ops::Deref for AnyElement {
|
||||
type Target = web_sys::HtmlElement;
|
||||
|
||||
#[inline(always)]
|
||||
fn deref(&self) -> &Self::Target {
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
return &self.element;
|
||||
|
@ -150,6 +152,7 @@ impl std::ops::Deref for AnyElement {
|
|||
}
|
||||
|
||||
impl std::convert::AsRef<web_sys::HtmlElement> for AnyElement {
|
||||
#[inline(always)]
|
||||
fn as_ref(&self) -> &web_sys::HtmlElement {
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
return &self.element;
|
||||
|
@ -164,11 +167,13 @@ impl ElementDescriptor for AnyElement {
|
|||
self.name.clone()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn is_void(&self) -> bool {
|
||||
self.is_void
|
||||
}
|
||||
|
||||
#[cfg(not(all(target_arch = "wasm32", feature = "web")))]
|
||||
#[inline(always)]
|
||||
fn hydration_id(&self) -> &HydrationKey {
|
||||
&self.id
|
||||
}
|
||||
|
@ -254,6 +259,7 @@ impl Custom {
|
|||
impl std::ops::Deref for Custom {
|
||||
type Target = web_sys::HtmlElement;
|
||||
|
||||
#[inline(always)]
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.element
|
||||
}
|
||||
|
@ -261,6 +267,7 @@ impl std::ops::Deref for Custom {
|
|||
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
impl std::convert::AsRef<web_sys::HtmlElement> for Custom {
|
||||
#[inline(always)]
|
||||
fn as_ref(&self) -> &web_sys::HtmlElement {
|
||||
&self.element
|
||||
}
|
||||
|
@ -272,6 +279,7 @@ impl ElementDescriptor for Custom {
|
|||
}
|
||||
|
||||
#[cfg(not(all(target_arch = "wasm32", feature = "web")))]
|
||||
#[inline(always)]
|
||||
fn hydration_id(&self) -> &HydrationKey {
|
||||
&self.id
|
||||
}
|
||||
|
@ -413,6 +421,7 @@ impl<El: ElementDescriptor + 'static> HtmlElement<El> {
|
|||
|
||||
#[cfg(debug_assertions)]
|
||||
/// Adds an optional marker indicating the view macro source.
|
||||
#[inline(always)]
|
||||
pub fn with_view_marker(mut self, marker: impl Into<String>) -> Self {
|
||||
self.view_marker = Some(marker.into());
|
||||
self
|
||||
|
@ -471,15 +480,18 @@ impl<El: ElementDescriptor + 'static> HtmlElement<El> {
|
|||
|
||||
/// Adds an `id` to the element.
|
||||
#[track_caller]
|
||||
#[inline(always)]
|
||||
pub fn id(self, id: impl Into<Cow<'static, str>>) -> Self {
|
||||
let id = id.into();
|
||||
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
{
|
||||
self.element
|
||||
.as_ref()
|
||||
.set_attribute(wasm_bindgen::intern("id"), &id)
|
||||
.unwrap();
|
||||
#[inline(never)]
|
||||
fn id_inner(el: &web_sys::HtmlElement, id: &str) {
|
||||
el.set_attribute(wasm_bindgen::intern("id"), id).unwrap()
|
||||
}
|
||||
|
||||
id_inner(self.element.as_ref(), &id);
|
||||
|
||||
self
|
||||
}
|
||||
|
@ -495,6 +507,7 @@ impl<El: ElementDescriptor + 'static> HtmlElement<El> {
|
|||
}
|
||||
|
||||
/// Binds the element reference to [`NodeRef`].
|
||||
#[inline(always)]
|
||||
pub fn node_ref(self, node_ref: NodeRef<El>) -> Self
|
||||
where
|
||||
Self: Clone,
|
||||
|
@ -577,13 +590,16 @@ impl<El: ElementDescriptor + 'static> HtmlElement<El> {
|
|||
/// of `body`.
|
||||
///
|
||||
/// This method will always return [`None`] on non-wasm CSR targets.
|
||||
#[inline(always)]
|
||||
pub fn is_mounted(&self) -> bool {
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
{
|
||||
crate::document()
|
||||
.body()
|
||||
.unwrap()
|
||||
.contains(Some(self.element.as_ref()))
|
||||
#[inline(never)]
|
||||
fn is_mounted_inner(el: &web_sys::HtmlElement) -> bool {
|
||||
crate::document().body().unwrap().contains(Some(el))
|
||||
}
|
||||
|
||||
is_mounted_inner(self.element.as_ref())
|
||||
}
|
||||
|
||||
#[cfg(not(all(target_arch = "wasm32", feature = "web")))]
|
||||
|
@ -592,6 +608,7 @@ impl<El: ElementDescriptor + 'static> HtmlElement<El> {
|
|||
|
||||
/// Adds an attribute to this element.
|
||||
#[track_caller]
|
||||
#[cfg_attr(all(target_arch = "wasm32", feature = "web"), inline(always))]
|
||||
pub fn attr(
|
||||
self,
|
||||
name: impl Into<Cow<'static, str>>,
|
||||
|
@ -621,7 +638,7 @@ impl<El: ElementDescriptor + 'static> HtmlElement<El> {
|
|||
}
|
||||
match attr {
|
||||
Attribute::String(value) => {
|
||||
this.attrs.push((name, value.into()));
|
||||
this.attrs.push((name, value));
|
||||
}
|
||||
Attribute::Bool(include) => {
|
||||
if include {
|
||||
|
@ -630,7 +647,7 @@ impl<El: ElementDescriptor + 'static> HtmlElement<El> {
|
|||
}
|
||||
Attribute::Option(_, maybe) => {
|
||||
if let Some(value) = maybe {
|
||||
this.attrs.push((name, value.into()));
|
||||
this.attrs.push((name, value));
|
||||
}
|
||||
}
|
||||
_ => unreachable!(),
|
||||
|
@ -685,10 +702,7 @@ impl<El: ElementDescriptor + 'static> HtmlElement<El> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Adds a list of classes separated by ASCII whitespace to an element.
|
||||
#[track_caller]
|
||||
pub fn classes(self, classes: impl Into<Cow<'static, str>>) -> Self {
|
||||
let classes = classes.into();
|
||||
fn classes_inner(self, classes: &str) -> Self {
|
||||
let mut this = self;
|
||||
for class in classes.split_ascii_whitespace() {
|
||||
this = this.class(class.to_string(), true);
|
||||
|
@ -696,6 +710,13 @@ impl<El: ElementDescriptor + 'static> HtmlElement<El> {
|
|||
this
|
||||
}
|
||||
|
||||
/// Adds a list of classes separated by ASCII whitespace to an element.
|
||||
#[track_caller]
|
||||
#[inline(always)]
|
||||
pub fn classes(self, classes: impl Into<Cow<'static, str>>) -> Self {
|
||||
self.classes_inner(&classes.into())
|
||||
}
|
||||
|
||||
/// Sets the class on the element as the class signal changes.
|
||||
#[track_caller]
|
||||
pub fn dyn_classes<I, C>(
|
||||
|
@ -820,6 +841,7 @@ impl<El: ElementDescriptor + 'static> HtmlElement<El> {
|
|||
|
||||
/// Adds an event listener to this element.
|
||||
#[track_caller]
|
||||
#[inline(always)]
|
||||
pub fn on<E: EventDescriptor + 'static>(
|
||||
self,
|
||||
event: E,
|
||||
|
@ -842,8 +864,9 @@ impl<El: ElementDescriptor + 'static> HtmlElement<El> {
|
|||
let event_name = event.name();
|
||||
|
||||
let key = event.event_delegation_key();
|
||||
let event_handler = Box::new(event_handler);
|
||||
|
||||
if event.bubbles() {
|
||||
if E::BUBBLES {
|
||||
add_event_listener(
|
||||
self.element.as_ref(),
|
||||
key,
|
||||
|
@ -922,6 +945,7 @@ impl<El: ElementDescriptor + 'static> HtmlElement<El> {
|
|||
/// Be very careful when using this method. Always remember to
|
||||
/// sanitize the input to avoid a cross-site scripting (XSS)
|
||||
/// vulnerability.
|
||||
#[inline(always)]
|
||||
pub fn inner_html(self, html: impl Into<Cow<'static, str>>) -> Self {
|
||||
let html = html.into();
|
||||
|
||||
|
@ -945,6 +969,7 @@ impl<El: ElementDescriptor + 'static> HtmlElement<El> {
|
|||
|
||||
impl<El: ElementDescriptor> IntoView for HtmlElement<El> {
|
||||
#[cfg_attr(debug_assertions, instrument(level = "trace", name = "<HtmlElement />", skip_all, fields(tag = %self.element.name())))]
|
||||
#[cfg_attr(all(target_arch = "wasm32", feature = "web"), inline(always))]
|
||||
fn into_view(self, _: Scope) -> View {
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
{
|
||||
|
@ -1011,6 +1036,7 @@ pub fn custom<El: ElementDescriptor>(cx: Scope, el: El) -> HtmlElement<Custom> {
|
|||
}
|
||||
|
||||
/// Creates a text node.
|
||||
#[inline(always)]
|
||||
pub fn text(text: impl Into<Cow<'static, str>>) -> Text {
|
||||
Text::new(text.into())
|
||||
}
|
||||
|
@ -1072,6 +1098,7 @@ macro_rules! generate_html_tags {
|
|||
impl std::ops::Deref for [<$tag:camel $($trailing_)?>] {
|
||||
type Target = web_sys::$el_type;
|
||||
|
||||
#[inline(always)]
|
||||
fn deref(&self) -> &Self::Target {
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
{
|
||||
|
@ -1085,6 +1112,7 @@ macro_rules! generate_html_tags {
|
|||
}
|
||||
|
||||
impl std::convert::AsRef<web_sys::HtmlElement> for [<$tag:camel $($trailing_)?>] {
|
||||
#[inline(always)]
|
||||
fn as_ref(&self) -> &web_sys::HtmlElement {
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
return &self.element;
|
||||
|
@ -1095,11 +1123,13 @@ macro_rules! generate_html_tags {
|
|||
}
|
||||
|
||||
impl ElementDescriptor for [<$tag:camel $($trailing_)?>] {
|
||||
#[inline(always)]
|
||||
fn name(&self) -> Cow<'static, str> {
|
||||
stringify!($tag).into()
|
||||
}
|
||||
|
||||
#[cfg(not(all(target_arch = "wasm32", feature = "web")))]
|
||||
#[inline(always)]
|
||||
fn hydration_id(&self) -> &HydrationKey {
|
||||
&self.id
|
||||
}
|
||||
|
@ -1127,6 +1157,7 @@ macro_rules! generate_html_tags {
|
|||
};
|
||||
(@void) => {};
|
||||
(@void void) => {
|
||||
#[inline(always)]
|
||||
fn is_void(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
|
|
@ -137,6 +137,7 @@ impl<T> IntoView for (Scope, T)
|
|||
where
|
||||
T: IntoView,
|
||||
{
|
||||
#[inline(always)]
|
||||
fn into_view(self, _: Scope) -> View {
|
||||
self.1.into_view(self.0)
|
||||
}
|
||||
|
@ -373,48 +374,53 @@ struct Comment {
|
|||
}
|
||||
|
||||
impl Comment {
|
||||
#[inline]
|
||||
fn new(
|
||||
content: impl Into<Cow<'static, str>>,
|
||||
id: &HydrationKey,
|
||||
closing: bool,
|
||||
) -> Self {
|
||||
let content = content.into();
|
||||
Self::new_inner(content.into(), id, closing)
|
||||
}
|
||||
|
||||
#[cfg(not(all(target_arch = "wasm32", feature = "web")))]
|
||||
{
|
||||
let _ = id;
|
||||
let _ = closing;
|
||||
}
|
||||
fn new_inner(
|
||||
content: Cow<'static, str>,
|
||||
id: &HydrationKey,
|
||||
closing: bool,
|
||||
) -> Self {
|
||||
cfg_if! {
|
||||
if #[cfg(not(all(target_arch = "wasm32", feature = "web")))] {
|
||||
let _ = id;
|
||||
let _ = closing;
|
||||
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
let node = COMMENT.with(|comment| comment.clone_node().unwrap());
|
||||
Self { content }
|
||||
} else {
|
||||
let node = COMMENT.with(|comment| comment.clone_node().unwrap());
|
||||
|
||||
#[cfg(all(debug_assertions, target_arch = "wasm32", feature = "web"))]
|
||||
node.set_text_content(Some(&format!(" {content} ")));
|
||||
#[cfg(debug_assertions)]
|
||||
node.set_text_content(Some(&format!(" {content} ")));
|
||||
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
{
|
||||
if HydrationCtx::is_hydrating() {
|
||||
let id = HydrationCtx::to_string(id, closing);
|
||||
if HydrationCtx::is_hydrating() {
|
||||
let id = HydrationCtx::to_string(id, closing);
|
||||
|
||||
if let Some(marker) = hydration::get_marker(&id) {
|
||||
marker.before_with_node_1(&node).unwrap();
|
||||
if let Some(marker) = hydration::get_marker(&id) {
|
||||
marker.before_with_node_1(&node).unwrap();
|
||||
|
||||
marker.remove();
|
||||
} else {
|
||||
crate::warn!(
|
||||
"component with id {id} not found, ignoring it for \
|
||||
hydration"
|
||||
);
|
||||
marker.remove();
|
||||
} else {
|
||||
crate::warn!(
|
||||
"component with id {id} not found, ignoring it for \
|
||||
hydration"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Self {
|
||||
node,
|
||||
content,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Self {
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
node,
|
||||
content,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -652,6 +658,7 @@ impl View {
|
|||
///
|
||||
/// This method will attach an event listener to **all** child
|
||||
/// [`HtmlElement`] children.
|
||||
#[inline(always)]
|
||||
pub fn on<E: ev::EventDescriptor + 'static>(
|
||||
self,
|
||||
event: E,
|
||||
|
@ -680,7 +687,7 @@ impl View {
|
|||
if #[cfg(all(target_arch = "wasm32", feature = "web"))] {
|
||||
match &self {
|
||||
Self::Element(el) => {
|
||||
if event.bubbles() {
|
||||
if E::BUBBLES {
|
||||
add_event_listener(&el.element, event.event_delegation_key(), event.name(), event_handler, &None);
|
||||
} else {
|
||||
add_event_listener_undelegated(
|
||||
|
@ -919,6 +926,7 @@ macro_rules! impl_into_view_for_tuples {
|
|||
where
|
||||
$($ty: IntoView),*
|
||||
{
|
||||
#[inline]
|
||||
fn into_view(self, cx: Scope) -> View {
|
||||
paste::paste! {
|
||||
let ($([<$ty:lower>],)*) = self;
|
||||
|
@ -993,12 +1001,14 @@ impl IntoView for String {
|
|||
debug_assertions,
|
||||
instrument(level = "trace", name = "#text", skip_all)
|
||||
)]
|
||||
#[inline(always)]
|
||||
fn into_view(self, _: Scope) -> View {
|
||||
View::Text(Text::new(self.into()))
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoView for &'static str {
|
||||
#[inline(always)]
|
||||
fn into_view(self, _: Scope) -> View {
|
||||
View::Text(Text::new(self.into()))
|
||||
}
|
||||
|
@ -1020,6 +1030,7 @@ macro_rules! viewable_primitive {
|
|||
($($child_type:ty),* $(,)?) => {
|
||||
$(
|
||||
impl IntoView for $child_type {
|
||||
#[inline(always)]
|
||||
fn into_view(self, _cx: Scope) -> View {
|
||||
View::Text(Text::new(self.to_string().into()))
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use leptos_reactive::Scope;
|
||||
use std::rc::Rc;
|
||||
use std::{borrow::Cow, rc::Rc};
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
use wasm_bindgen::UnwrapThrowExt;
|
||||
|
||||
|
@ -11,11 +11,11 @@ use wasm_bindgen::UnwrapThrowExt;
|
|||
#[derive(Clone)]
|
||||
pub enum Attribute {
|
||||
/// A plain string value.
|
||||
String(String),
|
||||
String(Cow<'static, str>),
|
||||
/// A (presumably reactive) function, which will be run inside an effect to do targeted updates to the attribute.
|
||||
Fn(Scope, Rc<dyn Fn() -> Attribute>),
|
||||
/// An optional string value, which sets the attribute to the value if `Some` and removes the attribute if `None`.
|
||||
Option(Scope, Option<String>),
|
||||
Option(Scope, Option<Cow<'static, str>>),
|
||||
/// A boolean attribute, which sets the attribute if `true` and removes the attribute if `false`.
|
||||
Bool(bool),
|
||||
}
|
||||
|
@ -23,9 +23,14 @@ pub enum Attribute {
|
|||
impl Attribute {
|
||||
/// Converts the attribute to its HTML value at that moment, including the attribute name,
|
||||
/// so it can be rendered on the server.
|
||||
pub fn as_value_string(&self, attr_name: &'static str) -> String {
|
||||
pub fn as_value_string(
|
||||
&self,
|
||||
attr_name: &'static str,
|
||||
) -> Cow<'static, str> {
|
||||
match self {
|
||||
Attribute::String(value) => format!("{attr_name}=\"{value}\""),
|
||||
Attribute::String(value) => {
|
||||
format!("{attr_name}=\"{value}\"").into()
|
||||
}
|
||||
Attribute::Fn(_, f) => {
|
||||
let mut value = f();
|
||||
while let Attribute::Fn(_, f) = value {
|
||||
|
@ -35,23 +40,19 @@ impl Attribute {
|
|||
}
|
||||
Attribute::Option(_, value) => value
|
||||
.as_ref()
|
||||
.map(|value| format!("{attr_name}=\"{value}\""))
|
||||
.map(|value| format!("{attr_name}=\"{value}\"").into())
|
||||
.unwrap_or_default(),
|
||||
Attribute::Bool(include) => {
|
||||
if *include {
|
||||
attr_name.to_string()
|
||||
} else {
|
||||
String::new()
|
||||
}
|
||||
Cow::Borrowed(if *include { attr_name } else { "" })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts the attribute to its HTML value at that moment, not including
|
||||
/// the attribute name, so it can be rendered on the server.
|
||||
pub fn as_nameless_value_string(&self) -> Option<String> {
|
||||
pub fn as_nameless_value_string(&self) -> Option<Cow<'static, str>> {
|
||||
match self {
|
||||
Attribute::String(value) => Some(value.to_string()),
|
||||
Attribute::String(value) => Some(value.clone()),
|
||||
Attribute::Fn(_, f) => {
|
||||
let mut value = f();
|
||||
while let Attribute::Fn(_, f) = value {
|
||||
|
@ -59,12 +60,10 @@ impl Attribute {
|
|||
}
|
||||
value.as_nameless_value_string()
|
||||
}
|
||||
Attribute::Option(_, value) => {
|
||||
value.as_ref().map(|value| value.to_string())
|
||||
}
|
||||
Attribute::Option(_, value) => value.as_ref().cloned(),
|
||||
Attribute::Bool(include) => {
|
||||
if *include {
|
||||
Some("".to_string())
|
||||
Some("".into())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
@ -109,18 +108,19 @@ pub trait IntoAttribute {
|
|||
}
|
||||
|
||||
impl<T: IntoAttribute + 'static> From<T> for Box<dyn IntoAttribute> {
|
||||
#[inline(always)]
|
||||
fn from(value: T) -> Self {
|
||||
Box::new(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoAttribute for Attribute {
|
||||
#[inline]
|
||||
#[inline(always)]
|
||||
fn into_attribute(self, _: Scope) -> Attribute {
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[inline(always)]
|
||||
fn into_attribute_boxed(self: Box<Self>, _: Scope) -> Attribute {
|
||||
*self
|
||||
}
|
||||
|
@ -128,7 +128,7 @@ impl IntoAttribute for Attribute {
|
|||
|
||||
macro_rules! impl_into_attr_boxed {
|
||||
() => {
|
||||
#[inline]
|
||||
#[inline(always)]
|
||||
fn into_attribute_boxed(self: Box<Self>, cx: Scope) -> Attribute {
|
||||
self.into_attribute(cx)
|
||||
}
|
||||
|
@ -136,6 +136,7 @@ macro_rules! impl_into_attr_boxed {
|
|||
}
|
||||
|
||||
impl IntoAttribute for Option<Attribute> {
|
||||
#[inline(always)]
|
||||
fn into_attribute(self, cx: Scope) -> Attribute {
|
||||
self.unwrap_or(Attribute::Option(cx, None))
|
||||
}
|
||||
|
@ -144,6 +145,25 @@ impl IntoAttribute for Option<Attribute> {
|
|||
}
|
||||
|
||||
impl IntoAttribute for String {
|
||||
#[inline(always)]
|
||||
fn into_attribute(self, _: Scope) -> Attribute {
|
||||
Attribute::String(Cow::Owned(self))
|
||||
}
|
||||
|
||||
impl_into_attr_boxed! {}
|
||||
}
|
||||
|
||||
impl IntoAttribute for &'static str {
|
||||
#[inline(always)]
|
||||
fn into_attribute(self, _: Scope) -> Attribute {
|
||||
Attribute::String(Cow::Borrowed(self))
|
||||
}
|
||||
|
||||
impl_into_attr_boxed! {}
|
||||
}
|
||||
|
||||
impl IntoAttribute for Cow<'static, str> {
|
||||
#[inline(always)]
|
||||
fn into_attribute(self, _: Scope) -> Attribute {
|
||||
Attribute::String(self)
|
||||
}
|
||||
|
@ -152,6 +172,7 @@ impl IntoAttribute for String {
|
|||
}
|
||||
|
||||
impl IntoAttribute for bool {
|
||||
#[inline(always)]
|
||||
fn into_attribute(self, _: Scope) -> Attribute {
|
||||
Attribute::Bool(self)
|
||||
}
|
||||
|
@ -160,6 +181,25 @@ impl IntoAttribute for bool {
|
|||
}
|
||||
|
||||
impl IntoAttribute for Option<String> {
|
||||
#[inline(always)]
|
||||
fn into_attribute(self, cx: Scope) -> Attribute {
|
||||
Attribute::Option(cx, self.map(Cow::Owned))
|
||||
}
|
||||
|
||||
impl_into_attr_boxed! {}
|
||||
}
|
||||
|
||||
impl IntoAttribute for Option<&'static str> {
|
||||
#[inline(always)]
|
||||
fn into_attribute(self, cx: Scope) -> Attribute {
|
||||
Attribute::Option(cx, self.map(Cow::Borrowed))
|
||||
}
|
||||
|
||||
impl_into_attr_boxed! {}
|
||||
}
|
||||
|
||||
impl IntoAttribute for Option<Cow<'static, str>> {
|
||||
#[inline(always)]
|
||||
fn into_attribute(self, cx: Scope) -> Attribute {
|
||||
Attribute::Option(cx, self)
|
||||
}
|
||||
|
@ -181,6 +221,7 @@ where
|
|||
}
|
||||
|
||||
impl<T: IntoAttribute> IntoAttribute for (Scope, T) {
|
||||
#[inline(always)]
|
||||
fn into_attribute(self, _: Scope) -> Attribute {
|
||||
self.1.into_attribute(self.0)
|
||||
}
|
||||
|
@ -200,6 +241,7 @@ impl IntoAttribute for (Scope, Option<Box<dyn IntoAttribute>>) {
|
|||
}
|
||||
|
||||
impl IntoAttribute for (Scope, Box<dyn IntoAttribute>) {
|
||||
#[inline(always)]
|
||||
fn into_attribute(self, _: Scope) -> Attribute {
|
||||
self.1.into_attribute_boxed(self.0)
|
||||
}
|
||||
|
@ -211,7 +253,7 @@ macro_rules! attr_type {
|
|||
($attr_type:ty) => {
|
||||
impl IntoAttribute for $attr_type {
|
||||
fn into_attribute(self, _: Scope) -> Attribute {
|
||||
Attribute::String(self.to_string())
|
||||
Attribute::String(self.to_string().into())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
@ -222,7 +264,7 @@ macro_rules! attr_type {
|
|||
|
||||
impl IntoAttribute for Option<$attr_type> {
|
||||
fn into_attribute(self, cx: Scope) -> Attribute {
|
||||
Attribute::Option(cx, self.map(|n| n.to_string()))
|
||||
Attribute::Option(cx, self.map(|n| n.to_string().into()))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
@ -234,7 +276,6 @@ macro_rules! attr_type {
|
|||
}
|
||||
|
||||
attr_type!(&String);
|
||||
attr_type!(&str);
|
||||
attr_type!(usize);
|
||||
attr_type!(u8);
|
||||
attr_type!(u16);
|
||||
|
@ -251,10 +292,9 @@ attr_type!(f32);
|
|||
attr_type!(f64);
|
||||
attr_type!(char);
|
||||
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
use std::borrow::Cow;
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
#[doc(hidden)]
|
||||
#[inline(never)]
|
||||
pub fn attribute_helper(
|
||||
el: &web_sys::Element,
|
||||
name: Cow<'static, str>,
|
||||
|
@ -277,6 +317,7 @@ pub fn attribute_helper(
|
|||
}
|
||||
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
#[inline(never)]
|
||||
pub(crate) fn attribute_expression(
|
||||
el: &web_sys::Element,
|
||||
attr_name: &str,
|
||||
|
|
|
@ -21,6 +21,7 @@ pub trait IntoClass {
|
|||
}
|
||||
|
||||
impl IntoClass for bool {
|
||||
#[inline(always)]
|
||||
fn into_class(self, _cx: Scope) -> Class {
|
||||
Class::Value(self)
|
||||
}
|
||||
|
@ -30,6 +31,7 @@ impl<T> IntoClass for T
|
|||
where
|
||||
T: Fn() -> bool + 'static,
|
||||
{
|
||||
#[inline(always)]
|
||||
fn into_class(self, cx: Scope) -> Class {
|
||||
let modified_fn = Box::new(self);
|
||||
Class::Fn(cx, modified_fn)
|
||||
|
@ -60,6 +62,7 @@ impl Class {
|
|||
}
|
||||
|
||||
impl<T: IntoClass> IntoClass for (Scope, T) {
|
||||
#[inline(always)]
|
||||
fn into_class(self, _: Scope) -> Class {
|
||||
self.1.into_class(self.0)
|
||||
}
|
||||
|
@ -70,6 +73,7 @@ use std::borrow::Cow;
|
|||
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
#[doc(hidden)]
|
||||
#[inline(never)]
|
||||
pub fn class_helper(
|
||||
el: &web_sys::Element,
|
||||
name: Cow<'static, str>,
|
||||
|
@ -95,6 +99,7 @@ pub fn class_helper(
|
|||
}
|
||||
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
#[inline(never)]
|
||||
pub(crate) fn class_expression(
|
||||
class_list: &web_sys::DomTokenList,
|
||||
class_name: &str,
|
||||
|
|
|
@ -36,6 +36,7 @@ where
|
|||
}
|
||||
|
||||
impl<T: IntoProperty> IntoProperty for (Scope, T) {
|
||||
#[inline(always)]
|
||||
fn into_property(self, _: Scope) -> Property {
|
||||
self.1.into_property(self.0)
|
||||
}
|
||||
|
@ -44,12 +45,14 @@ impl<T: IntoProperty> IntoProperty for (Scope, T) {
|
|||
macro_rules! prop_type {
|
||||
($prop_type:ty) => {
|
||||
impl IntoProperty for $prop_type {
|
||||
#[inline(always)]
|
||||
fn into_property(self, _cx: Scope) -> Property {
|
||||
Property::Value(self.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoProperty for Option<$prop_type> {
|
||||
#[inline(always)]
|
||||
fn into_property(self, _cx: Scope) -> Property {
|
||||
Property::Value(self.into())
|
||||
}
|
||||
|
@ -81,6 +84,7 @@ prop_type!(bool);
|
|||
use std::borrow::Cow;
|
||||
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
#[inline(never)]
|
||||
pub(crate) fn property_helper(
|
||||
el: &web_sys::Element,
|
||||
name: Cow<'static, str>,
|
||||
|
@ -106,6 +110,7 @@ pub(crate) fn property_helper(
|
|||
}
|
||||
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
#[inline(never)]
|
||||
pub(crate) fn property_expression(
|
||||
el: &web_sys::Element,
|
||||
prop_name: &str,
|
||||
|
|
|
@ -35,6 +35,7 @@ use std::cell::Cell;
|
|||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
#[repr(transparent)]
|
||||
pub struct NodeRef<T: ElementDescriptor + 'static>(
|
||||
RwSignal<Option<HtmlElement<T>>>,
|
||||
);
|
||||
|
@ -70,6 +71,7 @@ pub struct NodeRef<T: ElementDescriptor + 'static>(
|
|||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn create_node_ref<T: ElementDescriptor + 'static>(
|
||||
cx: Scope,
|
||||
) -> NodeRef<T> {
|
||||
|
@ -89,6 +91,7 @@ impl<T: ElementDescriptor + 'static> NodeRef<T> {
|
|||
/// Initially, the value will be `None`, but once it is loaded the effect
|
||||
/// will rerun and its value will be `Some(Element)`.
|
||||
#[track_caller]
|
||||
#[inline(always)]
|
||||
pub fn get(&self) -> Option<HtmlElement<T>>
|
||||
where
|
||||
T: Clone,
|
||||
|
@ -120,6 +123,7 @@ impl<T: ElementDescriptor + 'static> NodeRef<T> {
|
|||
|
||||
/// Runs the provided closure when the `NodeRef` has been connected
|
||||
/// with it's [`HtmlElement`].
|
||||
#[inline(always)]
|
||||
pub fn on_load<F>(self, cx: Scope, f: F)
|
||||
where
|
||||
T: Clone,
|
||||
|
@ -148,18 +152,21 @@ cfg_if::cfg_if! {
|
|||
impl<T: Clone + ElementDescriptor + 'static> FnOnce<()> for NodeRef<T> {
|
||||
type Output = Option<HtmlElement<T>>;
|
||||
|
||||
#[inline(always)]
|
||||
extern "rust-call" fn call_once(self, _args: ()) -> Self::Output {
|
||||
self.get()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone + ElementDescriptor + 'static> FnMut<()> for NodeRef<T> {
|
||||
#[inline(always)]
|
||||
extern "rust-call" fn call_mut(&mut self, _args: ()) -> Self::Output {
|
||||
self.get()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone + ElementDescriptor + Clone + 'static> Fn<()> for NodeRef<T> {
|
||||
#[inline(always)]
|
||||
extern "rust-call" fn call(&self, _args: ()) -> Self::Output {
|
||||
self.get()
|
||||
}
|
||||
|
|
|
@ -4,10 +4,12 @@ use std::{any::Any, fmt, rc::Rc};
|
|||
|
||||
/// Wrapper for arbitrary data that can be passed through the view.
|
||||
#[derive(Clone)]
|
||||
#[repr(transparent)]
|
||||
pub struct Transparent(Rc<dyn Any>);
|
||||
|
||||
impl Transparent {
|
||||
/// Creates a new wrapper for this data.
|
||||
#[inline(always)]
|
||||
pub fn new<T>(value: T) -> Self
|
||||
where
|
||||
T: 'static,
|
||||
|
@ -16,6 +18,7 @@ impl Transparent {
|
|||
}
|
||||
|
||||
/// Returns some reference to the inner value if it is of type `T`, or `None` if it isn't.
|
||||
#[inline(always)]
|
||||
pub fn downcast_ref<T>(&self) -> Option<&T>
|
||||
where
|
||||
T: 'static,
|
||||
|
@ -31,6 +34,7 @@ impl fmt::Debug for Transparent {
|
|||
}
|
||||
|
||||
impl PartialEq for Transparent {
|
||||
#[inline(always)]
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
std::ptr::eq(&self.0, &other.0)
|
||||
}
|
||||
|
@ -39,6 +43,7 @@ impl PartialEq for Transparent {
|
|||
impl Eq for Transparent {}
|
||||
|
||||
impl IntoView for Transparent {
|
||||
#[inline(always)]
|
||||
fn into_view(self, _: Scope) -> View {
|
||||
View::Transparent(self)
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@ use std::any::{Any, TypeId};
|
|||
/// #[component]
|
||||
/// pub fn Provider(cx: Scope) -> impl IntoView {
|
||||
/// let (value, set_value) = create_signal(cx, 0);
|
||||
///
|
||||
///
|
||||
/// // the newtype pattern isn't *necessary* here but is a good practice
|
||||
/// // it avoids confusion with other possible future `WriteSignal<bool>` contexts
|
||||
/// // and makes it easier to refer to it in ButtonD
|
||||
|
@ -86,7 +86,7 @@ where
|
|||
/// #[component]
|
||||
/// pub fn Provider(cx: Scope) -> impl IntoView {
|
||||
/// let (value, set_value) = create_signal(cx, 0);
|
||||
///
|
||||
///
|
||||
/// // the newtype pattern isn't *necessary* here but is a good practice
|
||||
/// // it avoids confusion with other possible future `WriteSignal<bool>` contexts
|
||||
/// // and makes it easier to refer to it in ButtonD
|
||||
|
|
|
@ -35,6 +35,7 @@ cfg_if! {
|
|||
|
||||
impl SpecialNonReactiveZone {
|
||||
#[allow(dead_code)] // allowed for SSR
|
||||
#[inline(always)]
|
||||
pub(crate) fn is_inside() -> bool {
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
|
@ -44,6 +45,7 @@ impl SpecialNonReactiveZone {
|
|||
false
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn enter() {
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
|
@ -51,6 +53,7 @@ impl SpecialNonReactiveZone {
|
|||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn exit() {
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
|
|
|
@ -58,6 +58,7 @@ use std::{any::Any, cell::RefCell, marker::PhantomData, rc::Rc};
|
|||
)
|
||||
)]
|
||||
#[track_caller]
|
||||
#[inline(always)]
|
||||
pub fn create_effect<T>(cx: Scope, f: impl Fn(Option<T>) -> T + 'static)
|
||||
where
|
||||
T: 'static,
|
||||
|
@ -66,7 +67,7 @@ where
|
|||
if #[cfg(not(feature = "ssr"))] {
|
||||
let e = cx.runtime.create_effect(f);
|
||||
//eprintln!("created effect {e:?}");
|
||||
cx.with_scope_property(|prop| prop.push(ScopeProperty::Effect(e)))
|
||||
cx.push_scope_property(ScopeProperty::Effect(e))
|
||||
} else {
|
||||
// clear warnings
|
||||
_ = cx;
|
||||
|
@ -113,6 +114,7 @@ where
|
|||
)
|
||||
)]
|
||||
#[track_caller]
|
||||
#[inline(always)]
|
||||
pub fn create_isomorphic_effect<T>(
|
||||
cx: Scope,
|
||||
f: impl Fn(Option<T>) -> T + 'static,
|
||||
|
@ -121,7 +123,7 @@ pub fn create_isomorphic_effect<T>(
|
|||
{
|
||||
let e = cx.runtime.create_effect(f);
|
||||
//eprintln!("created effect {e:?}");
|
||||
cx.with_scope_property(|prop| prop.push(ScopeProperty::Effect(e)))
|
||||
cx.push_scope_property(ScopeProperty::Effect(e))
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
|
@ -136,6 +138,7 @@ pub fn create_isomorphic_effect<T>(
|
|||
)
|
||||
)
|
||||
)]
|
||||
#[inline(always)]
|
||||
pub fn create_render_effect<T>(cx: Scope, f: impl Fn(Option<T>) -> T + 'static)
|
||||
where
|
||||
T: 'static,
|
||||
|
|
|
@ -72,6 +72,7 @@ use std::{any::Any, cell::RefCell, fmt::Debug, marker::PhantomData, rc::Rc};
|
|||
)
|
||||
)]
|
||||
#[track_caller]
|
||||
#[inline(always)]
|
||||
pub fn create_memo<T>(
|
||||
cx: Scope,
|
||||
f: impl Fn(Option<&T>) -> T + 'static,
|
||||
|
@ -218,12 +219,9 @@ impl<T: Clone> SignalGetUntracked<T> for Memo<T> {
|
|||
)
|
||||
)
|
||||
)]
|
||||
#[inline(always)]
|
||||
fn try_get_untracked(&self) -> Option<T> {
|
||||
with_runtime(self.runtime, move |runtime| {
|
||||
self.id.try_with_no_subscription(runtime, T::clone).ok()
|
||||
})
|
||||
.ok()
|
||||
.flatten()
|
||||
self.try_with_untracked(T::clone)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -269,6 +267,7 @@ impl<T> SignalWithUntracked<T> for Memo<T> {
|
|||
)
|
||||
)
|
||||
)]
|
||||
#[inline]
|
||||
fn try_with_untracked<O>(&self, f: impl FnOnce(&T) -> O) -> Option<O> {
|
||||
with_runtime(self.runtime, |runtime| {
|
||||
self.id.try_with_no_subscription(runtime, |v: &T| f(v)).ok()
|
||||
|
@ -309,6 +308,7 @@ impl<T: Clone> SignalGet<T> for Memo<T> {
|
|||
)
|
||||
)]
|
||||
#[track_caller]
|
||||
#[inline(always)]
|
||||
fn get(&self) -> T {
|
||||
self.with(T::clone)
|
||||
}
|
||||
|
@ -327,6 +327,7 @@ impl<T: Clone> SignalGet<T> for Memo<T> {
|
|||
)
|
||||
)]
|
||||
#[track_caller]
|
||||
#[inline(always)]
|
||||
fn try_get(&self) -> Option<T> {
|
||||
self.try_with(T::clone)
|
||||
}
|
||||
|
@ -481,6 +482,8 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
#[cold]
|
||||
#[inline(never)]
|
||||
#[track_caller]
|
||||
fn format_memo_warning(
|
||||
msg: &str,
|
||||
|
@ -503,6 +506,8 @@ fn format_memo_warning(
|
|||
format!("{msg}\n{defined_at_msg}warning happened here: {location}",)
|
||||
}
|
||||
|
||||
#[cold]
|
||||
#[inline(never)]
|
||||
#[track_caller]
|
||||
pub(crate) fn panic_getting_dead_memo(
|
||||
#[cfg(debug_assertions)] defined_at: &'static std::panic::Location<'static>,
|
||||
|
|
|
@ -216,7 +216,7 @@ where
|
|||
}
|
||||
});
|
||||
|
||||
cx.with_scope_property(|prop| prop.push(ScopeProperty::Resource(id)));
|
||||
cx.push_scope_property(ScopeProperty::Resource(id));
|
||||
|
||||
Resource {
|
||||
runtime: cx.runtime,
|
||||
|
@ -339,7 +339,7 @@ where
|
|||
move |_| r.load(false)
|
||||
});
|
||||
|
||||
cx.with_scope_property(|prop| prop.push(ScopeProperty::Resource(id)));
|
||||
cx.push_scope_property(ScopeProperty::Resource(id));
|
||||
|
||||
Resource {
|
||||
runtime: cx.runtime,
|
||||
|
|
|
@ -246,11 +246,11 @@ impl Runtime {
|
|||
|
||||
while let Some(iter) = stack.last_mut() {
|
||||
let res = iter.with_iter_mut(|iter| {
|
||||
let Some(&child) = iter.next() else {
|
||||
let Some(mut child) = iter.next().copied() else {
|
||||
return IterResult::Empty;
|
||||
};
|
||||
|
||||
if let Some(node) = nodes.get_mut(child) {
|
||||
while let Some(node) = nodes.get_mut(child) {
|
||||
if node.state == ReactiveNodeState::Check
|
||||
|| node.state == ReactiveNodeState::DirtyMarked
|
||||
{
|
||||
|
@ -266,11 +266,23 @@ impl Runtime {
|
|||
);
|
||||
|
||||
if let Some(children) = subscribers.get(child) {
|
||||
return IterResult::NewIter(RefIter::new(
|
||||
children.borrow(),
|
||||
|children| children.iter(),
|
||||
));
|
||||
let children = children.borrow();
|
||||
|
||||
if !children.is_empty() {
|
||||
// avoid going through an iterator in the simple psuedo-recursive case
|
||||
if children.len() == 1 {
|
||||
child = children[0];
|
||||
continue;
|
||||
}
|
||||
|
||||
return IterResult::NewIter(RefIter::new(
|
||||
children,
|
||||
|children| children.iter(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
IterResult::Continue
|
||||
|
@ -320,9 +332,11 @@ impl Runtime {
|
|||
}
|
||||
|
||||
pub(crate) fn run_your_effects(&self) {
|
||||
let effects = self.pending_effects.take();
|
||||
for effect_id in effects {
|
||||
self.update_if_necessary(effect_id);
|
||||
if !self.batching.get() {
|
||||
let effects = self.pending_effects.take();
|
||||
for effect_id in effects {
|
||||
self.update_if_necessary(effect_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -346,6 +360,7 @@ impl Debug for Runtime {
|
|||
}
|
||||
/// 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.
|
||||
#[inline(always)] // it monomorphizes anyway
|
||||
pub(crate) fn with_runtime<T>(
|
||||
id: RuntimeId,
|
||||
f: impl FnOnce(&Runtime) -> T,
|
||||
|
@ -406,7 +421,7 @@ impl RuntimeId {
|
|||
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()));
|
||||
let disposer = ScopeDisposer(scope);
|
||||
(scope, disposer)
|
||||
})
|
||||
.expect(
|
||||
|
@ -415,24 +430,34 @@ impl RuntimeId {
|
|||
)
|
||||
}
|
||||
|
||||
pub(crate) fn run_scope_undisposed<T>(
|
||||
pub(crate) fn raw_scope_and_disposer_with_parent(
|
||||
self,
|
||||
f: impl FnOnce(Scope) -> T,
|
||||
parent: Option<Scope>,
|
||||
) -> (T, ScopeId, ScopeDisposer) {
|
||||
) -> (Scope, 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)
|
||||
let disposer = ScopeDisposer(scope);
|
||||
(scope, disposer)
|
||||
})
|
||||
.expect("tried to run scope in a runtime that has been disposed")
|
||||
.expect("tried to crate scope in a runtime that has been disposed")
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) fn run_scope_undisposed<T>(
|
||||
self,
|
||||
f: impl FnOnce(Scope) -> T,
|
||||
parent: Option<Scope>,
|
||||
) -> (T, ScopeId, ScopeDisposer) {
|
||||
let (scope, disposer) = self.raw_scope_and_disposer_with_parent(parent);
|
||||
|
||||
(f(scope), scope.id, disposer)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) fn run_scope<T>(
|
||||
self,
|
||||
f: impl FnOnce(Scope) -> T,
|
||||
|
@ -443,7 +468,6 @@ impl RuntimeId {
|
|||
ret
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
pub(crate) fn create_concrete_signal(
|
||||
self,
|
||||
value: Rc<RefCell<dyn Any>>,
|
||||
|
@ -459,6 +483,7 @@ impl RuntimeId {
|
|||
}
|
||||
|
||||
#[track_caller]
|
||||
#[inline(always)]
|
||||
pub(crate) fn create_signal<T>(
|
||||
self,
|
||||
value: T,
|
||||
|
@ -545,6 +570,7 @@ impl RuntimeId {
|
|||
}
|
||||
|
||||
#[track_caller]
|
||||
#[inline(always)]
|
||||
pub(crate) fn create_rw_signal<T>(self, value: T) -> RwSignal<T>
|
||||
where
|
||||
T: Any + 'static,
|
||||
|
@ -565,7 +591,6 @@ impl RuntimeId {
|
|||
}
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
pub(crate) fn create_concrete_effect(
|
||||
self,
|
||||
value: Rc<RefCell<dyn Any>>,
|
||||
|
@ -593,7 +618,25 @@ impl RuntimeId {
|
|||
.expect("tried to create an effect in a runtime that has been disposed")
|
||||
}
|
||||
|
||||
pub(crate) fn create_concrete_memo(
|
||||
self,
|
||||
value: Rc<RefCell<dyn Any>>,
|
||||
computation: Rc<dyn AnyComputation>,
|
||||
) -> NodeId {
|
||||
with_runtime(self, |runtime| {
|
||||
runtime.nodes.borrow_mut().insert(ReactiveNode {
|
||||
value,
|
||||
// memos are lazy, so are dirty when created
|
||||
// will be run the first time we ask for it
|
||||
state: ReactiveNodeState::Dirty,
|
||||
node_type: ReactiveNodeType::Memo { f: computation },
|
||||
})
|
||||
})
|
||||
.expect("tried to create a memo in a runtime that has been disposed")
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
#[inline(always)]
|
||||
pub(crate) fn create_effect<T>(
|
||||
self,
|
||||
f: impl Fn(Option<T>) -> T + 'static,
|
||||
|
@ -601,21 +644,19 @@ impl RuntimeId {
|
|||
where
|
||||
T: Any + 'static,
|
||||
{
|
||||
#[cfg(debug_assertions)]
|
||||
let defined_at = std::panic::Location::caller();
|
||||
|
||||
let effect = Effect {
|
||||
f,
|
||||
ty: PhantomData,
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at,
|
||||
};
|
||||
|
||||
let value = Rc::new(RefCell::new(None::<T>));
|
||||
self.create_concrete_effect(value, Rc::new(effect))
|
||||
self.create_concrete_effect(
|
||||
Rc::new(RefCell::new(None::<T>)),
|
||||
Rc::new(Effect {
|
||||
f,
|
||||
ty: PhantomData,
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: std::panic::Location::caller(),
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
#[inline(always)]
|
||||
pub(crate) fn create_memo<T>(
|
||||
self,
|
||||
f: impl Fn(Option<&T>) -> T + 'static,
|
||||
|
@ -623,33 +664,20 @@ impl RuntimeId {
|
|||
where
|
||||
T: PartialEq + Any + 'static,
|
||||
{
|
||||
#[cfg(debug_assertions)]
|
||||
let defined_at = std::panic::Location::caller();
|
||||
|
||||
let id = with_runtime(self, |runtime| {
|
||||
runtime.nodes.borrow_mut().insert(ReactiveNode {
|
||||
value: Rc::new(RefCell::new(None::<T>)),
|
||||
// memos are lazy, so are dirty when created
|
||||
// will be run the first time we ask for it
|
||||
state: ReactiveNodeState::Dirty,
|
||||
node_type: ReactiveNodeType::Memo {
|
||||
f: Rc::new(MemoState {
|
||||
f,
|
||||
t: PhantomData,
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at,
|
||||
}),
|
||||
},
|
||||
})
|
||||
})
|
||||
.expect("tried to create a memo in a runtime that has been disposed");
|
||||
|
||||
Memo {
|
||||
runtime: self,
|
||||
id,
|
||||
id: self.create_concrete_memo(
|
||||
Rc::new(RefCell::new(None::<T>)),
|
||||
Rc::new(MemoState {
|
||||
f,
|
||||
t: PhantomData,
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: std::panic::Location::caller(),
|
||||
}),
|
||||
),
|
||||
ty: PhantomData,
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at,
|
||||
defined_at: std::panic::Location::caller(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -746,6 +774,14 @@ impl Runtime {
|
|||
}
|
||||
f
|
||||
}
|
||||
|
||||
pub(crate) fn get_value(
|
||||
&self,
|
||||
node_id: NodeId,
|
||||
) -> Option<Rc<RefCell<dyn Any>>> {
|
||||
let signals = self.nodes.borrow();
|
||||
signals.get(node_id).map(|node| Rc::clone(&node.value))
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Runtime {
|
||||
|
|
|
@ -116,6 +116,7 @@ impl Scope {
|
|||
/// 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.)
|
||||
#[inline(always)]
|
||||
pub fn child_scope(self, f: impl FnOnce(Scope)) -> ScopeDisposer {
|
||||
let (_, disposer) = self.run_child_scope(f);
|
||||
disposer
|
||||
|
@ -130,12 +131,20 @@ impl Scope {
|
|||
/// 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.)
|
||||
#[inline(always)]
|
||||
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));
|
||||
|
||||
self.push_child(child_id);
|
||||
|
||||
(res, disposer)
|
||||
}
|
||||
|
||||
fn push_child(&self, child_id: ScopeId) {
|
||||
_ = with_runtime(self.runtime, |runtime| {
|
||||
let mut children = runtime.scope_children.borrow_mut();
|
||||
children
|
||||
|
@ -147,7 +156,6 @@ impl Scope {
|
|||
.or_default()
|
||||
.push(child_id);
|
||||
});
|
||||
(res, disposer)
|
||||
}
|
||||
|
||||
/// Suspends reactive tracking while running the given function.
|
||||
|
@ -175,13 +183,23 @@ impl Scope {
|
|||
///
|
||||
/// # });
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn untrack<T>(&self, f: impl FnOnce() -> T) -> T {
|
||||
with_runtime(self.runtime, |runtime| {
|
||||
let untracked_result;
|
||||
|
||||
SpecialNonReactiveZone::enter();
|
||||
let prev_observer = runtime.observer.take();
|
||||
let untracked_result = f();
|
||||
runtime.observer.set(prev_observer);
|
||||
|
||||
let prev_observer =
|
||||
SetObserverOnDrop(self.runtime, runtime.observer.take());
|
||||
|
||||
untracked_result = f();
|
||||
|
||||
runtime.observer.set(prev_observer.1);
|
||||
std::mem::forget(prev_observer); // avoid Drop
|
||||
|
||||
SpecialNonReactiveZone::exit();
|
||||
|
||||
untracked_result
|
||||
})
|
||||
.expect(
|
||||
|
@ -191,6 +209,16 @@ impl Scope {
|
|||
}
|
||||
}
|
||||
|
||||
struct SetObserverOnDrop(RuntimeId, Option<NodeId>);
|
||||
|
||||
impl Drop for SetObserverOnDrop {
|
||||
fn drop(&mut self) {
|
||||
_ = with_runtime(self.0, |rt| {
|
||||
rt.observer.set(self.1);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Internals
|
||||
|
||||
impl Scope {
|
||||
|
@ -271,14 +299,11 @@ impl Scope {
|
|||
})
|
||||
}
|
||||
|
||||
pub(crate) fn with_scope_property(
|
||||
&self,
|
||||
f: impl FnOnce(&mut Vec<ScopeProperty>),
|
||||
) {
|
||||
pub(crate) fn push_scope_property(&self, prop: ScopeProperty) {
|
||||
_ = with_runtime(self.runtime, |runtime| {
|
||||
let scopes = runtime.scopes.borrow();
|
||||
if let Some(scope) = scopes.get(self.id) {
|
||||
f(&mut scope.borrow_mut());
|
||||
scope.borrow_mut().push(prop);
|
||||
} else {
|
||||
console_warn(
|
||||
"tried to add property to a scope that has been disposed",
|
||||
|
@ -289,31 +314,36 @@ impl Scope {
|
|||
|
||||
/// Returns the the parent Scope, if any.
|
||||
pub fn parent(&self) -> Option<Scope> {
|
||||
with_runtime(self.runtime, |runtime| {
|
||||
match with_runtime(self.runtime, |runtime| {
|
||||
runtime.scope_parents.borrow().get(self.id).copied()
|
||||
})
|
||||
.ok()
|
||||
.flatten()
|
||||
.map(|id| Scope {
|
||||
runtime: self.runtime,
|
||||
id,
|
||||
})
|
||||
}) {
|
||||
Ok(Some(id)) => Some(Scope {
|
||||
runtime: self.runtime,
|
||||
id,
|
||||
}),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a cleanup function, which will be run when a [Scope] is disposed.
|
||||
///
|
||||
/// 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) {
|
||||
fn push_cleanup(cx: Scope, cleanup_fn: Box<dyn FnOnce()>) {
|
||||
_ = 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));
|
||||
})
|
||||
cleanups.push(cleanup_fn);
|
||||
});
|
||||
}
|
||||
|
||||
/// Creates a cleanup function, which will be run when a [Scope] is disposed.
|
||||
///
|
||||
/// It runs after child scopes have been disposed, but before signals, effects, and resources
|
||||
/// are invalidated.
|
||||
#[inline(always)]
|
||||
pub fn on_cleanup(cx: Scope, cleanup_fn: impl FnOnce() + 'static) {
|
||||
push_cleanup(cx, Box::new(cleanup_fn))
|
||||
}
|
||||
|
||||
slotmap::new_key_type! {
|
||||
|
@ -336,7 +366,8 @@ pub(crate) enum ScopeProperty {
|
|||
/// 1. dispose of all child `Scope`s
|
||||
/// 2. run all cleanup functions defined for this scope by [on_cleanup](crate::on_cleanup).
|
||||
/// 3. dispose of all signals, effects, and resources owned by this `Scope`.
|
||||
pub struct ScopeDisposer(pub(crate) Box<dyn FnOnce()>);
|
||||
#[repr(transparent)]
|
||||
pub struct ScopeDisposer(pub(crate) Scope);
|
||||
|
||||
impl ScopeDisposer {
|
||||
/// Disposes of a reactive [Scope](crate::Scope).
|
||||
|
@ -345,8 +376,9 @@ impl ScopeDisposer {
|
|||
/// 1. dispose of all child `Scope`s
|
||||
/// 2. run all cleanup functions defined for this scope by [on_cleanup](crate::on_cleanup).
|
||||
/// 3. dispose of all signals, effects, and resources owned by this `Scope`.
|
||||
#[inline(always)]
|
||||
pub fn dispose(self) {
|
||||
(self.0)()
|
||||
self.0.dispose()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -476,11 +508,18 @@ impl Scope {
|
|||
///
|
||||
/// # Panics
|
||||
/// Panics if the runtime this scope belongs to has already been disposed.
|
||||
#[inline(always)]
|
||||
pub fn batch<T>(&self, f: impl FnOnce() -> T) -> T {
|
||||
with_runtime(self.runtime, move |runtime| {
|
||||
let batching =
|
||||
SetBatchingOnDrop(self.runtime, runtime.batching.get());
|
||||
runtime.batching.set(true);
|
||||
|
||||
let val = f();
|
||||
runtime.batching.set(false);
|
||||
|
||||
runtime.batching.set(batching.1);
|
||||
std::mem::forget(batching);
|
||||
|
||||
runtime.run_your_effects();
|
||||
val
|
||||
})
|
||||
|
@ -490,6 +529,16 @@ impl Scope {
|
|||
}
|
||||
}
|
||||
|
||||
struct SetBatchingOnDrop(RuntimeId, bool);
|
||||
|
||||
impl Drop for SetBatchingOnDrop {
|
||||
fn drop(&mut self) {
|
||||
_ = with_runtime(self.0, |rt| {
|
||||
rt.batching.set(self.1);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for ScopeDisposer {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_tuple("ScopeDisposer").finish()
|
||||
|
|
|
@ -46,6 +46,7 @@ use std::{
|
|||
/// # })
|
||||
/// # .dispose()
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn create_selector<T>(
|
||||
cx: Scope,
|
||||
source: impl Fn() -> T + Clone + 'static,
|
||||
|
@ -53,7 +54,7 @@ pub fn create_selector<T>(
|
|||
where
|
||||
T: PartialEq + Eq + Debug + Clone + Hash + 'static,
|
||||
{
|
||||
create_selector_with_fn(cx, source, |a, b| a == b)
|
||||
create_selector_with_fn(cx, source, PartialEq::eq)
|
||||
}
|
||||
|
||||
/// Creates a conditional signal that only notifies subscribers when a change
|
||||
|
|
|
@ -10,7 +10,9 @@ use crate::{
|
|||
};
|
||||
use cfg_if::cfg_if;
|
||||
use futures::Stream;
|
||||
use std::{fmt::Debug, marker::PhantomData, pin::Pin, rc::Rc};
|
||||
use std::{
|
||||
any::Any, cell::RefCell, fmt::Debug, marker::PhantomData, pin::Pin, rc::Rc,
|
||||
};
|
||||
use thiserror::Error;
|
||||
|
||||
macro_rules! impl_get_fn_traits {
|
||||
|
@ -20,6 +22,7 @@ macro_rules! impl_get_fn_traits {
|
|||
impl<T: Clone> FnOnce<()> for $ty<T> {
|
||||
type Output = T;
|
||||
|
||||
#[inline(always)]
|
||||
extern "rust-call" fn call_once(self, _args: ()) -> Self::Output {
|
||||
impl_get_fn_traits!(@method_name self $($method_name)?)
|
||||
}
|
||||
|
@ -27,6 +30,7 @@ macro_rules! impl_get_fn_traits {
|
|||
|
||||
#[cfg(not(feature = "stable"))]
|
||||
impl<T: Clone> FnMut<()> for $ty<T> {
|
||||
#[inline(always)]
|
||||
extern "rust-call" fn call_mut(&mut self, _args: ()) -> Self::Output {
|
||||
impl_get_fn_traits!(@method_name self $($method_name)?)
|
||||
}
|
||||
|
@ -34,6 +38,7 @@ macro_rules! impl_get_fn_traits {
|
|||
|
||||
#[cfg(not(feature = "stable"))]
|
||||
impl<T: Clone> Fn<()> for $ty<T> {
|
||||
#[inline(always)]
|
||||
extern "rust-call" fn call(&self, _args: ()) -> Self::Output {
|
||||
impl_get_fn_traits!(@method_name self $($method_name)?)
|
||||
}
|
||||
|
@ -55,6 +60,7 @@ macro_rules! impl_set_fn_traits {
|
|||
impl<T> FnOnce<(T,)> for $ty<T> {
|
||||
type Output = ();
|
||||
|
||||
#[inline(always)]
|
||||
extern "rust-call" fn call_once(self, args: (T,)) -> Self::Output {
|
||||
impl_set_fn_traits!(@method_name self $($method_name)? args)
|
||||
}
|
||||
|
@ -62,6 +68,7 @@ macro_rules! impl_set_fn_traits {
|
|||
|
||||
#[cfg(not(feature = "stable"))]
|
||||
impl<T> FnMut<(T,)> for $ty<T> {
|
||||
#[inline(always)]
|
||||
extern "rust-call" fn call_mut(&mut self, args: (T,)) -> Self::Output {
|
||||
impl_set_fn_traits!(@method_name self $($method_name)? args)
|
||||
}
|
||||
|
@ -69,6 +76,7 @@ macro_rules! impl_set_fn_traits {
|
|||
|
||||
#[cfg(not(feature = "stable"))]
|
||||
impl<T> Fn<(T,)> for $ty<T> {
|
||||
#[inline(always)]
|
||||
extern "rust-call" fn call(&self, args: (T,)) -> Self::Output {
|
||||
impl_set_fn_traits!(@method_name self $($method_name)? args)
|
||||
}
|
||||
|
@ -146,7 +154,7 @@ pub trait SignalSet<T> {
|
|||
/// if the signal is still valid, [`Some(T)`] otherwise.
|
||||
///
|
||||
/// **Note:** `set()` does not auto-memoize, i.e., it will notify subscribers
|
||||
/// even if the value has not actually changed.
|
||||
/// even if the value has not actually changed.
|
||||
fn try_set(&self, new_value: T) -> Option<T>;
|
||||
}
|
||||
|
||||
|
@ -247,6 +255,7 @@ pub trait SignalUpdateUntracked<T> {
|
|||
/// the value the closure returned.
|
||||
#[deprecated = "Please use `try_update_untracked` instead. This method \
|
||||
will be removed in a future version of `leptos`"]
|
||||
#[inline(always)]
|
||||
fn update_returning_untracked<U>(
|
||||
&self,
|
||||
f: impl FnOnce(&mut T) -> U,
|
||||
|
@ -340,7 +349,7 @@ pub fn create_signal<T>(
|
|||
value: T,
|
||||
) -> (ReadSignal<T>, WriteSignal<T>) {
|
||||
let s = cx.runtime.create_signal(value);
|
||||
cx.with_scope_property(|prop| prop.push(ScopeProperty::Signal(s.0.id)));
|
||||
cx.push_scope_property(ScopeProperty::Signal(s.0.id));
|
||||
s
|
||||
}
|
||||
|
||||
|
@ -557,6 +566,7 @@ impl<T> SignalWithUntracked<T> for ReadSignal<T> {
|
|||
)
|
||||
)
|
||||
)]
|
||||
#[inline(always)]
|
||||
fn with_untracked<O>(&self, f: impl FnOnce(&T) -> O) -> O {
|
||||
self.with_no_subscription(f)
|
||||
}
|
||||
|
@ -575,16 +585,16 @@ impl<T> SignalWithUntracked<T> for ReadSignal<T> {
|
|||
)
|
||||
)]
|
||||
#[track_caller]
|
||||
#[inline(always)]
|
||||
fn try_with_untracked<O>(&self, f: impl FnOnce(&T) -> O) -> Option<O> {
|
||||
let diagnostics = diagnostics!(self);
|
||||
|
||||
with_runtime(self.runtime, |runtime| {
|
||||
match with_runtime(self.runtime, |runtime| {
|
||||
self.id.try_with(runtime, f, diagnostics)
|
||||
})
|
||||
.ok()
|
||||
.transpose()
|
||||
.ok()
|
||||
.flatten()
|
||||
}) {
|
||||
Ok(Ok(o)) => Some(o),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -621,6 +631,7 @@ impl<T> SignalWith<T> for ReadSignal<T> {
|
|||
)
|
||||
)]
|
||||
#[track_caller]
|
||||
#[inline(always)]
|
||||
fn with<O>(&self, f: impl FnOnce(&T) -> O) -> O {
|
||||
let diagnostics = diagnostics!(self);
|
||||
|
||||
|
@ -651,6 +662,7 @@ impl<T> SignalWith<T> for ReadSignal<T> {
|
|||
)
|
||||
)]
|
||||
#[track_caller]
|
||||
#[inline(always)]
|
||||
fn try_with<O>(&self, f: impl FnOnce(&T) -> O) -> Option<O> {
|
||||
let diagnostics = diagnostics!(self);
|
||||
|
||||
|
@ -765,6 +777,7 @@ impl<T> ReadSignal<T>
|
|||
where
|
||||
T: 'static,
|
||||
{
|
||||
#[inline(always)]
|
||||
pub(crate) fn with_no_subscription<U>(&self, f: impl FnOnce(&T) -> U) -> U {
|
||||
self.id.with_no_subscription(self.runtime, f)
|
||||
}
|
||||
|
@ -772,6 +785,7 @@ where
|
|||
/// Applies the function to the current Signal, if it exists, and subscribes
|
||||
/// the running effect.
|
||||
#[track_caller]
|
||||
#[inline(always)]
|
||||
pub(crate) fn try_with<U>(
|
||||
&self,
|
||||
f: impl FnOnce(&T) -> U,
|
||||
|
@ -790,13 +804,7 @@ where
|
|||
|
||||
impl<T> Clone for ReadSignal<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
runtime: self.runtime,
|
||||
id: self.id,
|
||||
ty: PhantomData,
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: self.defined_at,
|
||||
}
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -918,6 +926,7 @@ impl<T> SignalUpdateUntracked<T> for WriteSignal<T> {
|
|||
)
|
||||
)
|
||||
)]
|
||||
#[inline(always)]
|
||||
fn update_untracked(&self, f: impl FnOnce(&mut T)) {
|
||||
self.id.update_with_no_effect(self.runtime, f);
|
||||
}
|
||||
|
@ -935,6 +944,7 @@ impl<T> SignalUpdateUntracked<T> for WriteSignal<T> {
|
|||
)
|
||||
)
|
||||
)]
|
||||
#[inline(always)]
|
||||
fn update_returning_untracked<U>(
|
||||
&self,
|
||||
f: impl FnOnce(&mut T) -> U,
|
||||
|
@ -942,6 +952,7 @@ impl<T> SignalUpdateUntracked<T> for WriteSignal<T> {
|
|||
self.id.update_with_no_effect(self.runtime, f)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn try_update_untracked<O>(
|
||||
&self,
|
||||
f: impl FnOnce(&mut T) -> O,
|
||||
|
@ -980,6 +991,7 @@ impl<T> SignalUpdate<T> for WriteSignal<T> {
|
|||
)
|
||||
)
|
||||
)]
|
||||
#[inline(always)]
|
||||
fn update(&self, f: impl FnOnce(&mut T)) {
|
||||
if self.id.update(self.runtime, f).is_none() {
|
||||
warn_updating_dead_signal(
|
||||
|
@ -1002,6 +1014,7 @@ impl<T> SignalUpdate<T> for WriteSignal<T> {
|
|||
)
|
||||
)
|
||||
)]
|
||||
#[inline(always)]
|
||||
fn try_update<O>(&self, f: impl FnOnce(&mut T) -> O) -> Option<O> {
|
||||
self.id.update(self.runtime, f)
|
||||
}
|
||||
|
@ -1073,13 +1086,7 @@ impl<T> SignalDispose for WriteSignal<T> {
|
|||
|
||||
impl<T> Clone for WriteSignal<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
runtime: self.runtime,
|
||||
id: self.id,
|
||||
ty: PhantomData,
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: self.defined_at,
|
||||
}
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1119,7 +1126,7 @@ impl<T> Copy for WriteSignal<T> {}
|
|||
#[track_caller]
|
||||
pub fn create_rw_signal<T>(cx: Scope, value: T) -> RwSignal<T> {
|
||||
let s = cx.runtime.create_rw_signal(value);
|
||||
cx.with_scope_property(|prop| prop.push(ScopeProperty::Signal(s.id)));
|
||||
cx.push_scope_property(ScopeProperty::Signal(s.id));
|
||||
s
|
||||
}
|
||||
|
||||
|
@ -1180,13 +1187,7 @@ where
|
|||
|
||||
impl<T> Clone for RwSignal<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
runtime: self.runtime,
|
||||
id: self.id,
|
||||
ty: self.ty,
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: self.defined_at,
|
||||
}
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1252,6 +1253,7 @@ impl<T> SignalWithUntracked<T> for RwSignal<T> {
|
|||
)
|
||||
)
|
||||
)]
|
||||
#[inline(always)]
|
||||
fn with_untracked<O>(&self, f: impl FnOnce(&T) -> O) -> O {
|
||||
self.id.with_no_subscription(self.runtime, f)
|
||||
}
|
||||
|
@ -1270,16 +1272,16 @@ impl<T> SignalWithUntracked<T> for RwSignal<T> {
|
|||
)
|
||||
)]
|
||||
#[track_caller]
|
||||
#[inline(always)]
|
||||
fn try_with_untracked<O>(&self, f: impl FnOnce(&T) -> O) -> Option<O> {
|
||||
let diagnostics = diagnostics!(self);
|
||||
|
||||
with_runtime(self.runtime, |runtime| {
|
||||
match with_runtime(self.runtime, |runtime| {
|
||||
self.id.try_with(runtime, f, diagnostics)
|
||||
})
|
||||
.ok()
|
||||
.transpose()
|
||||
.ok()
|
||||
.flatten()
|
||||
}) {
|
||||
Ok(Ok(o)) => Some(o),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1339,6 +1341,7 @@ impl<T> SignalUpdateUntracked<T> for RwSignal<T> {
|
|||
)
|
||||
)
|
||||
)]
|
||||
#[inline(always)]
|
||||
fn update_untracked(&self, f: impl FnOnce(&mut T)) {
|
||||
self.id.update_with_no_effect(self.runtime, f);
|
||||
}
|
||||
|
@ -1356,6 +1359,7 @@ impl<T> SignalUpdateUntracked<T> for RwSignal<T> {
|
|||
)
|
||||
)
|
||||
)]
|
||||
#[inline(always)]
|
||||
fn update_returning_untracked<U>(
|
||||
&self,
|
||||
f: impl FnOnce(&mut T) -> U,
|
||||
|
@ -1376,6 +1380,7 @@ impl<T> SignalUpdateUntracked<T> for RwSignal<T> {
|
|||
)
|
||||
)
|
||||
)]
|
||||
#[inline(always)]
|
||||
fn try_update_untracked<O>(
|
||||
&self,
|
||||
f: impl FnOnce(&mut T) -> O,
|
||||
|
@ -1418,6 +1423,7 @@ impl<T> SignalWith<T> for RwSignal<T> {
|
|||
)
|
||||
)]
|
||||
#[track_caller]
|
||||
#[inline(always)]
|
||||
fn with<O>(&self, f: impl FnOnce(&T) -> O) -> O {
|
||||
let diagnostics = diagnostics!(self);
|
||||
|
||||
|
@ -1448,6 +1454,7 @@ impl<T> SignalWith<T> for RwSignal<T> {
|
|||
)
|
||||
)]
|
||||
#[track_caller]
|
||||
#[inline(always)]
|
||||
fn try_with<O>(&self, f: impl FnOnce(&T) -> O) -> Option<O> {
|
||||
let diagnostics = diagnostics!(self);
|
||||
|
||||
|
@ -1567,6 +1574,7 @@ impl<T> SignalUpdate<T> for RwSignal<T> {
|
|||
)
|
||||
)
|
||||
)]
|
||||
#[inline(always)]
|
||||
fn update(&self, f: impl FnOnce(&mut T)) {
|
||||
if self.id.update(self.runtime, f).is_none() {
|
||||
warn_updating_dead_signal(
|
||||
|
@ -1589,6 +1597,7 @@ impl<T> SignalUpdate<T> for RwSignal<T> {
|
|||
)
|
||||
)
|
||||
)]
|
||||
#[inline(always)]
|
||||
fn try_update<O>(&self, f: impl FnOnce(&mut T) -> O) -> Option<O> {
|
||||
self.id.update(self.runtime, f)
|
||||
}
|
||||
|
@ -1857,7 +1866,18 @@ impl NodeId {
|
|||
}
|
||||
}
|
||||
|
||||
fn try_with_no_subscription_inner(
|
||||
&self,
|
||||
runtime: &Runtime,
|
||||
) -> Result<Rc<RefCell<dyn Any>>, SignalError> {
|
||||
runtime.update_if_necessary(*self);
|
||||
let nodes = runtime.nodes.borrow();
|
||||
let node = nodes.get(*self).ok_or(SignalError::Disposed)?;
|
||||
Ok(Rc::clone(&node.value))
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
#[inline(always)]
|
||||
pub(crate) fn try_with_no_subscription<T, U>(
|
||||
&self,
|
||||
runtime: &Runtime,
|
||||
|
@ -1866,13 +1886,7 @@ impl NodeId {
|
|||
where
|
||||
T: 'static,
|
||||
{
|
||||
runtime.update_if_necessary(*self);
|
||||
let value = {
|
||||
let nodes = runtime.nodes.borrow();
|
||||
let node = nodes.get(*self).ok_or(SignalError::Disposed)?;
|
||||
Rc::clone(&node.value)
|
||||
};
|
||||
|
||||
let value = self.try_with_no_subscription_inner(runtime)?;
|
||||
let value = value.borrow();
|
||||
let value = value
|
||||
.downcast_ref::<T>()
|
||||
|
@ -1882,6 +1896,7 @@ impl NodeId {
|
|||
}
|
||||
|
||||
#[track_caller]
|
||||
#[inline(always)]
|
||||
pub(crate) fn try_with<T, U>(
|
||||
&self,
|
||||
runtime: &Runtime,
|
||||
|
@ -1896,6 +1911,7 @@ impl NodeId {
|
|||
self.try_with_no_subscription(runtime, f)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) fn with_no_subscription<T, U>(
|
||||
&self,
|
||||
runtime: RuntimeId,
|
||||
|
@ -1910,6 +1926,7 @@ impl NodeId {
|
|||
.expect("runtime to be alive")
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn update_value<T, U>(
|
||||
&self,
|
||||
runtime: RuntimeId,
|
||||
|
@ -1919,11 +1936,7 @@ impl NodeId {
|
|||
T: 'static,
|
||||
{
|
||||
with_runtime(runtime, |runtime| {
|
||||
let value = {
|
||||
let signals = runtime.nodes.borrow();
|
||||
signals.get(*self).map(|node| Rc::clone(&node.value))
|
||||
};
|
||||
if let Some(value) = value {
|
||||
if let Some(value) = runtime.get_value(*self) {
|
||||
let mut value = value.borrow_mut();
|
||||
if let Some(value) = value.downcast_mut::<T>() {
|
||||
Some(f(value))
|
||||
|
@ -1950,6 +1963,7 @@ impl NodeId {
|
|||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) fn update<T, U>(
|
||||
&self,
|
||||
runtime_id: RuntimeId,
|
||||
|
@ -1959,11 +1973,7 @@ impl NodeId {
|
|||
T: 'static,
|
||||
{
|
||||
with_runtime(runtime_id, |runtime| {
|
||||
let value = {
|
||||
let signals = runtime.nodes.borrow();
|
||||
signals.get(*self).map(|node| Rc::clone(&node.value))
|
||||
};
|
||||
let updated = if let Some(value) = value {
|
||||
let updated = if let Some(value) = runtime.get_value(*self) {
|
||||
let mut value = value.borrow_mut();
|
||||
if let Some(value) = value.downcast_mut::<T>() {
|
||||
Some(f(value))
|
||||
|
@ -1999,6 +2009,7 @@ impl NodeId {
|
|||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) fn update_with_no_effect<T, U>(
|
||||
&self,
|
||||
runtime: RuntimeId,
|
||||
|
@ -2012,6 +2023,8 @@ impl NodeId {
|
|||
}
|
||||
}
|
||||
|
||||
#[cold]
|
||||
#[inline(never)]
|
||||
#[track_caller]
|
||||
fn format_signal_warning(
|
||||
msg: &str,
|
||||
|
@ -2034,6 +2047,8 @@ fn format_signal_warning(
|
|||
format!("{msg}\n{defined_at_msg}warning happened here: {location}",)
|
||||
}
|
||||
|
||||
#[cold]
|
||||
#[inline(never)]
|
||||
#[track_caller]
|
||||
pub(crate) fn panic_getting_dead_signal(
|
||||
#[cfg(debug_assertions)] defined_at: &'static std::panic::Location<'static>,
|
||||
|
@ -2048,6 +2063,8 @@ pub(crate) fn panic_getting_dead_signal(
|
|||
)
|
||||
}
|
||||
|
||||
#[cold]
|
||||
#[inline(never)]
|
||||
#[track_caller]
|
||||
pub(crate) fn warn_updating_dead_signal(
|
||||
#[cfg(debug_assertions)] defined_at: &'static std::panic::Location<'static>,
|
||||
|
|
|
@ -418,7 +418,7 @@ where
|
|||
.insert(Rc::new(RefCell::new(value)))
|
||||
})
|
||||
.unwrap_or_default();
|
||||
cx.with_scope_property(|prop| prop.push(ScopeProperty::StoredValue(id)));
|
||||
cx.push_scope_property(ScopeProperty::StoredValue(id));
|
||||
StoredValue {
|
||||
runtime: cx.runtime,
|
||||
id,
|
||||
|
|
|
@ -3,6 +3,7 @@ use crate::TextProp;
|
|||
/// A collection of additional HTML attributes to be applied to an element,
|
||||
/// each of which may or may not be reactive.
|
||||
#[derive(Default, Clone)]
|
||||
#[repr(transparent)]
|
||||
pub struct AdditionalAttributes(pub(crate) Vec<(String, TextProp)>);
|
||||
|
||||
impl<I, T, U> From<I> for AdditionalAttributes
|
||||
|
@ -22,6 +23,7 @@ where
|
|||
}
|
||||
|
||||
/// Iterator over additional HTML attributes.
|
||||
#[repr(transparent)]
|
||||
pub struct AdditionalAttributesIter<'a>(
|
||||
std::slice::Iter<'a, (String, TextProp)>,
|
||||
);
|
||||
|
@ -29,6 +31,7 @@ pub struct AdditionalAttributesIter<'a>(
|
|||
impl<'a> Iterator for AdditionalAttributesIter<'a> {
|
||||
type Item = &'a (String, TextProp);
|
||||
|
||||
#[inline(always)]
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.0.next()
|
||||
}
|
||||
|
|
|
@ -50,6 +50,7 @@ use leptos::{
|
|||
*,
|
||||
};
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
cell::{Cell, RefCell},
|
||||
collections::HashMap,
|
||||
fmt::Debug,
|
||||
|
@ -101,7 +102,7 @@ pub struct MetaTagsContext {
|
|||
els: Rc<
|
||||
RefCell<
|
||||
HashMap<
|
||||
String,
|
||||
Cow<'static, str>,
|
||||
(HtmlElement<AnyElement>, Scope, Option<web_sys::Element>),
|
||||
>,
|
||||
>,
|
||||
|
@ -131,7 +132,7 @@ impl MetaTagsContext {
|
|||
pub fn register(
|
||||
&self,
|
||||
cx: Scope,
|
||||
id: String,
|
||||
id: Cow<'static, str>,
|
||||
builder_el: HtmlElement<AnyElement>,
|
||||
) {
|
||||
cfg_if! {
|
||||
|
@ -314,6 +315,7 @@ pub fn generate_head_metadata_separated(cx: Scope) -> (String, String) {
|
|||
pub struct TextProp(Rc<dyn Fn() -> String>);
|
||||
|
||||
impl TextProp {
|
||||
#[inline(always)]
|
||||
fn get(&self) -> String {
|
||||
(self.0)()
|
||||
}
|
||||
|
@ -342,6 +344,7 @@ impl<F> From<F> for TextProp
|
|||
where
|
||||
F: Fn() -> String + 'static,
|
||||
{
|
||||
#[inline(always)]
|
||||
fn from(s: F) -> Self {
|
||||
TextProp(Rc::new(s))
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use crate::use_head;
|
||||
use leptos::*;
|
||||
use std::borrow::Cow;
|
||||
|
||||
/// Injects an [HTMLLinkElement](https://developer.mozilla.org/en-US/docs/Web/API/HTMLLinkElement) into the document
|
||||
/// head, accepting any of the valid attributes for that tag.
|
||||
|
@ -28,68 +29,69 @@ pub fn Link(
|
|||
cx: Scope,
|
||||
/// The [`id`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link#attr-id) attribute.
|
||||
#[prop(optional, into)]
|
||||
id: Option<String>,
|
||||
id: Option<Cow<'static, str>>,
|
||||
/// The [`as`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link#attr-as) attribute.
|
||||
#[prop(optional, into)]
|
||||
as_: Option<String>,
|
||||
as_: Option<Cow<'static, str>>,
|
||||
/// The [`crossorigin`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link#attr-crossorigin) attribute.
|
||||
#[prop(optional, into)]
|
||||
crossorigin: Option<String>,
|
||||
crossorigin: Option<Cow<'static, str>>,
|
||||
/// The [`disabled`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link#attr-disabled) attribute.
|
||||
#[prop(optional, into)]
|
||||
disabled: Option<bool>,
|
||||
/// The [`fetchpriority`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link#attr-fetchpriority) attribute.
|
||||
#[prop(optional, into)]
|
||||
fetchpriority: Option<String>,
|
||||
fetchpriority: Option<Cow<'static, str>>,
|
||||
/// The [`href`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link#attr-href) attribute.
|
||||
#[prop(optional, into)]
|
||||
href: Option<String>,
|
||||
href: Option<Cow<'static, str>>,
|
||||
/// The [`hreflang`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link#attr-hreflang) attribute.
|
||||
#[prop(optional, into)]
|
||||
hreflang: Option<String>,
|
||||
hreflang: Option<Cow<'static, str>>,
|
||||
/// The [`imagesizes`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link#attr-imagesizes) attribute.
|
||||
#[prop(optional, into)]
|
||||
imagesizes: Option<String>,
|
||||
imagesizes: Option<Cow<'static, str>>,
|
||||
/// The [`imagesrcset`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link#attr-imagesrcset) attribute.
|
||||
#[prop(optional, into)]
|
||||
imagesrcset: Option<String>,
|
||||
imagesrcset: Option<Cow<'static, str>>,
|
||||
/// The [`integrity`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link#attr-integrity) attribute.
|
||||
#[prop(optional, into)]
|
||||
integrity: Option<String>,
|
||||
integrity: Option<Cow<'static, str>>,
|
||||
/// The [`media`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link#attr-media) attribute.
|
||||
#[prop(optional, into)]
|
||||
media: Option<String>,
|
||||
media: Option<Cow<'static, str>>,
|
||||
/// The [`prefetch`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link#attr-prefetch) attribute.
|
||||
#[prop(optional, into)]
|
||||
prefetch: Option<String>,
|
||||
prefetch: Option<Cow<'static, str>>,
|
||||
/// The [`referrerpolicy`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link#attr-referrerpolicy) attribute.
|
||||
#[prop(optional, into)]
|
||||
referrerpolicy: Option<String>,
|
||||
referrerpolicy: Option<Cow<'static, str>>,
|
||||
/// The [`rel`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link#attr-rel) attribute.
|
||||
#[prop(optional, into)]
|
||||
rel: Option<String>,
|
||||
rel: Option<Cow<'static, str>>,
|
||||
/// The [`sizes`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link#attr-sizes) attribute.
|
||||
#[prop(optional, into)]
|
||||
sizes: Option<String>,
|
||||
sizes: Option<Cow<'static, str>>,
|
||||
/// The [`title`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link#attr-title) attribute.
|
||||
#[prop(optional, into)]
|
||||
title: Option<String>,
|
||||
title: Option<Cow<'static, str>>,
|
||||
/// The [`type`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link#attr-type) attribute.
|
||||
#[prop(optional, into)]
|
||||
type_: Option<String>,
|
||||
type_: Option<Cow<'static, str>>,
|
||||
/// The [`blocking`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link#attr-blocking) attribute.
|
||||
#[prop(optional, into)]
|
||||
blocking: Option<String>,
|
||||
blocking: Option<Cow<'static, str>>,
|
||||
) -> impl IntoView {
|
||||
let meta = use_head(cx);
|
||||
let next_id = meta.tags.get_next_id();
|
||||
let id = id.unwrap_or_else(|| format!("leptos-link-{}", next_id.0));
|
||||
let id: Cow<'static, str> =
|
||||
id.unwrap_or_else(|| format!("leptos-link-{}", next_id.0).into());
|
||||
|
||||
let builder_el = leptos::leptos_dom::html::as_meta_tag({
|
||||
let id = id.clone();
|
||||
move || {
|
||||
leptos::leptos_dom::html::link(cx)
|
||||
.attr("id", &id)
|
||||
.attr("id", id)
|
||||
.attr("as", as_)
|
||||
.attr("crossorigin", crossorigin)
|
||||
.attr("disabled", disabled.unwrap_or(false))
|
||||
|
|
|
@ -53,5 +53,5 @@ pub fn Meta(
|
|||
.attr("content", move || content.as_ref().map(|v| v.get()))
|
||||
});
|
||||
|
||||
meta.tags.register(cx, id, builder_el.into_any());
|
||||
meta.tags.register(cx, id.into(), builder_el.into_any());
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use crate::use_head;
|
||||
use leptos::*;
|
||||
use std::borrow::Cow;
|
||||
|
||||
/// Injects an [HTMLScriptElement](https://developer.mozilla.org/en-US/docs/Web/API/HTMLScriptElement) into the document
|
||||
/// head, accepting any of the valid attributes for that tag.
|
||||
|
@ -25,53 +26,54 @@ pub fn Script(
|
|||
cx: Scope,
|
||||
/// An ID for the `<script>` tag.
|
||||
#[prop(optional, into)]
|
||||
id: Option<String>,
|
||||
id: Option<Cow<'static, str>>,
|
||||
/// The [`async`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#attr-async) attribute.
|
||||
#[prop(optional, into)]
|
||||
async_: Option<String>,
|
||||
async_: Option<Cow<'static, str>>,
|
||||
/// The [`crossorigin`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#attr-crossorigin) attribute.
|
||||
#[prop(optional, into)]
|
||||
crossorigin: Option<String>,
|
||||
crossorigin: Option<Cow<'static, str>>,
|
||||
/// The [`defer`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#attr-defer) attribute.
|
||||
#[prop(optional, into)]
|
||||
defer: Option<String>,
|
||||
defer: Option<Cow<'static, str>>,
|
||||
/// The [`fetchpriority `](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#attr-fetchpriority ) attribute.
|
||||
#[prop(optional, into)]
|
||||
fetchpriority: Option<String>,
|
||||
fetchpriority: Option<Cow<'static, str>>,
|
||||
/// The [`integrity`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#attr-integrity) attribute.
|
||||
#[prop(optional, into)]
|
||||
integrity: Option<String>,
|
||||
integrity: Option<Cow<'static, str>>,
|
||||
/// The [`nomodule`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#attr-nomodule) attribute.
|
||||
#[prop(optional, into)]
|
||||
nomodule: Option<String>,
|
||||
nomodule: Option<Cow<'static, str>>,
|
||||
/// The [`nonce`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#attr-nonce) attribute.
|
||||
#[prop(optional, into)]
|
||||
nonce: Option<String>,
|
||||
nonce: Option<Cow<'static, str>>,
|
||||
/// The [`referrerpolicy`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#attr-referrerpolicy) attribute.
|
||||
#[prop(optional, into)]
|
||||
referrerpolicy: Option<String>,
|
||||
referrerpolicy: Option<Cow<'static, str>>,
|
||||
/// The [`src`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#attr-src) attribute.
|
||||
#[prop(optional, into)]
|
||||
src: Option<String>,
|
||||
src: Option<Cow<'static, str>>,
|
||||
/// The [`type`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#attr-type) attribute.
|
||||
#[prop(optional, into)]
|
||||
type_: Option<String>,
|
||||
type_: Option<Cow<'static, str>>,
|
||||
/// The [`blocking`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#attr-blocking) attribute.
|
||||
#[prop(optional, into)]
|
||||
blocking: Option<String>,
|
||||
blocking: Option<Cow<'static, str>>,
|
||||
/// The content of the `<script>` tag.
|
||||
#[prop(optional)]
|
||||
children: Option<Box<dyn FnOnce(Scope) -> Fragment>>,
|
||||
) -> impl IntoView {
|
||||
let meta = use_head(cx);
|
||||
let next_id = meta.tags.get_next_id();
|
||||
let id = id.unwrap_or_else(|| format!("leptos-link-{}", next_id.0));
|
||||
let id: Cow<'static, str> =
|
||||
id.unwrap_or_else(|| format!("leptos-link-{}", next_id.0).into());
|
||||
|
||||
let builder_el = leptos::leptos_dom::html::as_meta_tag({
|
||||
let id = id.clone();
|
||||
move || {
|
||||
leptos::leptos_dom::html::script(cx)
|
||||
.attr("id", &id)
|
||||
.attr("id", id)
|
||||
.attr("async", async_)
|
||||
.attr("crossorigin", crossorigin)
|
||||
.attr("defer", defer)
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use crate::use_head;
|
||||
use leptos::*;
|
||||
use std::borrow::Cow;
|
||||
|
||||
/// Injects an [HTMLStyleElement](https://developer.mozilla.org/en-US/docs/Web/API/HTMLStyleElement) into the document
|
||||
/// head, accepting any of the valid attributes for that tag.
|
||||
|
@ -25,32 +26,33 @@ pub fn Style(
|
|||
cx: Scope,
|
||||
/// An ID for the `<script>` tag.
|
||||
#[prop(optional, into)]
|
||||
id: Option<String>,
|
||||
id: Option<Cow<'static, str>>,
|
||||
/// The [`media`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/style#attr-media) attribute.
|
||||
#[prop(optional, into)]
|
||||
media: Option<String>,
|
||||
media: Option<Cow<'static, str>>,
|
||||
/// The [`nonce`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/style#attr-nonce) attribute.
|
||||
#[prop(optional, into)]
|
||||
nonce: Option<String>,
|
||||
nonce: Option<Cow<'static, str>>,
|
||||
/// The [`title`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/style#attr-title) attribute.
|
||||
#[prop(optional, into)]
|
||||
title: Option<String>,
|
||||
title: Option<Cow<'static, str>>,
|
||||
/// The [`blocking`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/style#attr-blocking) attribute.
|
||||
#[prop(optional, into)]
|
||||
blocking: Option<String>,
|
||||
blocking: Option<Cow<'static, str>>,
|
||||
/// The content of the `<style>` tag.
|
||||
#[prop(optional)]
|
||||
children: Option<Box<dyn FnOnce(Scope) -> Fragment>>,
|
||||
) -> impl IntoView {
|
||||
let meta = use_head(cx);
|
||||
let next_id = meta.tags.get_next_id();
|
||||
let id = id.unwrap_or_else(|| format!("leptos-link-{}", next_id.0));
|
||||
let id: Cow<'static, str> =
|
||||
id.unwrap_or_else(|| format!("leptos-link-{}", next_id.0).into());
|
||||
|
||||
let builder_el = leptos::leptos_dom::html::as_meta_tag({
|
||||
let id = id.clone();
|
||||
move || {
|
||||
leptos::leptos_dom::html::style(cx)
|
||||
.attr("id", &id)
|
||||
.attr("id", id)
|
||||
.attr("media", media)
|
||||
.attr("nonce", nonce)
|
||||
.attr("title", title)
|
||||
|
|
|
@ -35,12 +35,14 @@ impl std::fmt::Debug for TitleContext {
|
|||
}
|
||||
|
||||
/// A function that is applied to the text value before setting `document.title`.
|
||||
#[repr(transparent)]
|
||||
pub struct Formatter(Box<dyn Fn(String) -> String>);
|
||||
|
||||
impl<F> From<F> for Formatter
|
||||
where
|
||||
F: Fn(String) -> String + 'static,
|
||||
{
|
||||
#[inline(always)]
|
||||
fn from(f: F) -> Formatter {
|
||||
Formatter(Box::new(f))
|
||||
}
|
||||
|
|
|
@ -7,30 +7,36 @@ use thiserror::Error;
|
|||
// that O(n) iteration over a vectorized map is (*probably*) more space-
|
||||
// and time-efficient than hashing and using an actual `HashMap`
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
#[repr(transparent)]
|
||||
pub struct ParamsMap(pub LinearMap<String, String>);
|
||||
|
||||
impl ParamsMap {
|
||||
/// Creates an empty map.
|
||||
#[inline(always)]
|
||||
pub fn new() -> Self {
|
||||
Self(LinearMap::new())
|
||||
}
|
||||
|
||||
/// Creates an empty map with the given capacity.
|
||||
#[inline(always)]
|
||||
pub fn with_capacity(capacity: usize) -> Self {
|
||||
Self(LinearMap::with_capacity(capacity))
|
||||
}
|
||||
|
||||
/// Inserts a value into the map.
|
||||
#[inline(always)]
|
||||
pub fn insert(&mut self, key: String, value: String) -> Option<String> {
|
||||
self.0.insert(key, value)
|
||||
}
|
||||
|
||||
/// Gets a value from the map.
|
||||
#[inline(always)]
|
||||
pub fn get(&self, key: &str) -> Option<&String> {
|
||||
self.0.get(key)
|
||||
}
|
||||
|
||||
/// Removes a value from the map.
|
||||
#[inline(always)]
|
||||
pub fn remove(&mut self, key: &str) -> Option<String> {
|
||||
self.0.remove(key)
|
||||
}
|
||||
|
@ -51,6 +57,7 @@ impl ParamsMap {
|
|||
}
|
||||
|
||||
impl Default for ParamsMap {
|
||||
#[inline(always)]
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
|
@ -95,6 +102,7 @@ where
|
|||
}
|
||||
|
||||
impl Params for () {
|
||||
#[inline(always)]
|
||||
fn from_map(_map: &ParamsMap) -> Result<Self, ParamsError> {
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -15,19 +15,14 @@
|
|||
/// 4. **`async`**: Load all resources on the server. Wait until all data are loaded, and render HTML in one sweep.
|
||||
/// - *Pros*: Better handling for meta tags (because you know async data even before you render the `<head>`). Faster complete load than **synchronous** because async resources begin loading on server.
|
||||
/// - *Cons*: Slower load time/TTFB: you need to wait for all async resources to load before displaying anything on the client.
|
||||
///
|
||||
///
|
||||
/// The mode defaults to out-of-order streaming. For a path that includes multiple nested routes, the most
|
||||
/// restrictive mode will be used: i.e., if even a single nested route asks for `async` rendering, the whole initial
|
||||
/// request will be rendered `async`. (`async` is the most restricted requirement, followed by in-order, out-of-order, and synchronous.)
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
#[derive(Default, Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub enum SsrMode {
|
||||
#[default]
|
||||
OutOfOrder,
|
||||
InOrder,
|
||||
Async,
|
||||
}
|
||||
|
||||
impl Default for SsrMode {
|
||||
fn default() -> Self {
|
||||
Self::OutOfOrder
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue