Merge pull request #196 from gbj/cleanup

Clean up issues relating to `0.1.0` merge
This commit is contained in:
Greg Johnston 2022-12-29 20:44:31 -05:00 committed by GitHub
commit 26e90d1959
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 269 additions and 237 deletions

View file

@ -63,9 +63,20 @@ Here are some resources for learning more about Leptos:
- [Common Bugs](https://github.com/gbj/leptos/tree/main/docs/COMMON_BUGS.md) (and how to fix them!)
- Leptos Guide (in progress)
## `nightly` Note
Most of the examples assume youre using `nightly` Rust. If youre on stable, note the following:
Most of the examples assume youre using `nightly` Rust.
To set up your Rust toolchain using `nightly` (and add the ability to compile Rust to WebAssembly, if you havent already)
```
rustup toolchain install nightly
rustup default nightly
rustup target add wasm32-unknown-unknown
```
If youre on `stable`, note the following:
1. You need to enable the `"stable"` flag in `Cargo.toml`: `leptos = { version = "0.1.0-alpha", features = ["stable"] }`
2. `nightly` enables the function call syntax for accessing and setting signals. If youre using `stable`,
@ -73,6 +84,17 @@ Most of the examples assume youre using `nightly` Rust. If youre on stable
[`counters-stable` example](https://github.com/gbj/leptos/blob/main/examples/counters-stable/src/main.rs)
for examples of the correct API.
## `cargo-leptos`
[`cargo-leptos`](https://github.com/akesson/cargo-leptos) is a build tool that's designed to make it easy to build apps that run on both the client and the server, with seamless integration. The best way to get started with a real Leptos project right now is to use `cargo-leptos` and our [starter template](https://github.com/leptos-rs/start).
```bash
cargo install cargo-leptos
cargo leptos new --git https://github.com/leptos-rs/start
cd [your project name]
cargo leptos watch
```
## FAQs
### Can I use this for native GUI?
@ -106,17 +128,16 @@ There are some practical differences that make a significant difference:
- **Read-write segregation:** Leptos, like Solid, encourages read-write segregation between signal getters and setters, so you end up accessing signals with tuples like `let (count, set_count) = create_signal(cx, 0);` _(If you prefer or if it's more convenient for your API, you can use `create_rw_signal` to give a unified read/write signal.)_
- **Signals are functions:** In Leptos, you can call a signal to access it rather than calling a specific method (so, `count()` instead of `count.get()`) This creates a more consistent mental model: accessing a reactive value is always a matter of calling a function. For example:
```rust
let (count, set_count) = create_signal(cx, 0); // a signal
let double_count = move || count() * 2; // a derived signal
let memoized_count = create_memo(cx, move |_| count() * 3); // a memo
// all are accessed by calling them
assert_eq!(count(), 0);
assert_eq!(double_count(), 0);
assert_eq!(memoized_count(), 0);
// this function can accept any of those signals
fn do_work_on_signal(my_signal: impl Fn() -> i32) { ... }
```
```rust
let (count, set_count) = create_signal(cx, 0); // a signal
let double_count = move || count() * 2; // a derived signal
let memoized_count = create_memo(cx, move |_| count() * 3); // a memo
// all are accessed by calling them
assert_eq!(count(), 0);
assert_eq!(double_count(), 0);
assert_eq!(memoized_count(), 0);
// this function can accept any of those signals
fn do_work_on_signal(my_signal: impl Fn() -> i32) { ... }
```
- **Signals and scopes are `'static`:** Both Leptos and Sycamore ease the pain of moving signals in closures (in particular, event listeners) by making them `Copy`, to avoid the `{ let count = count.clone(); move |_| ... }` that's very familiar in Rust UI code. Sycamore does this by using bump allocation to tie the lifetimes of its signals to its scopes: since references are `Copy`, `&'a Signal<T>` can be moved into a closure. Leptos does this by using arena allocation and passing around indices: types like `ReadSignal<T>`, `WriteSignal<T>`, and `Memo<T>` are actually wrapper for indices into an arena. This means that both scopes and signals are both `Copy` and `'static` in Leptos, which means that they can be moved easily into closures without adding lifetime complexity.

View file

@ -10,7 +10,7 @@ This document is intended as a running list of common issues, with example code
```rust
let (a, set_a) = create_signal(cx, 0);
let (b, set_a) = create_signal(cx, false);
let (b, set_b) = create_signal(cx, false);
create_effect(cx, move |_| {
if a() > 5 {

View file

@ -38,6 +38,7 @@ ssr = [
"leptos_meta/ssr",
"leptos_router/ssr",
]
stable = ["leptos/stable", "leptos_router/stable"]
[package.metadata.cargo-all-features]
denylist = ["actix-files", "actix-web", "leptos_actix"]

View file

@ -22,42 +22,45 @@ pub fn Story(cx: Scope) -> impl IntoView {
view! { cx,
<>
<Meta name="description" content=meta_description/>
{move || story.read().map(|story| match story {
None => view! { cx, <div class="item-view">"Error loading this story."</div> },
Some(story) => view! { cx,
<div class="item-view">
<div class="item-view-header">
<a href=story.url target="_blank">
<h1>{story.title}</h1>
</a>
<span class="host">
"("{story.domain}")"
</span>
{story.user.map(|user| view! { cx, <p class="meta">
{story.points}
" points | by "
<A href=format!("/users/{}", user)>{user.clone()}</A>
{format!(" {}", story.time_ago)}
</p>})}
<Suspense fallback=|| view! { cx, "Loading..." }>
{move || story.read().map(|story| match story {
None => view! { cx, <div class="item-view">"Error loading this story."</div> },
Some(story) => view! { cx,
<div class="item-view">
<div class="item-view-header">
<a href=story.url target="_blank">
<h1>{story.title}</h1>
</a>
<span class="host">
"("{story.domain}")"
</span>
{story.user.map(|user| view! { cx, <p class="meta">
{story.points}
" points | by "
<A href=format!("/users/{}", user)>{user.clone()}</A>
{format!(" {}", story.time_ago)}
</p>})}
</div>
<div class="item-view-comments">
<p class="item-view-comments-header">
{if story.comments_count.unwrap_or_default() > 0 {
format!("{} comments", story.comments_count.unwrap_or_default())
} else {
"No comments yet.".into()
}}
</p>
<ul class="comment-children">
<For
each=move || story.comments.clone().unwrap_or_default()
key=|comment| comment.id
view=move |comment| view! { cx, <Comment comment /> }
/>
</ul>
</div>
</div>
<div class="item-view-comments">
<p class="item-view-comments-header">
{if story.comments_count.unwrap_or_default() > 0 {
format!("{} comments", story.comments_count.unwrap_or_default())
} else {
"No comments yet.".into()
}}
</p>
<ul class="comment-children">
<For
each=move || story.comments.clone().unwrap_or_default()
key=|comment| comment.id
view=move |comment| view! { cx, <Comment comment /> }
/>
</ul>
</div>
</div>
}})}
}})
}
</Suspense>
</>
}
}

View file

@ -18,28 +18,30 @@ pub fn User(cx: Scope) -> impl IntoView {
);
view! { cx,
<div class="user-view">
{move || user.read().map(|user| match user {
None => view! { cx, <h1>"User not found."</h1> }.into_any(),
Some(user) => view! { cx,
<div>
<h1>"User: " {&user.id}</h1>
<ul class="meta">
<li>
<span class="label">"Created: "</span> {user.created}
</li>
<li>
<span class="label">"Karma: "</span> {user.karma}
</li>
{user.about.as_ref().map(|about| view! { cx, <li inner_html=about class="about"></li> })}
</ul>
<p class="links">
<a href=format!("https://news.ycombinator.com/submitted?id={}", user.id)>"submissions"</a>
" | "
<a href=format!("https://news.ycombinator.com/threads?id={}", user.id)>"comments"</a>
</p>
</div>
}.into_any()
})}
<Suspense fallback=|| view! { cx, "Loading..." }>
{move || user.read().map(|user| match user {
None => view! { cx, <h1>"User not found."</h1> }.into_any(),
Some(user) => view! { cx,
<div>
<h1>"User: " {&user.id}</h1>
<ul class="meta">
<li>
<span class="label">"Created: "</span> {user.created}
</li>
<li>
<span class="label">"Karma: "</span> {user.karma}
</li>
{user.about.as_ref().map(|about| view! { cx, <li inner_html=about class="about"></li> })}
</ul>
<p class="links">
<a href=format!("https://news.ycombinator.com/submitted?id={}", user.id)>"submissions"</a>
" | "
<a href=format!("https://news.ycombinator.com/threads?id={}", user.id)>"comments"</a>
</p>
</div>
}.into_any()
})}
</Suspense>
</div>
}
}

View file

@ -49,7 +49,7 @@ skip_feature_sets = [["csr", "ssr"], ["csr", "hydrate"], ["ssr", "hydrate"]]
# The name used by wasm-bindgen/cargo-leptos for the JS/WASM bundle. Defaults to the crate name
output-name = "leptos_hackernews"
# The site root folder is where cargo-leptos generate all output. WARNING: all content of this folder will be erased on a rebuild. Use it in your server setup.
site-root = "target/site"
site-root = "/pkg"
# The site-root relative folder where all compiled output (JS, WASM and CSS) is written
# Defaults to pkg
site-pkg-dir = "pkg"

View file

@ -22,42 +22,45 @@ pub fn Story(cx: Scope) -> impl IntoView {
view! { cx,
<>
<Meta name="description" content=meta_description/>
{move || story.read().map(|story| match story {
None => view! { cx, <div class="item-view">"Error loading this story."</div> },
Some(story) => view! { cx,
<div class="item-view">
<div class="item-view-header">
<a href=story.url target="_blank">
<h1>{story.title}</h1>
</a>
<span class="host">
"("{story.domain}")"
</span>
{story.user.map(|user| view! { cx, <p class="meta">
{story.points}
" points | by "
<A href=format!("/users/{}", user)>{user.clone()}</A>
{format!(" {}", story.time_ago)}
</p>})}
<Suspense fallback=|| view! { cx, "Loading..." }>
{move || story.read().map(|story| match story {
None => view! { cx, <div class="item-view">"Error loading this story."</div> },
Some(story) => view! { cx,
<div class="item-view">
<div class="item-view-header">
<a href=story.url target="_blank">
<h1>{story.title}</h1>
</a>
<span class="host">
"("{story.domain}")"
</span>
{story.user.map(|user| view! { cx, <p class="meta">
{story.points}
" points | by "
<A href=format!("/users/{}", user)>{user.clone()}</A>
{format!(" {}", story.time_ago)}
</p>})}
</div>
<div class="item-view-comments">
<p class="item-view-comments-header">
{if story.comments_count.unwrap_or_default() > 0 {
format!("{} comments", story.comments_count.unwrap_or_default())
} else {
"No comments yet.".into()
}}
</p>
<ul class="comment-children">
<For
each=move || story.comments.clone().unwrap_or_default()
key=|comment| comment.id
view=move |comment| view! { cx, <Comment comment /> }
/>
</ul>
</div>
</div>
<div class="item-view-comments">
<p class="item-view-comments-header">
{if story.comments_count.unwrap_or_default() > 0 {
format!("{} comments", story.comments_count.unwrap_or_default())
} else {
"No comments yet.".into()
}}
</p>
<ul class="comment-children">
<For
each=move || story.comments.clone().unwrap_or_default()
key=|comment| comment.id
view=move |comment| view! { cx, <Comment comment /> }
/>
</ul>
</div>
</div>
}})}
}})
}
</Suspense>
</>
}
}

View file

@ -18,28 +18,30 @@ pub fn User(cx: Scope) -> impl IntoView {
);
view! { cx,
<div class="user-view">
{move || user.read().map(|user| match user {
None => view! { cx, <h1>"User not found."</h1> }.into_any(),
Some(user) => view! { cx,
<div>
<h1>"User: " {&user.id}</h1>
<ul class="meta">
<li>
<span class="label">"Created: "</span> {user.created}
</li>
<li>
<span class="label">"Karma: "</span> {user.karma}
</li>
{user.about.as_ref().map(|about| view! { cx, <li inner_html=about class="about"></li> })}
</ul>
<p class="links">
<a href=format!("https://news.ycombinator.com/submitted?id={}", user.id)>"submissions"</a>
" | "
<a href=format!("https://news.ycombinator.com/threads?id={}", user.id)>"comments"</a>
</p>
</div>
}.into_any()
})}
<Suspense fallback=|| view! { cx, "Loading..." }>
{move || user.read().map(|user| match user {
None => view! { cx, <h1>"User not found."</h1> }.into_any(),
Some(user) => view! { cx,
<div>
<h1>"User: " {&user.id}</h1>
<ul class="meta">
<li>
<span class="label">"Created: "</span> {user.created}
</li>
<li>
<span class="label">"Karma: "</span> {user.karma}
</li>
{user.about.as_ref().map(|about| view! { cx, <li inner_html=about class="about"></li> })}
</ul>
<p class="links">
<a href=format!("https://news.ycombinator.com/submitted?id={}", user.id)>"submissions"</a>
" | "
<a href=format!("https://news.ycombinator.com/threads?id={}", user.id)>"comments"</a>
</p>
</div>
}.into_any()
})}
</Suspense>
</div>
}
}

View file

@ -144,7 +144,7 @@ pub fn Settings(cx: Scope) -> impl IntoView {
<fieldset>
<legend>"Name"</legend>
<input type="text" name="first_name" placeholder="First"/>
<input type="text" name="first_name" placeholder="Last"/>
<input type="text" name="last_name" placeholder="Last"/>
</fieldset>
<pre>"This page is just a placeholder."</pre>
</form>

View file

@ -14,9 +14,9 @@ cfg_if! {
}
pub fn register_server_functions() {
GetTodos::register();
AddTodo::register();
DeleteTodo::register();
_ = GetTodos::register();
_ = AddTodo::register();
_ = DeleteTodo::register();
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, sqlx::FromRow)]
@ -151,7 +151,7 @@ pub fn Todos(cx: Scope) -> impl IntoView {
</label>
<input type="submit" value="Add"/>
</MultiActionForm>
<Suspense fallback=move || view! {cx, <p>"Loading..."</p> }>
<Transition fallback=move || view! {cx, <p>"Loading..."</p> }>
{
let delete_todo = delete_todo.clone();
move || {
@ -221,7 +221,7 @@ pub fn Todos(cx: Scope) -> impl IntoView {
}
}
}
</Suspense>
</Transition>
</div>
}
}

View file

@ -9,6 +9,7 @@ cfg_if! {
use actix_files::{Files};
use actix_web::*;
use crate::todo::*;
use std::net::SocketAddr;
#[get("/style.css")]
async fn css() -> impl Responder {

View file

@ -13,9 +13,9 @@ cfg_if! {
}
pub fn register_server_functions() {
GetTodos::register();
AddTodo::register();
DeleteTodo::register();
_ = GetTodos::register();
_ = AddTodo::register();
_ = DeleteTodo::register();
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, sqlx::FromRow)]
@ -135,7 +135,7 @@ pub fn Todos(cx: Scope) -> impl IntoView {
</label>
<input type="submit" value="Add"/>
</MultiActionForm>
<Suspense fallback=move || view! {cx, <p>"Loading..."</p> }>
<Transition fallback=move || view! {cx, <p>"Loading..."</p> }>
{
let delete_todo = delete_todo.clone();
move || {
@ -205,7 +205,7 @@ pub fn Todos(cx: Scope) -> impl IntoView {
}
}
}
</Suspense>
</Transition>
</div>
}
}

View file

@ -187,8 +187,10 @@ where
// If it was, `DynChild` no longer "owns" that child, and
// is therefore no longer sound to unmount it from the DOM
// or to reuse it in the case of a text node
let was_child_moved =
child.get_closing_node().next_sibling().as_ref() != Some(&closing);
// FIXME this was breaking DynChild updates on text nodes, at least...
let was_child_moved = false;
//child.get_closing_node().next_sibling().as_ref() != Some(&closing);
// If the previous child was a text node, we would like to
// make use of it again if our current child is also a text

View file

@ -49,7 +49,7 @@ use std::fmt::Debug;
/// ```
pub fn create_effect<T>(cx: Scope, f: impl Fn(Option<T>) -> T + 'static)
where
T: Debug + 'static,
T: 'static,
{
cfg_if! {
if #[cfg(not(feature = "ssr"))] {
@ -90,7 +90,7 @@ where
/// # }).dispose();
pub fn create_isomorphic_effect<T>(cx: Scope, f: impl Fn(Option<T>) -> T + 'static)
where
T: Debug + 'static,
T: 'static,
{
let e = cx.runtime.create_effect(f);
cx.with_scope_property(|prop| prop.push(ScopeProperty::Effect(e)))
@ -99,7 +99,7 @@ where
#[doc(hidden)]
pub fn create_render_effect<T>(cx: Scope, f: impl Fn(Option<T>) -> T + 'static)
where
T: Debug + 'static,
T: 'static,
{
create_effect(cx, f);
}

View file

@ -215,7 +215,7 @@ where
#[cfg(not(feature = "stable"))]
impl<T> FnOnce<()> for Memo<T>
where
T: Debug + Clone,
T: Clone,
{
type Output = T;
@ -227,7 +227,7 @@ where
#[cfg(not(feature = "stable"))]
impl<T> FnMut<()> for Memo<T>
where
T: Debug + Clone,
T: Clone,
{
extern "rust-call" fn call_mut(&mut self, _args: ()) -> Self::Output {
self.get()
@ -237,7 +237,7 @@ where
#[cfg(not(feature = "stable"))]
impl<T> Fn<()> for Memo<T>
where
T: Debug + Clone,
T: Clone,
{
extern "rust-call" fn call(&self, _args: ()) -> Self::Output {
self.get()

View file

@ -66,7 +66,7 @@ pub fn create_resource<S, T, Fu>(
) -> Resource<S, T>
where
S: PartialEq + Debug + Clone + 'static,
T: Debug + Serializable + 'static,
T: Serializable + 'static,
Fu: Future<Output = T> + 'static,
{
// can't check this on the server without running the future
@ -91,7 +91,7 @@ pub fn create_resource_with_initial_value<S, T, Fu>(
) -> Resource<S, T>
where
S: PartialEq + Debug + Clone + 'static,
T: Debug + Serializable + 'static,
T: Serializable + 'static,
Fu: Future<Output = T> + 'static,
{
let resolved = initial_value.is_some();
@ -173,7 +173,7 @@ pub fn create_local_resource<S, T, Fu>(
) -> Resource<S, T>
where
S: PartialEq + Debug + Clone + 'static,
T: Debug + 'static,
T: 'static,
Fu: Future<Output = T> + 'static,
{
let initial_value = None;
@ -195,7 +195,7 @@ pub fn create_local_resource_with_initial_value<S, T, Fu>(
) -> Resource<S, T>
where
S: PartialEq + Debug + Clone + 'static,
T: Debug + 'static,
T: 'static,
Fu: Future<Output = T> + 'static,
{
let resolved = initial_value.is_some();
@ -244,7 +244,7 @@ where
fn load_resource<S, T>(_cx: Scope, _id: ResourceId, r: Rc<ResourceState<S, T>>)
where
S: PartialEq + Debug + Clone + 'static,
T: Debug + 'static,
T: 'static,
{
r.load(false)
}
@ -253,7 +253,7 @@ where
fn load_resource<S, T>(cx: Scope, id: ResourceId, r: Rc<ResourceState<S, T>>)
where
S: PartialEq + Debug + Clone + 'static,
T: Debug + Serializable + 'static,
T: Serializable + 'static,
{
use wasm_bindgen::{JsCast, UnwrapThrowExt};
@ -267,18 +267,8 @@ where
let res = T::from_json(&data).expect_throw("could not deserialize Resource JSON");
// if we're under Suspense, the HTML has already streamed in so we can just set it
// if not under Suspense, there will be a hydration mismatch, so let's wait a tick
if use_context::<SuspenseContext>(cx).is_some() {
r.set_value.update(|n| *n = Some(res));
r.set_loading.update(|n| *n = false);
} else {
let r = Rc::clone(&r);
spawn_local(async move {
r.set_value.update(|n| *n = Some(res));
r.set_loading.update(|n| *n = false);
});
}
r.set_value.update(|n| *n = Some(res));
r.set_loading.update(|n| *n = false);
// for reactivity
r.source.subscribe();
@ -326,8 +316,8 @@ where
impl<S, T> Resource<S, T>
where
S: Debug + Clone + 'static,
T: Debug + 'static,
S: Clone + 'static,
T: 'static,
{
/// Clones and returns the current value of the resource ([Option::None] if the
/// resource is still pending). Also subscribes the running effect to this
@ -433,8 +423,8 @@ where
#[derive(Debug, PartialEq, Eq, Hash)]
pub struct Resource<S, T>
where
S: Debug + 'static,
T: Debug + 'static,
S: 'static,
T: 'static,
{
runtime: RuntimeId,
pub(crate) id: ResourceId,
@ -450,8 +440,8 @@ slotmap::new_key_type! {
impl<S, T> Clone for Resource<S, T>
where
S: Debug + Clone + 'static,
T: Debug + Clone + 'static,
S: Clone + 'static,
T: Clone + 'static,
{
fn clone(&self) -> Self {
Self {
@ -465,16 +455,16 @@ where
impl<S, T> Copy for Resource<S, T>
where
S: Debug + Clone + 'static,
T: Debug + Clone + 'static,
S: Clone + 'static,
T: Clone + 'static,
{
}
#[cfg(not(feature = "stable"))]
impl<S, T> FnOnce<()> for Resource<S, T>
where
S: Debug + Clone + 'static,
T: Debug + Clone + 'static,
S: Clone + 'static,
T: Clone + 'static,
{
type Output = Option<T>;
@ -486,8 +476,8 @@ where
#[cfg(not(feature = "stable"))]
impl<S, T> FnMut<()> for Resource<S, T>
where
S: Debug + Clone + 'static,
T: Debug + Clone + 'static,
S: Clone + 'static,
T: Clone + 'static,
{
extern "rust-call" fn call_mut(&mut self, _args: ()) -> Self::Output {
self.read()
@ -497,8 +487,8 @@ where
#[cfg(not(feature = "stable"))]
impl<S, T> Fn<()> for Resource<S, T>
where
S: Debug + Clone + 'static,
T: Debug + Clone + 'static,
S: Clone + 'static,
T: Clone + 'static,
{
extern "rust-call" fn call(&self, _args: ()) -> Self::Output {
self.read()
@ -509,7 +499,7 @@ where
pub(crate) struct ResourceState<S, T>
where
S: 'static,
T: Debug + 'static,
T: 'static,
{
scope: Scope,
value: ReadSignal<Option<T>>,
@ -526,8 +516,8 @@ where
impl<S, T> ResourceState<S, T>
where
S: Debug + Clone + 'static,
T: Debug + 'static,
S: Clone + 'static,
T: 'static,
{
pub fn read(&self) -> Option<T>
where
@ -666,8 +656,8 @@ pub(crate) trait SerializableResource {
impl<S, T> SerializableResource for ResourceState<S, T>
where
S: Debug + Clone,
T: Debug + Serializable,
S: Clone,
T: Serializable,
{
fn as_any(&self) -> &dyn Any {
self
@ -686,11 +676,7 @@ pub(crate) trait UnserializableResource {
fn as_any(&self) -> &dyn Any;
}
impl<S, T> UnserializableResource for ResourceState<S, T>
where
S: Debug,
T: Debug,
{
impl<S, T> UnserializableResource for ResourceState<S, T> {
fn as_any(&self) -> &dyn Any {
self
}

View file

@ -234,8 +234,8 @@ impl Runtime {
state: Rc<ResourceState<S, T>>,
) -> ResourceId
where
S: Debug + Clone + 'static,
T: Debug + 'static,
S: Clone + 'static,
T: 'static,
{
self.resources
.borrow_mut()
@ -247,8 +247,8 @@ impl Runtime {
state: Rc<ResourceState<S, T>>,
) -> ResourceId
where
S: Debug + Clone + 'static,
T: Debug + Serializable + 'static,
S: Clone + 'static,
T: Serializable + 'static,
{
self.resources
.borrow_mut()
@ -261,8 +261,8 @@ impl Runtime {
f: impl FnOnce(&ResourceState<S, T>) -> U,
) -> U
where
S: Debug + 'static,
T: Debug + 'static,
S: 'static,
T: 'static,
{
let resources = self.resources.borrow();
let res = resources.get(id);

View file

@ -228,7 +228,7 @@ impl<T> Copy for ReadSignal<T> {}
#[cfg(not(feature = "stable"))]
impl<T> FnOnce<()> for ReadSignal<T>
where
T: Debug + Clone,
T: Clone,
{
type Output = T;
@ -240,7 +240,7 @@ where
#[cfg(not(feature = "stable"))]
impl<T> FnMut<()> for ReadSignal<T>
where
T: Debug + Clone,
T: Clone,
{
extern "rust-call" fn call_mut(&mut self, _args: ()) -> Self::Output {
self.get()
@ -250,7 +250,7 @@ where
#[cfg(not(feature = "stable"))]
impl<T> Fn<()> for ReadSignal<T>
where
T: Debug + Clone,
T: Clone,
{
extern "rust-call" fn call(&self, _args: ()) -> Self::Output {
self.get()
@ -734,7 +734,7 @@ where
#[cfg(not(feature = "stable"))]
impl<T> FnOnce<()> for RwSignal<T>
where
T: Debug + Clone,
T: Clone,
{
type Output = T;
@ -746,7 +746,7 @@ where
#[cfg(not(feature = "stable"))]
impl<T> FnMut<()> for RwSignal<T>
where
T: Debug + Clone,
T: Clone,
{
extern "rust-call" fn call_mut(&mut self, _args: ()) -> Self::Output {
self.get()
@ -756,7 +756,7 @@ where
#[cfg(not(feature = "stable"))]
impl<T> Fn<()> for RwSignal<T>
where
T: Debug + Clone,
T: Clone,
{
extern "rust-call" fn call(&self, _args: ()) -> Self::Output {
self.get()

View file

@ -9,8 +9,8 @@ description = "Tools to set HTML metadata in the Leptos web framework."
[dependencies]
cfg-if = "1"
leptos = { path = "../leptos", version = "0.1.0-alpha", default-features = false }
tracing = "0.1"
leptos = { path = "../leptos", default-features = false }
typed-builder = "0.11"
[dependencies.web-sys]
version = "0.3"
@ -18,6 +18,10 @@ features = ["HtmlLinkElement", "HtmlMetaElement", "HtmlTitleElement"]
[features]
default = ["csr"]
csr = ["leptos/csr", "leptos/tracing"]
hydrate = ["leptos/hydrate", "leptos/tracing"]
ssr = ["leptos/ssr", "leptos/tracing"]
csr = ["leptos/csr"]
hydrate = ["leptos/hydrate"]
ssr = ["leptos/ssr"]
stable = ["leptos/stable"]
[package.metadata.cargo-all-features]
denylist = ["stable"]

View file

@ -57,7 +57,8 @@ default = ["csr"]
csr = ["leptos/csr"]
hydrate = ["leptos/hydrate"]
ssr = ["leptos/ssr", "dep:url", "dep:regex"]
stable = ["leptos/stable"]
[package.metadata.cargo-all-features]
# No need to test optional dependencies as they are enabled by the ssr feature
denylist = ["url", "regex"]
denylist = ["url", "regex", "stable"]

View file

@ -64,7 +64,6 @@ impl std::fmt::Debug for RouterContextInner {
f.debug_struct("RouterContextInner")
.field("location", &self.location)
.field("base", &self.base)
.field("history", &std::any::type_name_of_val(&self.history))
.field("cx", &self.cx)
.field("reference", &self.reference)
.field("set_reference", &self.set_reference)
@ -99,13 +98,15 @@ impl RouterContext {
let base = base.unwrap_or_default();
let base_path = resolve_path("", base, None);
if let Some(base_path) = &base_path && source.with(|s| s.value.is_empty()) {
history.navigate(&LocationChange {
value: base_path.to_string(),
replace: true,
scroll: false,
state: State(None)
});
if let Some(base_path) = &base_path {
if source.with(|s| s.value.is_empty()) {
history.navigate(&LocationChange {
value: base_path.to_string(),
replace: true,
scroll: false,
state: State(None)
});
}
}
// the current URL

View file

@ -126,17 +126,21 @@ where
}
}
auto trait NotOption {}
impl<T> !NotOption for Option<T> {}
cfg_if::cfg_if! {
if #[cfg(not(feature = "stable"))] {
auto trait NotOption {}
impl<T> !NotOption for Option<T> {}
impl<T> IntoParam for T
where
T: FromStr + NotOption,
<T as FromStr>::Err: std::error::Error + Send + Sync + 'static,
{
fn into_param(value: Option<&str>, name: &str) -> Result<Self, ParamsError> {
let value = value.ok_or_else(|| ParamsError::MissingParam(name.to_string()))?;
Self::from_str(value).map_err(|e| ParamsError::Params(Rc::new(e)))
impl<T> IntoParam for T
where
T: FromStr + NotOption,
<T as FromStr>::Err: std::error::Error + Send + Sync + 'static,
{
fn into_param(value: Option<&str>, name: &str) -> Result<Self, ParamsError> {
let value = value.ok_or_else(|| ParamsError::MissingParam(name.to_string()))?;
Self::from_str(value).map_err(|e| ParamsError::Params(Rc::new(e)))
}
}
}
}

View file

@ -136,10 +136,9 @@
//!
//! ```
#![feature(auto_traits)]
#![feature(let_chains)]
#![feature(negative_impls)]
#![feature(type_name_of_val)]
#![cfg_attr(not(feature = "stable"), feature(auto_traits))]
#![cfg_attr(not(feature = "stable"), feature(negative_impls))]
#![cfg_attr(not(feature = "stable"), feature(type_name_of_val))]
mod components;
mod history;

View file

@ -80,13 +80,15 @@ impl Matcher {
path.push_str(loc_segment);
}
if let Some(splat) = &self.splat && !splat.is_empty() {
let value = if len_diff > 0 {
loc_segments[self.len..].join("/")
} else {
"".into()
};
params.insert(splat.into(), value);
if let Some(splat) = &self.splat {
if !splat.is_empty() {
let value = if len_diff > 0 {
loc_segments[self.len..].join("/")
} else {
"".into()
};
params.insert(splat.into(), value);
}
}
Some(PathMatch { path, params })