Compare commits

..

1 Commits

Author SHA1 Message Date
Greg Johnston
0b62a6db8c chore(ci): cargo install --locked for cargo-leptos installation 2025-02-06 21:07:39 -05:00
31 changed files with 143 additions and 577 deletions

View File

@@ -55,7 +55,7 @@ jobs:
- name: Install wasm-bindgen
run: cargo binstall wasm-bindgen-cli --no-confirm
- name: Install cargo-leptos
run: cargo binstall cargo-leptos --locked --no-confirm
run: cargo binstall cargo-leptos --no-confirm
- name: Install Trunk
uses: jetli/trunk-action@v0.5.0
with:

42
Cargo.lock generated
View File

@@ -1718,7 +1718,7 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]]
name = "leptos"
version = "0.7.7"
version = "0.7.5"
dependencies = [
"any_spawner",
"base64",
@@ -1769,7 +1769,7 @@ dependencies = [
[[package]]
name = "leptos_actix"
version = "0.7.7"
version = "0.7.5"
dependencies = [
"actix-files",
"actix-http",
@@ -1794,7 +1794,7 @@ dependencies = [
[[package]]
name = "leptos_axum"
version = "0.7.7"
version = "0.7.5"
dependencies = [
"any_spawner",
"axum",
@@ -1817,7 +1817,7 @@ dependencies = [
[[package]]
name = "leptos_config"
version = "0.7.7"
version = "0.7.5"
dependencies = [
"config",
"regex",
@@ -1831,7 +1831,7 @@ dependencies = [
[[package]]
name = "leptos_dom"
version = "0.7.7"
version = "0.7.5"
dependencies = [
"js-sys",
"leptos",
@@ -1848,7 +1848,7 @@ dependencies = [
[[package]]
name = "leptos_hot_reload"
version = "0.7.7"
version = "0.7.5"
dependencies = [
"anyhow",
"camino",
@@ -1864,7 +1864,7 @@ dependencies = [
[[package]]
name = "leptos_integration_utils"
version = "0.7.7"
version = "0.7.5"
dependencies = [
"futures",
"hydration_context",
@@ -1877,7 +1877,7 @@ dependencies = [
[[package]]
name = "leptos_macro"
version = "0.7.7"
version = "0.7.5"
dependencies = [
"attribute-derive",
"cfg-if",
@@ -1896,7 +1896,7 @@ dependencies = [
"rstml",
"serde",
"server_fn",
"server_fn_macro 0.7.7",
"server_fn_macro 0.7.5",
"syn 2.0.90",
"tracing",
"trybuild",
@@ -1906,7 +1906,7 @@ dependencies = [
[[package]]
name = "leptos_meta"
version = "0.7.7"
version = "0.7.5"
dependencies = [
"futures",
"indexmap",
@@ -1921,7 +1921,7 @@ dependencies = [
[[package]]
name = "leptos_router"
version = "0.7.7"
version = "0.7.5"
dependencies = [
"any_spawner",
"either_of",
@@ -1945,7 +1945,7 @@ dependencies = [
[[package]]
name = "leptos_router_macro"
version = "0.7.7"
version = "0.7.5"
dependencies = [
"leptos_macro",
"leptos_router",
@@ -1957,7 +1957,7 @@ dependencies = [
[[package]]
name = "leptos_server"
version = "0.7.7"
version = "0.7.5"
dependencies = [
"any_spawner",
"base64",
@@ -2659,7 +2659,7 @@ dependencies = [
[[package]]
name = "reactive_graph"
version = "0.1.7"
version = "0.1.5"
dependencies = [
"any_spawner",
"async-lock",
@@ -2681,7 +2681,7 @@ dependencies = [
[[package]]
name = "reactive_stores"
version = "0.1.7"
version = "0.1.5"
dependencies = [
"any_spawner",
"guardian",
@@ -2698,7 +2698,7 @@ dependencies = [
[[package]]
name = "reactive_stores_macro"
version = "0.1.7"
version = "0.1.5"
dependencies = [
"convert_case 0.6.0",
"proc-macro-error2",
@@ -3155,7 +3155,7 @@ dependencies = [
[[package]]
name = "server_fn"
version = "0.7.7"
version = "0.7.5"
dependencies = [
"actix-web",
"axum",
@@ -3211,7 +3211,7 @@ dependencies = [
[[package]]
name = "server_fn_macro"
version = "0.7.7"
version = "0.7.5"
dependencies = [
"const_format",
"convert_case 0.6.0",
@@ -3223,9 +3223,9 @@ dependencies = [
[[package]]
name = "server_fn_macro_default"
version = "0.7.7"
version = "0.7.5"
dependencies = [
"server_fn_macro 0.7.7",
"server_fn_macro 0.7.5",
"syn 2.0.90",
]
@@ -3419,7 +3419,7 @@ dependencies = [
[[package]]
name = "tachys"
version = "0.1.7"
version = "0.1.5"
dependencies = [
"any_spawner",
"async-trait",

View File

@@ -40,7 +40,7 @@ members = [
exclude = ["benchmarks", "examples", "projects"]
[workspace.package]
version = "0.7.7"
version = "0.7.5"
edition = "2021"
rust-version = "1.76"
@@ -50,26 +50,26 @@ any_spawner = { path = "./any_spawner/", version = "0.2.0" }
const_str_slice_concat = { path = "./const_str_slice_concat", version = "0.1" }
either_of = { path = "./either_of/", version = "0.1.0" }
hydration_context = { path = "./hydration_context", version = "0.2.0" }
leptos = { path = "./leptos", version = "0.7.7" }
leptos_config = { path = "./leptos_config", version = "0.7.7" }
leptos_dom = { path = "./leptos_dom", version = "0.7.7" }
leptos_hot_reload = { path = "./leptos_hot_reload", version = "0.7.7" }
leptos_integration_utils = { path = "./integrations/utils", version = "0.7.7" }
leptos_macro = { path = "./leptos_macro", version = "0.7.7" }
leptos_router = { path = "./router", version = "0.7.7" }
leptos_router_macro = { path = "./router_macro", version = "0.7.7" }
leptos_server = { path = "./leptos_server", version = "0.7.7" }
leptos_meta = { path = "./meta", version = "0.7.7" }
leptos = { path = "./leptos", version = "0.7.5" }
leptos_config = { path = "./leptos_config", version = "0.7.5" }
leptos_dom = { path = "./leptos_dom", version = "0.7.5" }
leptos_hot_reload = { path = "./leptos_hot_reload", version = "0.7.5" }
leptos_integration_utils = { path = "./integrations/utils", version = "0.7.5" }
leptos_macro = { path = "./leptos_macro", version = "0.7.5" }
leptos_router = { path = "./router", version = "0.7.5" }
leptos_router_macro = { path = "./router_macro", version = "0.7.5" }
leptos_server = { path = "./leptos_server", version = "0.7.5" }
leptos_meta = { path = "./meta", version = "0.7.5" }
next_tuple = { path = "./next_tuple", version = "0.1.0" }
oco_ref = { path = "./oco", version = "0.2.0" }
or_poisoned = { path = "./or_poisoned", version = "0.1.0" }
reactive_graph = { path = "./reactive_graph", version = "0.1.7" }
reactive_stores = { path = "./reactive_stores", version = "0.1.7" }
reactive_stores_macro = { path = "./reactive_stores_macro", version = "0.1.7" }
server_fn = { path = "./server_fn", version = "0.7.7" }
server_fn_macro = { path = "./server_fn_macro", version = "0.7.7" }
server_fn_macro_default = { path = "./server_fn/server_fn_macro_default", version = "0.7.7" }
tachys = { path = "./tachys", version = "0.1.7" }
reactive_graph = { path = "./reactive_graph", version = "0.1.5" }
reactive_stores = { path = "./reactive_stores", version = "0.1.3" }
reactive_stores_macro = { path = "./reactive_stores_macro", version = "0.1.0" }
server_fn = { path = "./server_fn", version = "0.7.5" }
server_fn_macro = { path = "./server_fn_macro", version = "0.7.5" }
server_fn_macro_default = { path = "./server_fn/server_fn_macro_default", version = "0.7.5" }
tachys = { path = "./tachys", version = "0.1.5" }
[profile.release]
codegen-units = 1

View File

@@ -1,6 +1,6 @@
[package]
name = "leptos_macro"
version = { workspace = true }
version = "0.7.5"
authors = ["Greg Johnston"]
license = "MIT"
repository = "https://github.com/leptos-rs/leptos"
@@ -34,7 +34,7 @@ log = "0.4.22"
typed-builder = "0.20.0"
trybuild = "1.0"
leptos = { path = "../leptos" }
leptos_router = { path = "../router", features = ["ssr"] }
leptos_router = { path = "../router", features= ["ssr"] }
server_fn = { path = "../server_fn", features = ["cbor"] }
insta = "1.41"
serde = "1.0"

View File

@@ -144,6 +144,8 @@ impl ToTokens for Model {
let (impl_generics, generics, where_clause) =
body.sig.generics.split_for_impl();
let lifetimes = body.sig.generics.lifetimes();
let props_name = format_ident!("{name}Props");
let props_builder_name = format_ident!("{name}PropsBuilder");
let props_serialized_name = format_ident!("{name}PropsSerialized");
@@ -568,7 +570,7 @@ impl ToTokens for Model {
#tracing_instrument_attr
#vis fn #name #impl_generics (
#props_arg
) #ret
) #ret #(+ #lifetimes)*
#where_clause
{
#body

View File

@@ -677,21 +677,17 @@ fn component_macro(
#[allow(non_snake_case, dead_code, clippy::too_many_arguments, clippy::needless_lifetimes)]
#unexpanded
}
} else {
match dummy {
Ok(mut dummy) => {
dummy.sig.ident = unmodified_fn_name_from_fn_name(&dummy.sig.ident);
quote! {
#[doc(hidden)]
#[allow(non_snake_case, dead_code, clippy::too_many_arguments, clippy::needless_lifetimes)]
#dummy
}
}
Err(e) => {
proc_macro_error2::abort!(e.span(), e);
}
} else if let Ok(mut dummy) = dummy {
dummy.sig.ident = unmodified_fn_name_from_fn_name(&dummy.sig.ident);
quote! {
#[doc(hidden)]
#[allow(non_snake_case, dead_code, clippy::too_many_arguments, clippy::needless_lifetimes)]
#dummy
}
}.into()
} else {
quote! {}
}
.into()
}
/// Annotates a struct so that it can be used with your Component as a `slot`.

View File

@@ -154,12 +154,7 @@ fn is_inert_element(orig_node: &Node<impl CustomNode>) -> bool {
Some(value) => {
matches!(&value.value, KVAttributeValue::Expr(expr) if {
if let Expr::Lit(lit) = expr {
let key = attr.key.to_string();
if key.starts_with("style:") || key.starts_with("prop:") || key.starts_with("on:") || key.starts_with("use:") || key.starts_with("bind") {
false
} else {
matches!(&lit.lit, Lit::Str(_))
}
matches!(&lit.lit, Lit::Str(_))
} else {
false
}
@@ -1179,7 +1174,8 @@ pub(crate) fn event_type_and_handler(
) -> (TokenStream, TokenStream, TokenStream) {
let handler = attribute_value(node, false);
let (event_type, is_custom, options) = parse_event_name(name);
let (event_type, is_custom, is_force_undelegated, is_targeted) =
parse_event_name(name);
let event_name_ident = match &node.key {
NodeName::Punctuated(parts) => {
@@ -1197,17 +1193,11 @@ pub(crate) fn event_type_and_handler(
}
_ => unreachable!(),
};
let capture_ident = match &node.key {
NodeName::Punctuated(parts) => {
parts.iter().find(|part| part.to_string() == "capture")
}
_ => unreachable!(),
};
let on = match &node.key {
NodeName::Punctuated(parts) => &parts[0],
_ => unreachable!(),
};
let on = if options.targeted {
let on = if is_targeted {
Ident::new("on_target", on.span()).to_token_stream()
} else {
on.to_token_stream()
@@ -1220,29 +1210,15 @@ pub(crate) fn event_type_and_handler(
event_type
};
let event_type = quote! {
::leptos::tachys::html::event::#event_type
};
let event_type = if options.captured {
let capture = if let Some(capture) = capture_ident {
quote! { #capture }
} else {
quote! { capture }
};
quote! { ::leptos::tachys::html::event::#capture(#event_type) }
} else {
event_type
};
let event_type = if options.undelegated {
let event_type = if is_force_undelegated {
let undelegated = if let Some(undelegated) = undelegated_ident {
quote! { #undelegated }
} else {
quote! { undelegated }
};
quote! { ::leptos::tachys::html::event::#undelegated(#event_type) }
quote! { ::leptos::tachys::html::event::#undelegated(::leptos::tachys::html::event::#event_type) }
} else {
event_type
quote! { ::leptos::tachys::html::event::#event_type }
};
(on, event_type, handler)
@@ -1448,22 +1424,13 @@ fn is_ambiguous_element(tag: &str) -> bool {
tag == "a" || tag == "script" || tag == "title"
}
fn parse_event(event_name: &str) -> (String, EventNameOptions) {
let undelegated = event_name.contains(":undelegated");
let targeted = event_name.contains(":target");
let captured = event_name.contains(":capture");
fn parse_event(event_name: &str) -> (String, bool, bool) {
let is_undelegated = event_name.contains(":undelegated");
let is_targeted = event_name.contains(":target");
let event_name = event_name
.replace(":undelegated", "")
.replace(":target", "")
.replace(":capture", "");
(
event_name,
EventNameOptions {
undelegated,
targeted,
captured,
},
)
.replace(":target", "");
(event_name, is_undelegated, is_targeted)
}
/// Escapes Rust keywords that are also HTML attribute names
@@ -1655,17 +1622,8 @@ const TYPED_EVENTS: [&str; 126] = [
const CUSTOM_EVENT: &str = "Custom";
#[derive(Debug)]
pub(crate) struct EventNameOptions {
undelegated: bool,
targeted: bool,
captured: bool,
}
pub(crate) fn parse_event_name(
name: &str,
) -> (TokenStream, bool, EventNameOptions) {
let (name, options) = parse_event(name);
pub(crate) fn parse_event_name(name: &str) -> (TokenStream, bool, bool, bool) {
let (name, is_force_undelegated, is_targeted) = parse_event(name);
let (event_type, is_custom) = TYPED_EVENTS
.binary_search(&name.as_str())
@@ -1681,7 +1639,7 @@ pub(crate) fn parse_event_name(
} else {
event_type
};
(event_type, is_custom, options)
(event_type, is_custom, is_force_undelegated, is_targeted)
}
fn convert_to_snake_case(name: String) -> String {

View File

@@ -104,18 +104,3 @@ fn component_nostrip() {
/>
};
}
#[component]
fn WithLifetime<'a>(data: &'a str) -> impl IntoView {
_ = data;
"static lifetime"
}
#[test]
fn returns_static_lifetime() {
#[allow(unused)]
fn can_return_impl_intoview_from_body() -> impl IntoView {
let val = String::from("non_static_lifetime");
WithLifetime(WithLifetimeProps::builder().data(&val).build())
}
}

View File

@@ -1,6 +1,6 @@
[package]
name = "leptos_meta"
version = "0.7.7"
version = "0.7.5"
authors = ["Greg Johnston"]
license = "MIT"
repository = "https://github.com/leptos-rs/leptos"

View File

@@ -1,6 +1,6 @@
[package]
name = "reactive_graph"
version = "0.1.7"
version = "0.1.5"
authors = ["Greg Johnston"]
license = "MIT"
readme = "../README.md"

View File

@@ -939,8 +939,7 @@ where
#[track_caller]
pub fn dispatch(&self, input: I) -> ActionAbortHandle {
self.inner
.try_get_value()
.map(|inner| inner.dispatch(input))
.try_with_value(|inner| inner.dispatch(input))
.unwrap_or_else(unwrap_signal!(self))
}
}
@@ -955,8 +954,7 @@ where
#[track_caller]
pub fn dispatch_local(&self, input: I) -> ActionAbortHandle {
self.inner
.try_get_value()
.map(|inner| inner.dispatch_local(input))
.try_with_value(|inner| inner.dispatch_local(input))
.unwrap_or_else(unwrap_signal!(self))
}
}

View File

@@ -324,7 +324,7 @@ macro_rules! spawn_derived {
}
while rx.next().await.is_some() {
let update_if_necessary = !owner.paused() && if $should_track {
let update_if_necessary = if $should_track {
any_subscriber
.with_observer(|| any_subscriber.update_if_necessary())
} else {

View File

@@ -170,10 +170,9 @@ impl Effect<LocalStorage> {
async move {
while rx.next().await.is_some() {
if !owner.paused()
&& (subscriber.with_observer(|| {
subscriber.update_if_necessary()
}) || first_run)
if subscriber
.with_observer(|| subscriber.update_if_necessary())
|| first_run
{
first_run = false;
subscriber.clear_sources(&subscriber);
@@ -322,10 +321,9 @@ impl Effect<LocalStorage> {
async move {
while rx.next().await.is_some() {
if !owner.paused()
&& (subscriber.with_observer(|| {
subscriber.update_if_necessary()
}) || first_run)
if subscriber
.with_observer(|| subscriber.update_if_necessary())
|| first_run
{
subscriber.clear_sources(&subscriber);
@@ -390,10 +388,9 @@ impl Effect<SyncStorage> {
async move {
while rx.next().await.is_some() {
if !owner.paused()
&& (subscriber.with_observer(|| {
subscriber.update_if_necessary()
}) || first_run)
if subscriber
.with_observer(|| subscriber.update_if_necessary())
|| first_run
{
first_run = false;
subscriber.clear_sources(&subscriber);
@@ -437,10 +434,9 @@ impl Effect<SyncStorage> {
async move {
while rx.next().await.is_some() {
if !owner.paused()
&& (subscriber
.with_observer(|| subscriber.update_if_necessary())
|| first_run)
if subscriber
.with_observer(|| subscriber.update_if_necessary())
|| first_run
{
first_run = false;
subscriber.clear_sources(&subscriber);
@@ -491,10 +487,9 @@ impl Effect<SyncStorage> {
async move {
while rx.next().await.is_some() {
if !owner.paused()
&& (subscriber.with_observer(|| {
subscriber.update_if_necessary()
}) || first_run)
if subscriber
.with_observer(|| subscriber.update_if_necessary())
|| first_run
{
subscriber.clear_sources(&subscriber);

View File

@@ -91,11 +91,9 @@ where
async move {
while rx.next().await.is_some() {
if !owner.paused()
&& subscriber.with_observer(|| {
subscriber.update_if_necessary()
})
{
if subscriber.with_observer(|| {
subscriber.update_if_necessary()
}) {
subscriber.clear_sources(&subscriber);
let old_value = mem::take(
@@ -161,10 +159,8 @@ where
async move {
while rx.next().await.is_some() {
if !owner.paused()
&& subscriber.with_observer(|| {
subscriber.update_if_necessary()
})
if subscriber
.with_observer(|| subscriber.update_if_necessary())
{
subscriber.clear_sources(&subscriber);

View File

@@ -130,7 +130,6 @@ impl Owner {
.and_then(|parent| parent.upgrade())
.map(|parent| parent.read().or_poisoned().arena.clone())
.unwrap_or_default(),
paused: false,
})),
#[cfg(feature = "hydration")]
shared_context,
@@ -164,7 +163,6 @@ impl Owner {
children: Default::default(),
#[cfg(feature = "sandboxed-arenas")]
arena: Default::default(),
paused: false,
})),
#[cfg(feature = "hydration")]
shared_context,
@@ -176,10 +174,8 @@ impl Owner {
/// Creates a new `Owner` that is the child of the current `Owner`, if any.
pub fn child(&self) -> Self {
let parent = Some(Arc::downgrade(&self.inner));
let mut inner = self.inner.write().or_poisoned();
#[cfg(feature = "sandboxed-arenas")]
let arena = inner.arena.clone();
let paused = inner.paused;
let arena = self.inner.read().or_poisoned().arena.clone();
let child = Self {
inner: Arc::new(RwLock::new(OwnerInner {
parent,
@@ -189,12 +185,15 @@ impl Owner {
children: Default::default(),
#[cfg(feature = "sandboxed-arenas")]
arena,
paused,
})),
#[cfg(feature = "hydration")]
shared_context: self.shared_context.clone(),
};
inner.children.push(Arc::downgrade(&child.inner));
self.inner
.write()
.or_poisoned()
.children
.push(Arc::downgrade(&child.inner));
child
}
@@ -338,47 +337,6 @@ impl Owner {
inner(Box::new(fun))
}
/// Pauses the execution of side effects for this owner, and any of its descendants.
///
/// If this owner is the owner for an [`Effect`](crate::effect::Effect) or [`RenderEffect`](crate::effect::RenderEffect), this effect will not run until [`Owner::resume`] is called. All children of this effects are also paused.
///
/// Any notifications will be ignored; effects that are notified will paused will not run when
/// resumed, until they are notified again by a source after being resumed.
pub fn pause(&self) {
let mut stack = Vec::with_capacity(16);
stack.push(Arc::downgrade(&self.inner));
while let Some(curr) = stack.pop() {
if let Some(curr) = curr.upgrade() {
let mut curr = curr.write().or_poisoned();
curr.paused = true;
stack.extend(curr.children.iter().map(Weak::clone));
}
}
}
/// Whether this owner has been paused by [`Owner::pause`].
pub fn paused(&self) -> bool {
self.inner.read().or_poisoned().paused
}
/// Resumes side effects that have been paused by [`Owner::pause`].
///
/// All children will also be resumed.
///
/// This will *not* cause side effects that were notified while paused to run, until they are
/// notified again by a source after being resumed.
pub fn resume(&self) {
let mut stack = Vec::with_capacity(16);
stack.push(Arc::downgrade(&self.inner));
while let Some(curr) = stack.pop() {
if let Some(curr) = curr.upgrade() {
let mut curr = curr.write().or_poisoned();
curr.paused = false;
stack.extend(curr.children.iter().map(Weak::clone));
}
}
}
}
#[doc(hidden)]
@@ -405,7 +363,6 @@ pub(crate) struct OwnerInner {
pub children: Vec<Weak<RwLock<OwnerInner>>>,
#[cfg(feature = "sandboxed-arenas")]
arena: Arc<RwLock<ArenaMap>>,
paused: bool,
}
impl Debug for OwnerInner {

View File

@@ -55,7 +55,7 @@ impl<T: AsSubscriberSet + DefinedAt> ReactiveNode for T {
fn mark_subscribers_check(&self) {
if let Some(inner) = self.as_subscriber_set() {
let subs = inner.borrow().read().unwrap().clone();
let subs = inner.borrow().write().unwrap().take();
for sub in subs {
sub.mark_dirty();
}

View File

@@ -196,62 +196,3 @@ async fn recursive_effect_runs_recursively() {
})
.await;
}
#[cfg(feature = "effects")]
#[tokio::test]
async fn paused_effect_pauses() {
use imports::*;
use reactive_graph::owner::StoredValue;
_ = Executor::init_tokio();
let owner = Owner::new();
owner.set();
task::LocalSet::new()
.run_until(async {
let a = RwSignal::new(-1);
// simulate an arbitrary side effect
let runs = StoredValue::new(0);
let owner = StoredValue::new(None);
Effect::new({
move || {
*owner.write_value() = Owner::current();
let _ = a.get();
*runs.write_value() += 1;
}
});
Executor::tick().await;
assert_eq!(runs.get_value(), 1);
println!("setting to 1");
a.set(1);
Executor::tick().await;
assert_eq!(runs.get_value(), 2);
println!("pausing");
owner.get_value().unwrap().pause();
println!("setting to 2");
a.set(2);
Executor::tick().await;
assert_eq!(runs.get_value(), 2);
println!("resuming");
owner.get_value().unwrap().resume();
println!("setting to 3");
a.set(3);
Executor::tick().await;
println!("checking value");
assert_eq!(runs.get_value(), 3);
})
.await
}

View File

@@ -1,6 +1,6 @@
[package]
name = "reactive_stores"
version = "0.1.7"
version = "0.1.5"
authors = ["Greg Johnston"]
license = "MIT"
readme = "../README.md"

View File

@@ -10,6 +10,7 @@ use reactive_graph::{
DefinedAt, IsDisposed, Notify, ReadUntracked, Track, UntrackableGuard,
Write,
},
unwrap_signal,
};
use std::{
fmt::Debug,
@@ -43,14 +44,14 @@ where
self.inner
.try_get_value()
.map(|inner| inner.get_trigger(path))
.unwrap_or_default()
.unwrap_or_else(unwrap_signal!(self))
}
fn path(&self) -> impl IntoIterator<Item = StorePathSegment> {
self.inner
.try_get_value()
.map(|inner| inner.path().into_iter().collect::<Vec<_>>())
.unwrap_or_default()
.unwrap_or_else(unwrap_signal!(self))
}
fn reader(&self) -> Option<Self::Reader> {

View File

@@ -148,8 +148,11 @@ where
{
fn latest_keys(&self) -> Vec<K> {
self.reader()
.map(|r| r.deref().into_iter().map(|n| (self.key_fn)(n)).collect())
.unwrap_or_default()
.expect("trying to update keys")
.deref()
.into_iter()
.map(|n| (self.key_fn)(n))
.collect()
}
}
@@ -480,7 +483,8 @@ where
|| self.inner.latest_keys(),
)
.flatten()
.map(|(_, idx)| idx)?;
.map(|(_, idx)| idx)
.expect("reading from a keyed field that has not yet been created");
Some(WriteGuard::new(
trigger.children,
@@ -650,15 +654,13 @@ where
self.track_field();
// get the current length of the field by accessing slice
let reader = self.reader();
let reader = self
.reader()
.expect("creating iterator from unavailable store field");
let keys = reader
.map(|r| {
r.into_iter()
.map(|item| (self.key_fn)(item))
.collect::<VecDeque<_>>()
})
.unwrap_or_default();
.into_iter()
.map(|item| (self.key_fn)(item))
.collect::<VecDeque<_>>();
// return the iterator
StoreFieldKeyedIter { inner: self, keys }

View File

@@ -9,7 +9,8 @@ use reactive_graph::{
guards::{Plain, UntrackedWriteGuard, WriteGuard},
ArcTrigger,
},
traits::{Track, UntrackableGuard},
traits::{DefinedAt, Track, UntrackableGuard},
unwrap_signal,
};
use std::{iter, ops::Deref, sync::Arc};
@@ -104,7 +105,7 @@ where
self.inner
.try_get_value()
.map(|n| n.get_trigger(path))
.unwrap_or_default()
.unwrap_or_else(unwrap_signal!(self))
}
#[track_caller]
@@ -112,7 +113,7 @@ where
self.inner
.try_get_value()
.map(|n| n.path().into_iter().collect::<Vec<_>>())
.unwrap_or_default()
.unwrap_or_else(unwrap_signal!(self))
}
#[track_caller]

View File

@@ -1,6 +1,6 @@
[package]
name = "reactive_stores_macro"
version = "0.1.7"
version = "0.1.5"
authors = ["Greg Johnston"]
license = "MIT"
readme = "../README.md"

View File

@@ -87,15 +87,15 @@ impl Parse for SubfieldMode {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let mode: Ident = input.parse()?;
if mode == "key" {
let _col: Token![:] = input.parse()?;
let _col: Token!(:) = input.parse()?;
let ty: Type = input.parse()?;
let _eq: Token![=] = input.parse()?;
let closure: ExprClosure = input.parse()?;
Ok(SubfieldMode::Keyed(closure, ty))
let _eq: Token!(=) = input.parse()?;
let ident: ExprClosure = input.parse()?;
Ok(SubfieldMode::Keyed(ident, ty))
} else if mode == "skip" {
Ok(SubfieldMode::Skip)
} else {
Err(input.error("expected `key: <Type> = <closure>`"))
Err(input.error("expected `key = <ident>: <Type>`"))
}
}
}

View File

@@ -1,6 +1,6 @@
[package]
name = "leptos_router"
version = "0.7.7"
version = "0.7.5"
authors = ["Greg Johnston", "Ben Wishovich"]
license = "MIT"
readme = "../README.md"

View File

@@ -1,6 +1,6 @@
[package]
name = "leptos_router_macro"
version = "0.7.7"
version = "0.7.5"
authors = ["Greg Johnston", "Ben Wishovich"]
license = "MIT"
readme = "../README.md"

View File

@@ -1,6 +1,6 @@
[package]
name = "tachys"
version = "0.1.7"
version = "0.1.5"
authors = ["Greg Johnston"]
license = "MIT"
readme = "../README.md"

View File

@@ -283,7 +283,7 @@ html_elements! {
/// The `<em>` HTML element marks text that has stress emphasis. The `<em>` element can be nested, with each level of nesting indicating a greater degree of emphasis.
em HtmlElement [] true,
/// The `<fieldset>` HTML element is used to group several controls as well as labels (label) within a web form.
fieldset HtmlFieldSetElement [disabled, form, name] true,
fieldset HtmlFieldSetElement [] true,
/// The `<figcaption>` HTML element represents a caption or legend describing the rest of the contents of its parent figure element.
figcaption HtmlElement [] true,
/// The `<figure>` HTML element represents self-contained content, potentially with an optional caption, which is specified using the figcaption element. The figure, its caption, and its contents are referenced as a single unit.

View File

@@ -167,9 +167,6 @@ where
el: &crate::renderer::types::Element,
cb: Box<dyn FnMut(crate::renderer::types::Event)>,
name: Cow<'static, str>,
// TODO investigate: does passing this as an option
// (rather than, say, having a const DELEGATED: bool)
// add to binary size?
delegation_key: Option<Cow<'static, str>>,
) -> RemoveEventHandler<crate::renderer::types::Element> {
match delegation_key {
@@ -204,39 +201,6 @@ where
.then(|| self.event.event_delegation_key()),
)
}
/// Attaches the event listener to the element as a listener that is triggered during the capture phase,
/// meaning it will fire before any event listeners further down in the DOM.
pub fn attach_capture(
self,
el: &crate::renderer::types::Element,
) -> RemoveEventHandler<crate::renderer::types::Element> {
fn attach_inner(
el: &crate::renderer::types::Element,
cb: Box<dyn FnMut(crate::renderer::types::Event)>,
name: Cow<'static, str>,
) -> RemoveEventHandler<crate::renderer::types::Element> {
Rndr::add_event_listener_use_capture(el, &name, cb)
}
let mut cb = self.cb.expect("callback removed before attaching").take();
#[cfg(feature = "tracing")]
let span = tracing::Span::current();
let cb = Box::new(move |ev: crate::renderer::types::Event| {
#[cfg(all(debug_assertions, feature = "reactive_graph"))]
let _rx_guard =
reactive_graph::diagnostics::SpecialNonReactiveZone::enter();
#[cfg(feature = "tracing")]
let _tracing_guard = span.enter();
let ev = E::EventType::from(ev);
cb.invoke(ev);
}) as Box<dyn FnMut(crate::renderer::types::Event)>;
attach_inner(el, cb, self.event.name())
}
}
impl<E, F> Debug for On<E, F>
@@ -286,21 +250,13 @@ where
self,
el: &crate::renderer::types::Element,
) -> Self::State {
let cleanup = if E::CAPTURE {
self.attach_capture(el)
} else {
self.attach(el)
};
let cleanup = self.attach(el);
(el.clone(), Some(cleanup))
}
#[inline(always)]
fn build(self, el: &crate::renderer::types::Element) -> Self::State {
let cleanup = if E::CAPTURE {
self.attach_capture(el)
} else {
self.attach(el)
};
let cleanup = self.attach(el);
(el.clone(), Some(cleanup))
}
@@ -310,11 +266,7 @@ where
if let Some(prev) = prev_cleanup.take() {
(prev.into_inner())(el);
}
*prev_cleanup = Some(if E::CAPTURE {
self.attach_capture(el)
} else {
self.attach(el)
});
*prev_cleanup = Some(self.attach(el));
}
fn into_cloneable(self) -> Self::Cloneable {
@@ -382,13 +334,10 @@ pub trait EventDescriptor: Clone {
/// Indicates if this event bubbles. For example, `click` bubbles,
/// but `focus` does not.
///
/// If this is true, then the event will be delegated globally if the `delegation`
/// feature is enabled. Otherwise, event listeners will be directly attached to the element.
/// If this is true, then the event will be delegated globally,
/// otherwise, event listeners will be directly attached to the element.
const BUBBLES: bool;
/// Indicates if this event should be handled during the capture phase.
const CAPTURE: bool = false;
/// The name of the event, such as `click` or `mouseover`.
fn name(&self) -> Cow<'static, str>;
@@ -403,32 +352,6 @@ pub trait EventDescriptor: Clone {
}
}
/// A wrapper that tells the framework to handle an event during the capture phase.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Capture<E> {
inner: E,
}
/// Wraps an event to indicate that it should be handled during the capture phase.
pub fn capture<E>(event: E) -> Capture<E> {
Capture { inner: event }
}
impl<E: EventDescriptor> EventDescriptor for Capture<E> {
type EventType = E::EventType;
const CAPTURE: bool = true;
const BUBBLES: bool = E::BUBBLES;
fn name(&self) -> Cow<'static, str> {
self.inner.name()
}
fn event_delegation_key(&self) -> Cow<'static, str> {
self.inner.event_delegation_key()
}
}
/// A custom event.
#[derive(Debug)]
pub struct Custom<E: FromWasmAbi = web_sys::Event> {

View File

@@ -13,7 +13,7 @@ use once_cell::unsync::Lazy;
use rustc_hash::FxHashSet;
use std::{any::TypeId, borrow::Cow, cell::RefCell};
use wasm_bindgen::{intern, prelude::Closure, JsCast, JsValue};
use web_sys::{AddEventListenerOptions, Comment, HtmlTemplateElement};
use web_sys::{Comment, HtmlTemplateElement};
/// A [`Renderer`](crate::renderer::Renderer) that uses `web-sys` to manipulate DOM elements in the browser.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
@@ -245,44 +245,6 @@ impl Dom {
})
}
pub fn add_event_listener_use_capture(
el: &Element,
name: &str,
cb: Box<dyn FnMut(Event)>,
) -> RemoveEventHandler<Element> {
let cb = wasm_bindgen::closure::Closure::wrap(cb);
let name = intern(name);
let options = AddEventListenerOptions::new();
options.set_capture(true);
or_debug!(
el.add_event_listener_with_callback_and_add_event_listener_options(
name,
cb.as_ref().unchecked_ref(),
&options
),
el,
"addEventListenerUseCapture"
);
// return the remover
RemoveEventHandler::new({
let name = name.to_owned();
// safe to construct this here, because it will only run in the browser
// so it will always be accessed or dropped from the main thread
let cb = send_wrapper::SendWrapper::new(cb);
move |el: &Element| {
or_debug!(
el.remove_event_listener_with_callback(
intern(&name),
cb.as_ref().unchecked_ref()
),
el,
"removeEventListener"
)
}
})
}
pub fn event_target<T>(ev: &Event) -> T
where
T: CastFrom<Element>,

View File

@@ -3,9 +3,7 @@ use super::{
Render, RenderHtml,
};
use crate::{
html::attribute::{Attribute, NextAttribute},
hydration::Cursor,
ssr::StreamBuilder,
html::attribute::Attribute, hydration::Cursor, ssr::StreamBuilder,
};
use either_of::*;
use futures::future::join;
@@ -116,150 +114,6 @@ const fn max_usize(vals: &[usize]) -> usize {
max
}
#[cfg(not(erase_components))]
impl<A, B> NextAttribute for Either<A, B>
where
B: NextAttribute,
A: NextAttribute,
{
type Output<NewAttr: Attribute> = Either<
<A as NextAttribute>::Output<NewAttr>,
<B as NextAttribute>::Output<NewAttr>,
>;
fn add_any_attr<NewAttr: Attribute>(
self,
new_attr: NewAttr,
) -> Self::Output<NewAttr> {
match self {
Either::Left(left) => Either::Left(left.add_any_attr(new_attr)),
Either::Right(right) => Either::Right(right.add_any_attr(new_attr)),
}
}
}
#[cfg(erase_components)]
use crate::html::attribute::any_attribute::{AnyAttribute, IntoAnyAttribute};
#[cfg(erase_components)]
impl<A, B> NextAttribute for Either<A, B>
where
B: IntoAnyAttribute,
A: IntoAnyAttribute,
{
type Output<NewAttr: Attribute> = Vec<AnyAttribute>;
fn add_any_attr<NewAttr: Attribute>(
self,
new_attr: NewAttr,
) -> Self::Output<NewAttr> {
vec![
match self {
Either::Left(left) => left.into_any_attr(),
Either::Right(right) => right.into_any_attr(),
},
new_attr.into_any_attr(),
]
}
}
impl<A, B> Attribute for Either<A, B>
where
B: Attribute,
A: Attribute,
{
const MIN_LENGTH: usize = max_usize(&[A::MIN_LENGTH, B::MIN_LENGTH]);
type AsyncOutput = Either<A::AsyncOutput, B::AsyncOutput>;
type State = Either<A::State, B::State>;
type Cloneable = Either<A::Cloneable, B::Cloneable>;
type CloneableOwned = Either<A::CloneableOwned, B::CloneableOwned>;
fn html_len(&self) -> usize {
match self {
Either::Left(left) => left.html_len(),
Either::Right(right) => right.html_len(),
}
}
fn to_html(
self,
buf: &mut String,
class: &mut String,
style: &mut String,
inner_html: &mut String,
) {
match self {
Either::Left(left) => left.to_html(buf, class, style, inner_html),
Either::Right(right) => {
right.to_html(buf, class, style, inner_html)
}
}
}
fn hydrate<const FROM_SERVER: bool>(
self,
el: &crate::renderer::types::Element,
) -> Self::State {
match self {
Either::Left(left) => Either::Left(left.hydrate::<FROM_SERVER>(el)),
Either::Right(right) => {
Either::Right(right.hydrate::<FROM_SERVER>(el))
}
}
}
fn build(self, el: &crate::renderer::types::Element) -> Self::State {
match self {
Either::Left(left) => Either::Left(left.build(el)),
Either::Right(right) => Either::Right(right.build(el)),
}
}
fn rebuild(self, state: &mut Self::State) {
match self {
Either::Left(left) => {
if let Some(state) = state.as_left_mut() {
left.rebuild(state)
}
}
Either::Right(right) => {
if let Some(state) = state.as_right_mut() {
right.rebuild(state)
}
}
}
}
fn into_cloneable(self) -> Self::Cloneable {
match self {
Either::Left(left) => Either::Left(left.into_cloneable()),
Either::Right(right) => Either::Right(right.into_cloneable()),
}
}
fn into_cloneable_owned(self) -> Self::CloneableOwned {
match self {
Either::Left(left) => Either::Left(left.into_cloneable_owned()),
Either::Right(right) => Either::Right(right.into_cloneable_owned()),
}
}
fn dry_resolve(&mut self) {
match self {
Either::Left(left) => left.dry_resolve(),
Either::Right(right) => right.dry_resolve(),
}
}
async fn resolve(self) -> Self::AsyncOutput {
match self {
Either::Left(left) => Either::Left(left.resolve().await),
Either::Right(right) => Either::Right(right.resolve().await),
}
}
}
impl<A, B> RenderHtml for Either<A, B>
where
A: RenderHtml,

View File

@@ -3,12 +3,7 @@ use super::{
RenderHtml, ToTemplate,
};
use crate::{
html::attribute::{
maybe_next_attr_erasure_macros::{
next_attr_combine, next_attr_output_type,
},
Attribute, AttributeKey, AttributeValue, NextAttribute,
},
html::attribute::{Attribute, AttributeKey, AttributeValue, NextAttribute},
hydration::Cursor,
renderer::{CastFrom, Rndr},
};
@@ -116,13 +111,13 @@ impl<K, const V: &'static str> NextAttribute for StaticAttr<K, V>
where
K: AttributeKey,
{
next_attr_output_type!(Self, NewAttr);
type Output<NewAttr: Attribute> = (Self, NewAttr);
fn add_any_attr<NewAttr: Attribute>(
self,
new_attr: NewAttr,
) -> Self::Output<NewAttr> {
next_attr_combine!(StaticAttr::<K, V> { ty: PhantomData }, new_attr)
(StaticAttr::<K, V> { ty: PhantomData }, new_attr)
}
}