Merge pull request #831 from novacrazy/main

Various optimizations, size reductions and stability improvements
This commit is contained in:
Greg Johnston 2023-04-08 09:04:13 -04:00 committed by GitHub
commit 4e1f963750
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
47 changed files with 695 additions and 356 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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