mirror of
https://github.com/leptos-rs/leptos.git
synced 2025-12-28 12:31:55 -05:00
Compare commits
29 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
91fb315fe0 | ||
|
|
6954b77b62 | ||
|
|
77176f8395 | ||
|
|
344b79a01b | ||
|
|
051059c761 | ||
|
|
3c540dd858 | ||
|
|
4125688a0a | ||
|
|
bd3b962cfb | ||
|
|
5dd3c217c4 | ||
|
|
ae00e5ae13 | ||
|
|
1ce671ba08 | ||
|
|
ec9f26bd9f | ||
|
|
831eae31bc | ||
|
|
ff6ae5de25 | ||
|
|
c21712ba04 | ||
|
|
45771b6fd3 | ||
|
|
f3557970a7 | ||
|
|
c87ef331b0 | ||
|
|
e767518142 | ||
|
|
f94b681118 | ||
|
|
9c50e49253 | ||
|
|
57c7097ede | ||
|
|
1a06e0eee8 | ||
|
|
ce9af4a685 | ||
|
|
e0c79eb8d8 | ||
|
|
9fd972971e | ||
|
|
9473220639 | ||
|
|
ae11812dc6 | ||
|
|
4c55c25445 |
380
Cargo.lock
generated
380
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
36
Cargo.toml
36
Cargo.toml
@@ -40,7 +40,7 @@ members = [
|
||||
exclude = ["benchmarks", "examples", "projects"]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.8.2"
|
||||
version = "0.8.3"
|
||||
edition = "2021"
|
||||
rust-version = "1.80"
|
||||
|
||||
@@ -51,25 +51,25 @@ any_spawner = { path = "./any_spawner/", version = "0.3.0" }
|
||||
const_str_slice_concat = { path = "./const_str_slice_concat", version = "0.1" }
|
||||
either_of = { path = "./either_of/", version = "0.1.5" }
|
||||
hydration_context = { path = "./hydration_context", version = "0.3.0" }
|
||||
leptos = { path = "./leptos", version = "0.8.2" }
|
||||
leptos_config = { path = "./leptos_config", version = "0.8.2" }
|
||||
leptos_dom = { path = "./leptos_dom", version = "0.8.2" }
|
||||
leptos_hot_reload = { path = "./leptos_hot_reload", version = "0.8.2" }
|
||||
leptos_integration_utils = { path = "./integrations/utils", version = "0.8.2" }
|
||||
leptos_macro = { path = "./leptos_macro", version = "0.8.2" }
|
||||
leptos_router = { path = "./router", version = "0.8.2" }
|
||||
leptos_router_macro = { path = "./router_macro", version = "0.8.2" }
|
||||
leptos_server = { path = "./leptos_server", version = "0.8.2" }
|
||||
leptos_meta = { path = "./meta", version = "0.8.2" }
|
||||
leptos = { path = "./leptos", version = "0.8.3" }
|
||||
leptos_config = { path = "./leptos_config", version = "0.8.3" }
|
||||
leptos_dom = { path = "./leptos_dom", version = "0.8.3" }
|
||||
leptos_hot_reload = { path = "./leptos_hot_reload", version = "0.8.3" }
|
||||
leptos_integration_utils = { path = "./integrations/utils", version = "0.8.3" }
|
||||
leptos_macro = { path = "./leptos_macro", version = "0.8.3" }
|
||||
leptos_router = { path = "./router", version = "0.8.3" }
|
||||
leptos_router_macro = { path = "./router_macro", version = "0.8.3" }
|
||||
leptos_server = { path = "./leptos_server", version = "0.8.3" }
|
||||
leptos_meta = { path = "./meta", version = "0.8.3" }
|
||||
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.2.0" }
|
||||
reactive_stores = { path = "./reactive_stores", version = "0.2.0" }
|
||||
reactive_stores_macro = { path = "./reactive_stores_macro", version = "0.2.0" }
|
||||
server_fn = { path = "./server_fn", version = "0.8.2" }
|
||||
server_fn_macro = { path = "./server_fn_macro", version = "0.8.2" }
|
||||
server_fn_macro_default = { path = "./server_fn/server_fn_macro_default", version = "0.8.2" }
|
||||
server_fn = { path = "./server_fn", version = "0.8.3" }
|
||||
server_fn_macro = { path = "./server_fn_macro", version = "0.8.3" }
|
||||
server_fn_macro_default = { path = "./server_fn/server_fn_macro_default", version = "0.8.3" }
|
||||
tachys = { path = "./tachys", version = "0.2.0" }
|
||||
|
||||
# members deps
|
||||
@@ -108,7 +108,7 @@ serde = { default-features = false, version = "1.0.219" }
|
||||
parking_lot = { default-features = false, version = "0.12.4" }
|
||||
axum = { default-features = false, version = "0.8.4" }
|
||||
serde_qs = { default-features = false, version = "0.15.0" }
|
||||
syn = { default-features = false, version = "2.0.101" }
|
||||
syn = { default-features = false, version = "2.0.104" }
|
||||
xxhash-rust = { default-features = false, version = "0.8.15" }
|
||||
paste = { default-features = false, version = "1.0.15" }
|
||||
quote = { default-features = false, version = "1.0.40" }
|
||||
@@ -116,10 +116,10 @@ web-sys = { default-features = false, version = "0.3.77" }
|
||||
js-sys = { default-features = false, version = "0.3.77" }
|
||||
rand = { default-features = false, version = "0.9.1" }
|
||||
serde-lite = { default-features = false, version = "0.5.0" }
|
||||
tokio-tungstenite = { default-features = false, version = "0.26.2" }
|
||||
tokio-tungstenite = { default-features = false, version = "0.27.0" }
|
||||
serial_test = { default-features = false, version = "3.2.0" }
|
||||
erased = { default-features = false, version = "0.1.2" }
|
||||
glib = { default-features = false, version = "0.20.10" }
|
||||
glib = { default-features = false, version = "0.20.12" }
|
||||
async-trait = { default-features = false, version = "0.1.88" }
|
||||
typed-builder-macro = { default-features = false, version = "0.21.0" }
|
||||
linear-map = { default-features = false, version = "1.2.0" }
|
||||
@@ -127,7 +127,7 @@ anyhow = { default-features = false, version = "1.0.98" }
|
||||
walkdir = { default-features = false, version = "2.5.0" }
|
||||
actix-ws = { default-features = false, version = "0.3.0" }
|
||||
tower-http = { default-features = false, version = "0.6.4" }
|
||||
prettyplease = { default-features = false, version = "0.2.33" }
|
||||
prettyplease = { default-features = false, version = "0.2.35" }
|
||||
inventory = { default-features = false, version = "0.3.20" }
|
||||
config = { default-features = false, version = "0.15.11" }
|
||||
camino = { default-features = false, version = "1.1.9" }
|
||||
|
||||
@@ -118,7 +118,7 @@ The `nightly` feature enables the function call syntax for accessing and setting
|
||||
|
||||
```bash
|
||||
cargo install cargo-leptos
|
||||
cargo leptos new --git https://github.com/leptos-rs/start
|
||||
cargo leptos new --git https://github.com/leptos-rs/start-axum
|
||||
cd [your project name]
|
||||
cargo leptos watch
|
||||
```
|
||||
|
||||
20
examples/regression/e2e/features/issue_4088.feature
Normal file
20
examples/regression/e2e/features/issue_4088.feature
Normal file
@@ -0,0 +1,20 @@
|
||||
@check_issue_4088
|
||||
Feature: Check that issue 4088 does not reappear
|
||||
|
||||
Scenario: I can see the navbar
|
||||
Given I see the app
|
||||
And I can access regression test 4088
|
||||
Then I see the navbar
|
||||
|
||||
Scenario: The user info is shared via context
|
||||
Given I see the app
|
||||
And I can access regression test 4088
|
||||
When I select the link Class 1
|
||||
Then I see the result is the string Assignments for team of user with id 42
|
||||
|
||||
Scenario: The user info is shared via context
|
||||
Given I see the app
|
||||
And I can access regression test 4088
|
||||
When I select the link Class 1
|
||||
When I refresh the browser
|
||||
Then I see the result is the string Assignments for team of user with id 42
|
||||
8
examples/regression/e2e/features/pr_4015.feature
Normal file
8
examples/regression/e2e/features/pr_4015.feature
Normal file
@@ -0,0 +1,8 @@
|
||||
@check_pr_4015
|
||||
Feature: Check that PR 4015 does not regress
|
||||
|
||||
Scenario: The correct text appears
|
||||
Given I see the app
|
||||
And I can access regression test 4015
|
||||
Then I see the result is the string Some(42)
|
||||
|
||||
@@ -24,3 +24,25 @@ Feature: Regression from pull request 4091
|
||||
| test1 |
|
||||
| 4091 Home |
|
||||
Then I see the result is empty
|
||||
|
||||
Scenario: I can see the navbar
|
||||
Given I see the app
|
||||
And I can access regression test 4091
|
||||
Then I see the navbar
|
||||
|
||||
Scenario: If I navigate to home and back, I can still see the navbar
|
||||
Given I see the app
|
||||
And I can access regression test 4091
|
||||
When I select the following links
|
||||
| Home |
|
||||
| 4091 |
|
||||
Then I see the navbar
|
||||
|
||||
Scenario: The signal is not disposed too early
|
||||
Given I see the app
|
||||
And I can access regression test 4091
|
||||
When I select the following links
|
||||
| test1 |
|
||||
| Home |
|
||||
| 4091 |
|
||||
Then I see the navbar
|
||||
@@ -11,3 +11,10 @@ pub async fn result_text_is(
|
||||
assert_eq!(&actual, expected_text);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn element_exists(client: &Client, id: &str) -> Result<()> {
|
||||
find::element_by_id(client, id)
|
||||
.await
|
||||
.expect(&format!("could not find element with id `{id}`"));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -2,9 +2,7 @@ use anyhow::{Ok, Result};
|
||||
use fantoccini::{elements::Element, Client, Locator};
|
||||
|
||||
pub async fn text_at_id(client: &Client, id: &str) -> Result<String> {
|
||||
let element = client
|
||||
.wait()
|
||||
.for_element(Locator::Id(id))
|
||||
let element = element_by_id(client, id)
|
||||
.await
|
||||
.expect(format!("no such element with id `{}`", id).as_str());
|
||||
let text = element.text().await?;
|
||||
@@ -19,3 +17,7 @@ pub async fn link_with_text(client: &Client, text: &str) -> Result<Element> {
|
||||
.expect(format!("Link not found by `{}`", text).as_str());
|
||||
Ok(link)
|
||||
}
|
||||
|
||||
pub async fn element_by_id(client: &Client, id: &str) -> Result<Element> {
|
||||
Ok(client.wait().for_element(Locator::Id(id)).await?)
|
||||
}
|
||||
|
||||
@@ -3,9 +3,7 @@ use anyhow::{Ok, Result};
|
||||
use cucumber::then;
|
||||
|
||||
#[then(regex = r"^I see the result is empty$")]
|
||||
async fn i_see_the_result_is_empty(
|
||||
world: &mut AppWorld,
|
||||
) -> Result<()> {
|
||||
async fn i_see_the_result_is_empty(world: &mut AppWorld) -> Result<()> {
|
||||
let client = &world.client;
|
||||
check::result_text_is(client, "").await?;
|
||||
Ok(())
|
||||
@@ -20,3 +18,10 @@ async fn i_see_the_result_is_the_string(
|
||||
check::result_text_is(client, &text).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[then(regex = r"^I see the navbar$")]
|
||||
async fn i_see_the_navbar(world: &mut AppWorld) -> Result<()> {
|
||||
let client = &world.client;
|
||||
check::element_exists(client, "nav").await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::pr_4091::Routes4091;
|
||||
use crate::{issue_4088::Routes4088, pr_4015::Routes4015, pr_4091::Routes4091};
|
||||
use leptos::prelude::*;
|
||||
use leptos_meta::{MetaTags, *};
|
||||
use leptos_router::{
|
||||
@@ -35,6 +35,8 @@ pub fn App() -> impl IntoView {
|
||||
<Routes fallback>
|
||||
<Route path=path!("") view=HomePage/>
|
||||
<Routes4091/>
|
||||
<Routes4015/>
|
||||
<Routes4088/>
|
||||
</Routes>
|
||||
</main>
|
||||
</Router>
|
||||
@@ -55,6 +57,8 @@ fn HomePage() -> impl IntoView {
|
||||
<nav>
|
||||
<ul>
|
||||
<li><a href="/4091/">"4091"</a></li>
|
||||
<li><a href="/4015/">"4015"</a></li>
|
||||
<li><a href="/4088/">"4088"</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
}
|
||||
|
||||
119
examples/regression/src/issue_4088.rs
Normal file
119
examples/regression/src/issue_4088.rs
Normal file
@@ -0,0 +1,119 @@
|
||||
use leptos::{either::Either, prelude::*};
|
||||
#[allow(unused_imports)]
|
||||
use leptos_router::{
|
||||
components::{Outlet, ParentRoute, Redirect, Route},
|
||||
path, MatchNestedRoutes, NavigateOptions,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[component]
|
||||
pub fn Routes4088() -> impl MatchNestedRoutes + Clone {
|
||||
view! {
|
||||
<ParentRoute path=path!("4088") view=|| view!{ <LoggedIn/> }>
|
||||
<ParentRoute path=path!("") view=||view!{<AssignmentsSelector/>}>
|
||||
<Route path=path!("/:team_id") view=||view!{<AssignmentsForTeam/>} />
|
||||
<Route path=path!("") view=||view!{ <p>No class selected</p> }/>
|
||||
</ParentRoute>
|
||||
</ParentRoute>
|
||||
}
|
||||
.into_inner()
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct UserInfo {
|
||||
pub id: usize,
|
||||
}
|
||||
|
||||
#[server]
|
||||
pub async fn get_user_info() -> Result<Option<UserInfo>, ServerFnError> {
|
||||
Ok(Some(UserInfo { id: 42 }))
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn LoggedIn() -> impl IntoView {
|
||||
let user_info_resource =
|
||||
Resource::new(|| (), move |_| async { get_user_info().await });
|
||||
|
||||
view! {
|
||||
|
||||
<Transition fallback=move || view!{
|
||||
"loading"
|
||||
}
|
||||
>
|
||||
{move || {
|
||||
user_info_resource.get()
|
||||
.map(|a|
|
||||
match a {
|
||||
Ok(Some(a)) => Either::Left(view! {
|
||||
<LoggedInContent user_info={a} />
|
||||
}),
|
||||
_ => Either::Right(view!{
|
||||
<Redirect path="/not_logged_in"/>
|
||||
})
|
||||
})
|
||||
}}
|
||||
</Transition>
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
/// Component which provides UserInfo and renders it's child
|
||||
/// Can also contain some code to check for specific situations (e.g. privacy policies accepted or not? redirect if needed...)
|
||||
pub fn LoggedInContent(user_info: UserInfo) -> impl IntoView {
|
||||
provide_context(user_info.clone());
|
||||
|
||||
if user_info.id == 42 {
|
||||
Either::Left(Outlet())
|
||||
} else {
|
||||
Either::Right(
|
||||
view! { <Redirect path="/somewhere" options={NavigateOptions::default()}/> },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
/// This component also uses Outlet (so nested Outlet)
|
||||
fn AssignmentsSelector() -> impl IntoView {
|
||||
let user_info = use_context::<UserInfo>().expect("user info not provided");
|
||||
|
||||
view! {
|
||||
<p>"Assignments for user with ID: "{user_info.id}</p>
|
||||
<ul id="nav">
|
||||
<li><a href="/4088/1">"Class 1"</a></li>
|
||||
<li><a href="/4088/2">"Class 2"</a></li>
|
||||
<li><a href="/4088/3">"Class 3"</a></li>
|
||||
</ul>
|
||||
|
||||
<Outlet />
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn AssignmentsForTeam() -> impl IntoView {
|
||||
// THIS FAILS -> Because of the nested outlet in LoggedInContent > AssignmentsSelector?
|
||||
// It did not fail when LoggedIn did not use a resource and transition (but a hardcoded UserInfo in the component)
|
||||
let user_info = use_context::<UserInfo>().expect("user info not provided");
|
||||
|
||||
let items = vec!["Assignment 1", "Assignment 2", "Assignment 3"];
|
||||
view! {
|
||||
<p id="result">"Assignments for team of user with id " {user_info.id}</p>
|
||||
<ul>
|
||||
{
|
||||
items.into_iter().map(|item| {
|
||||
view! {
|
||||
<Assignment name=item.to_string() />
|
||||
}
|
||||
}).collect_view()
|
||||
}
|
||||
</ul>
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn Assignment(name: String) -> impl IntoView {
|
||||
let user_info = use_context::<UserInfo>().expect("user info not provided");
|
||||
|
||||
view! {
|
||||
<li>{name}" "{user_info.id}</li>
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
pub mod app;
|
||||
mod issue_4088;
|
||||
mod pr_4015;
|
||||
mod pr_4091;
|
||||
|
||||
#[cfg(feature = "hydrate")]
|
||||
|
||||
29
examples/regression/src/pr_4015.rs
Normal file
29
examples/regression/src/pr_4015.rs
Normal file
@@ -0,0 +1,29 @@
|
||||
use leptos::{context::Provider, prelude::*};
|
||||
use leptos_router::{
|
||||
components::{ParentRoute, Route},
|
||||
nested_router::Outlet,
|
||||
path,
|
||||
};
|
||||
|
||||
#[component]
|
||||
pub fn Routes4015() -> impl leptos_router::MatchNestedRoutes + Clone {
|
||||
view! {
|
||||
<ParentRoute path=path!("4015") view=|| view! {
|
||||
<Provider value=42i32>
|
||||
<Outlet/>
|
||||
</Provider>
|
||||
}>
|
||||
<Route path=path!("") view=Child/>
|
||||
</ParentRoute>
|
||||
}
|
||||
.into_inner()
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn Child() -> impl IntoView {
|
||||
let value = use_context::<i32>();
|
||||
|
||||
view! {
|
||||
<p id="result">{format!("{value:?}")}</p>
|
||||
}
|
||||
}
|
||||
@@ -28,8 +28,9 @@ fn Container() -> impl IntoView {
|
||||
provide_context(rw_signal);
|
||||
|
||||
view! {
|
||||
<nav>
|
||||
<nav id="nav">
|
||||
<ul>
|
||||
<li><A href="/">"Home"</A></li>
|
||||
<li><A href="./">"4091 Home"</A></li>
|
||||
<li><A href="test1">"test1"</A></li>
|
||||
</ul>
|
||||
|
||||
@@ -159,7 +159,7 @@ fn TodoRow(
|
||||
|
||||
view! {
|
||||
<li style:text-decoration=move || {
|
||||
status.done().then_some("line-through").unwrap_or_default()
|
||||
if status.done() { "line-through" } else { Default::default() }
|
||||
}>
|
||||
|
||||
<p
|
||||
|
||||
@@ -4,7 +4,7 @@ authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/leptos-rs/leptos"
|
||||
description = "Axum integrations for the Leptos web framework."
|
||||
version = "0.8.2"
|
||||
version = "0.8.3"
|
||||
rust-version.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
@@ -13,7 +13,7 @@ any_spawner = { workspace = true, features = ["tokio"] }
|
||||
hydration_context = { workspace = true }
|
||||
axum = { default-features = false, features = [
|
||||
"matched-path",
|
||||
] , workspace = true }
|
||||
], workspace = true }
|
||||
dashmap = { workspace = true, default-features = true }
|
||||
futures = { workspace = true, default-features = true }
|
||||
leptos = { workspace = true, features = ["nonce", "ssr"] }
|
||||
@@ -24,14 +24,17 @@ leptos_router = { workspace = true, features = ["ssr"] }
|
||||
leptos_integration_utils = { workspace = true }
|
||||
tachys = { workspace = true }
|
||||
parking_lot = { workspace = true, default-features = true }
|
||||
tokio = { default-features = false , workspace = true }
|
||||
tower = { features = ["util"] , workspace = true, default-features = true }
|
||||
tokio = { default-features = false, workspace = true }
|
||||
tower = { features = ["util"], workspace = true, default-features = true }
|
||||
tower-http = { workspace = true, default-features = true }
|
||||
tracing = { optional = true , workspace = true, default-features = true }
|
||||
tracing = { optional = true, workspace = true, default-features = true }
|
||||
|
||||
[dev-dependencies]
|
||||
axum = { workspace = true, default-features = true }
|
||||
tokio = { features = ["net", "rt-multi-thread"] , workspace = true, default-features = true }
|
||||
tokio = { features = [
|
||||
"net",
|
||||
"rt-multi-thread",
|
||||
], workspace = true, default-features = true }
|
||||
|
||||
[features]
|
||||
wasm = []
|
||||
|
||||
@@ -111,39 +111,28 @@ where
|
||||
|
||||
let on_submit = {
|
||||
move |ev: SubmitEvent| {
|
||||
// request_animation_frame here schedules this event handler to run slightly later
|
||||
// this means that this `submit` handler will run *after* any other `submit` handlers
|
||||
// that have been added by the user. this is useful because it means that the user can
|
||||
// add an `on:submit` handler and call `ev.prevent_default()` to prevent the form submission
|
||||
//
|
||||
// without this delay, this handler will always run before the user's handler (which was added
|
||||
// later), which means the user can't prevent the form submission in the same way
|
||||
//
|
||||
// see https://github.com/leptos-rs/leptos/issues/3872
|
||||
request_animation_frame(move || {
|
||||
if ev.default_prevented() {
|
||||
return;
|
||||
}
|
||||
if ev.default_prevented() {
|
||||
return;
|
||||
}
|
||||
|
||||
ev.prevent_default();
|
||||
ev.prevent_default();
|
||||
|
||||
match ServFn::from_event(&ev) {
|
||||
Ok(new_input) => {
|
||||
action.dispatch(new_input);
|
||||
}
|
||||
Err(err) => {
|
||||
crate::logging::error!(
|
||||
"Error converting form field into server function \
|
||||
arguments: {err:?}"
|
||||
);
|
||||
value.set(Some(Err(ServerFnErrorErr::Serialization(
|
||||
err.to_string(),
|
||||
)
|
||||
.into_app_error())));
|
||||
version.update(|n| *n += 1);
|
||||
}
|
||||
match ServFn::from_event(&ev) {
|
||||
Ok(new_input) => {
|
||||
action.dispatch(new_input);
|
||||
}
|
||||
});
|
||||
Err(err) => {
|
||||
crate::logging::error!(
|
||||
"Error converting form field into server function \
|
||||
arguments: {err:?}"
|
||||
);
|
||||
value.set(Some(Err(ServerFnErrorErr::Serialization(
|
||||
err.to_string(),
|
||||
)
|
||||
.into_app_error())));
|
||||
version.update(|n| *n += 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -90,6 +90,7 @@ impl<T: RenderHtml> RenderHtml for View<T> {
|
||||
type Owned = View<T::Owned>;
|
||||
|
||||
const MIN_LENGTH: usize = <T as RenderHtml>::MIN_LENGTH;
|
||||
const EXISTS: bool = <T as RenderHtml>::EXISTS;
|
||||
|
||||
async fn resolve(self) -> Self::AsyncOutput {
|
||||
self.inner.resolve().await
|
||||
|
||||
@@ -44,6 +44,8 @@ pub fn render_view(
|
||||
view_marker: Option<String>,
|
||||
disable_inert_html: bool,
|
||||
) -> Option<TokenStream> {
|
||||
let disable_inert_html = disable_inert_html || global_class.is_some();
|
||||
|
||||
let (base, should_add_view) = match nodes.len() {
|
||||
0 => {
|
||||
let span = Span::call_site();
|
||||
@@ -401,6 +403,9 @@ fn inert_element_to_tokens(
|
||||
}
|
||||
}
|
||||
|
||||
/// # Note
|
||||
/// Should not be used on top level `<svg>` elements.
|
||||
/// Use [`inert_element_to_tokens`] instead.
|
||||
fn inert_svg_element_to_tokens(
|
||||
node: &Node<impl CustomNode>,
|
||||
escape_text: bool,
|
||||
@@ -704,7 +709,7 @@ fn node_to_tokens(
|
||||
&& el_name != "textarea";
|
||||
|
||||
let el_name = el_node.name().to_string();
|
||||
if is_svg_element(&el_name) {
|
||||
if is_svg_element(&el_name) && el_name != "svg" {
|
||||
Some(inert_svg_element_to_tokens(
|
||||
node,
|
||||
escape,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "leptos_meta"
|
||||
version = "0.8.2"
|
||||
version = "0.8.3"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/leptos-rs/leptos"
|
||||
@@ -13,8 +13,8 @@ leptos = { workspace = true }
|
||||
or_poisoned = { workspace = true }
|
||||
indexmap = { workspace = true, default-features = true }
|
||||
send_wrapper = { workspace = true, default-features = true }
|
||||
tracing = { optional = true , workspace = true, default-features = true }
|
||||
wasm-bindgen = { workspace = true , default-features = true }
|
||||
tracing = { optional = true, workspace = true, default-features = true }
|
||||
wasm-bindgen = { workspace = true, default-features = true }
|
||||
futures = { workspace = true, default-features = true }
|
||||
|
||||
[dependencies.web-sys]
|
||||
|
||||
@@ -413,6 +413,7 @@ where
|
||||
type Owned = RegisteredMetaTag<E, At::CloneableOwned, Ch::Owned>;
|
||||
|
||||
const MIN_LENGTH: usize = 0;
|
||||
const EXISTS: bool = false;
|
||||
|
||||
fn dry_resolve(&mut self) {
|
||||
self.el.dry_resolve()
|
||||
|
||||
@@ -322,6 +322,7 @@ impl RenderHtml for TitleView {
|
||||
type Owned = Self;
|
||||
|
||||
const MIN_LENGTH: usize = 0;
|
||||
const EXISTS: bool = false;
|
||||
|
||||
fn dry_resolve(&mut self) {}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "reactive_graph"
|
||||
version = "0.2.2"
|
||||
version = "0.2.3"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
readme = "../README.md"
|
||||
@@ -16,19 +16,26 @@ futures = { workspace = true, default-features = true }
|
||||
hydration_context = { workspace = true, optional = true }
|
||||
pin-project-lite = { workspace = true, default-features = true }
|
||||
rustc-hash = { workspace = true, default-features = true }
|
||||
serde = { features = ["derive"], optional = true , workspace = true, default-features = true }
|
||||
serde = { features = [
|
||||
"derive",
|
||||
], optional = true, workspace = true, default-features = true }
|
||||
slotmap = { workspace = true, default-features = true }
|
||||
thiserror = { workspace = true , default-features = true }
|
||||
tracing = { optional = true , workspace = true, default-features = true }
|
||||
thiserror = { workspace = true, default-features = true }
|
||||
tracing = { optional = true, workspace = true, default-features = true }
|
||||
guardian = { workspace = true, default-features = true }
|
||||
async-lock = { workspace = true, default-features = true }
|
||||
send_wrapper = { features = ["futures"] , workspace = true, default-features = true }
|
||||
send_wrapper = { features = [
|
||||
"futures",
|
||||
], workspace = true, default-features = true }
|
||||
|
||||
[target.'cfg(all(target_arch = "wasm32", target_os = "unknown"))'.dependencies]
|
||||
web-sys = { version = "0.3.77", features = ["console"] }
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { features = ["rt-multi-thread", "macros"] , workspace = true, default-features = true }
|
||||
tokio = { features = [
|
||||
"rt-multi-thread",
|
||||
"macros",
|
||||
], workspace = true, default-features = true }
|
||||
tokio-test = { workspace = true, default-features = true }
|
||||
any_spawner = { workspace = true, features = ["futures-executor", "tokio"] }
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "reactive_stores"
|
||||
version = "0.2.2"
|
||||
version = "0.2.3"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
readme = "../README.md"
|
||||
@@ -11,7 +11,7 @@ edition.workspace = true
|
||||
|
||||
[dependencies]
|
||||
guardian = { workspace = true, default-features = true }
|
||||
itertools = { workspace = true , default-features = true }
|
||||
itertools = { workspace = true, default-features = true }
|
||||
or_poisoned = { workspace = true }
|
||||
paste = { workspace = true, default-features = true }
|
||||
reactive_graph = { workspace = true }
|
||||
@@ -21,7 +21,10 @@ dashmap = { workspace = true, default-features = true }
|
||||
send_wrapper = { workspace = true, default-features = true }
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { features = ["rt-multi-thread", "macros"] , workspace = true, default-features = true }
|
||||
tokio = { features = [
|
||||
"rt-multi-thread",
|
||||
"macros",
|
||||
], workspace = true, default-features = true }
|
||||
tokio-test = { workspace = true, default-features = true }
|
||||
any_spawner = { workspace = true, features = ["futures-executor", "tokio"] }
|
||||
reactive_graph = { workspace = true, features = ["effects"] }
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "reactive_stores_macro"
|
||||
version = "0.2.2"
|
||||
version = "0.2.3"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
readme = "../README.md"
|
||||
@@ -13,8 +13,8 @@ edition.workspace = true
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
convert_case = { workspace = true , default-features = true }
|
||||
convert_case = { workspace = true, default-features = true }
|
||||
proc-macro-error2 = { workspace = true, default-features = true }
|
||||
proc-macro2 = { workspace = true, default-features = true }
|
||||
quote = { workspace = true, default-features = true }
|
||||
syn = { features = ["full"] , workspace = true, default-features = true }
|
||||
syn = { features = ["full"], workspace = true, default-features = true }
|
||||
|
||||
@@ -111,10 +111,8 @@ impl ToTokens for Model {
|
||||
} = &self;
|
||||
let any_store_field = Ident::new("AnyStoreField", Span::call_site());
|
||||
let trait_name = Ident::new(&format!("{name}StoreFields"), name.span());
|
||||
let generics_with_orig = {
|
||||
let params = &generics.params;
|
||||
quote! { <#any_store_field, #params> }
|
||||
};
|
||||
let params = &generics.params;
|
||||
let generics_with_orig = quote! { <#any_store_field, #params> };
|
||||
let where_with_orig = {
|
||||
generics
|
||||
.where_clause
|
||||
@@ -140,13 +138,13 @@ impl ToTokens for Model {
|
||||
|
||||
// read access
|
||||
tokens.extend(quote! {
|
||||
#vis trait #trait_name <AnyStoreField>
|
||||
#vis trait #trait_name <AnyStoreField, #params>
|
||||
#where_with_orig
|
||||
{
|
||||
#(#trait_fields)*
|
||||
}
|
||||
|
||||
impl #generics_with_orig #trait_name <AnyStoreField> for AnyStoreField
|
||||
impl #generics_with_orig #trait_name <AnyStoreField, #params> for AnyStoreField
|
||||
#where_with_orig
|
||||
{
|
||||
#(#read_fields)*
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "leptos_router"
|
||||
version = "0.8.2"
|
||||
version = "0.8.3"
|
||||
authors = ["Greg Johnston", "Ben Wishovich"]
|
||||
license = "MIT"
|
||||
readme = "../README.md"
|
||||
@@ -20,11 +20,11 @@ tachys = { workspace = true, features = ["reactive_graph"] }
|
||||
futures = { workspace = true, default-features = true }
|
||||
url = { workspace = true, default-features = true }
|
||||
js-sys = { workspace = true, default-features = true }
|
||||
wasm-bindgen = { workspace = true , default-features = true }
|
||||
tracing = { optional = true , workspace = true, default-features = true }
|
||||
wasm-bindgen = { workspace = true, default-features = true }
|
||||
tracing = { optional = true, workspace = true, default-features = true }
|
||||
send_wrapper = { workspace = true, default-features = true }
|
||||
thiserror = { workspace = true , default-features = true }
|
||||
percent-encoding = { optional = true , workspace = true, default-features = true }
|
||||
thiserror = { workspace = true, default-features = true }
|
||||
percent-encoding = { optional = true, workspace = true, default-features = true }
|
||||
gloo-net = { workspace = true, default-features = true }
|
||||
|
||||
[dependencies.web-sys]
|
||||
|
||||
@@ -109,7 +109,6 @@ where
|
||||
base,
|
||||
&mut loaders,
|
||||
&mut outlets,
|
||||
&outer_owner,
|
||||
);
|
||||
drop(url);
|
||||
|
||||
@@ -180,7 +179,6 @@ where
|
||||
&mut preloaders,
|
||||
&mut full_loaders,
|
||||
&mut state.outlets,
|
||||
&self.outer_owner,
|
||||
self.set_is_routing.is_some(),
|
||||
0,
|
||||
);
|
||||
@@ -340,7 +338,6 @@ where
|
||||
base,
|
||||
&mut loaders,
|
||||
&mut outlets,
|
||||
&outer_owner,
|
||||
);
|
||||
|
||||
// outlets will not send their views if the loaders are never polled
|
||||
@@ -394,7 +391,6 @@ where
|
||||
base,
|
||||
&mut loaders,
|
||||
&mut outlets,
|
||||
&outer_owner,
|
||||
);
|
||||
|
||||
// outlets will not send their views if the loaders are never polled
|
||||
@@ -448,7 +444,6 @@ where
|
||||
base,
|
||||
&mut loaders,
|
||||
&mut outlets,
|
||||
&outer_owner,
|
||||
);
|
||||
drop(url);
|
||||
|
||||
@@ -483,10 +478,10 @@ pub(crate) struct RouteContext {
|
||||
trigger: ArcTrigger,
|
||||
url: ArcRwSignal<Url>,
|
||||
params: ArcRwSignal<ParamsMap>,
|
||||
owner: Owner,
|
||||
pub matched: ArcRwSignal<String>,
|
||||
base: Option<Oco<'static, str>>,
|
||||
view_fn: Arc<Mutex<OutletViewFn>>,
|
||||
owner: Arc<Mutex<Option<Owner>>>,
|
||||
child: ChildRoute,
|
||||
}
|
||||
|
||||
@@ -500,7 +495,6 @@ impl Debug for RouteContext {
|
||||
.field("trigger", &self.trigger)
|
||||
.field("url", &self.url)
|
||||
.field("params", &self.params)
|
||||
.field("owner", &self.owner.debug_id())
|
||||
.field("matched", &self.matched)
|
||||
.field("base", &self.base)
|
||||
.finish_non_exhaustive()
|
||||
@@ -514,10 +508,10 @@ impl Clone for RouteContext {
|
||||
id: self.id,
|
||||
trigger: self.trigger.clone(),
|
||||
params: self.params.clone(),
|
||||
owner: self.owner.clone(),
|
||||
matched: self.matched.clone(),
|
||||
base: self.base.clone(),
|
||||
view_fn: Arc::clone(&self.view_fn),
|
||||
owner: Arc::clone(&self.owner),
|
||||
child: self.child.clone(),
|
||||
}
|
||||
}
|
||||
@@ -530,7 +524,6 @@ trait AddNestedRoute {
|
||||
base: Option<Oco<'static, str>>,
|
||||
loaders: &mut Vec<Pin<Box<dyn Future<Output = ArcTrigger>>>>,
|
||||
outlets: &mut Vec<RouteContext>,
|
||||
parent: &Owner,
|
||||
);
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
@@ -540,9 +533,8 @@ trait AddNestedRoute {
|
||||
base: Option<Oco<'static, str>>,
|
||||
items: &mut usize,
|
||||
loaders: &mut Vec<Pin<Box<dyn Future<Output = ArcTrigger>>>>,
|
||||
full_loaders: &mut Vec<oneshot::Receiver<()>>,
|
||||
full_loaders: &mut Vec<oneshot::Receiver<Option<Owner>>>,
|
||||
outlets: &mut Vec<RouteContext>,
|
||||
parent: &Owner,
|
||||
set_is_routing: bool,
|
||||
level: u8,
|
||||
) -> u8;
|
||||
@@ -558,15 +550,9 @@ where
|
||||
base: Option<Oco<'static, str>>,
|
||||
loaders: &mut Vec<Pin<Box<dyn Future<Output = ArcTrigger>>>>,
|
||||
outlets: &mut Vec<RouteContext>,
|
||||
parent: &Owner,
|
||||
) {
|
||||
let orig_url = url;
|
||||
|
||||
// each Outlet gets its own owner, so it can inherit context from its parent route,
|
||||
// a new owner will be constructed if a different route replaces this one in the outlet,
|
||||
// so that any signals it creates or context it provides will be cleaned up
|
||||
let owner = parent.child();
|
||||
|
||||
// the params signal can be updated to allow the same outlet to update to changes in the
|
||||
// params, even if there's not a route match change
|
||||
let params = ArcRwSignal::new(self.to_params().into_iter().collect());
|
||||
@@ -624,13 +610,13 @@ where
|
||||
url,
|
||||
trigger: trigger.clone(),
|
||||
params,
|
||||
owner: owner.clone(),
|
||||
matched,
|
||||
view_fn: Arc::new(Mutex::new(Box::new(|_owner| {
|
||||
Suspend::new(Box::pin(async { ().into_any() }))
|
||||
}))),
|
||||
base: base.clone(),
|
||||
child: ChildRoute(Arc::new(Mutex::new(None))),
|
||||
owner: Arc::new(Mutex::new(None)),
|
||||
};
|
||||
if !outlets.is_empty() {
|
||||
let prev_index = outlets.len().saturating_sub(1);
|
||||
@@ -646,6 +632,7 @@ where
|
||||
let url = outlet.url.clone();
|
||||
let matched = Matched(matched_including_parents);
|
||||
let view_fn = Arc::clone(&outlet.view_fn);
|
||||
let route_owner = Arc::clone(&outlet.owner);
|
||||
let outlet = outlet.clone();
|
||||
let params = params_including_parents.clone();
|
||||
let url = url.clone();
|
||||
@@ -655,6 +642,8 @@ where
|
||||
let child = outlet.child.clone();
|
||||
*view_fn.lock().or_poisoned() =
|
||||
Box::new(move |owner_where_used| {
|
||||
*route_owner.lock().or_poisoned() =
|
||||
Some(owner_where_used.clone());
|
||||
let view = view.clone();
|
||||
let child = child.clone();
|
||||
let params = params.clone();
|
||||
@@ -696,7 +685,7 @@ where
|
||||
// this is important because to build the view, we need access to the outlet
|
||||
// and the outlet will be returned from building this child
|
||||
if let Some(child) = child {
|
||||
child.build_nested_route(orig_url, base, loaders, outlets, &owner);
|
||||
child.build_nested_route(orig_url, base, loaders, outlets);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -707,9 +696,8 @@ where
|
||||
base: Option<Oco<'static, str>>,
|
||||
items: &mut usize,
|
||||
preloaders: &mut Vec<Pin<Box<dyn Future<Output = ArcTrigger>>>>,
|
||||
full_loaders: &mut Vec<oneshot::Receiver<()>>,
|
||||
full_loaders: &mut Vec<oneshot::Receiver<Option<Owner>>>,
|
||||
outlets: &mut Vec<RouteContext>,
|
||||
parent: &Owner,
|
||||
set_is_routing: bool,
|
||||
level: u8,
|
||||
) -> u8 {
|
||||
@@ -718,11 +706,17 @@ where
|
||||
.take(*items)
|
||||
.map(|route| (route.params.clone(), route.matched.clone()))
|
||||
.unzip();
|
||||
|
||||
if outlets.get(*items).is_some() && *items > 0 {
|
||||
*outlets[*items - 1].child.0.lock().or_poisoned() =
|
||||
Some(outlets[*items].clone());
|
||||
}
|
||||
|
||||
let current = outlets.get_mut(*items);
|
||||
match current {
|
||||
// if there's nothing currently in the routes at this point, build from here
|
||||
None => {
|
||||
self.build_nested_route(url, base, preloaders, outlets, parent);
|
||||
self.build_nested_route(url, base, preloaders, outlets);
|
||||
level
|
||||
}
|
||||
Some(current) => {
|
||||
@@ -789,9 +783,6 @@ where
|
||||
|
||||
// assign a new owner, so that contexts and signals owned by the previous route
|
||||
// in this outlet can be dropped
|
||||
let mut old_owner =
|
||||
Some(mem::replace(&mut current.owner, parent.child()));
|
||||
let owner = current.owner.clone();
|
||||
let (full_tx, full_rx) = oneshot::channel();
|
||||
let full_tx = Mutex::new(Some(full_tx));
|
||||
full_loaders.push(full_rx);
|
||||
@@ -801,22 +792,24 @@ where
|
||||
// and notify the trigger so that the reactive view inside the Outlet tracking
|
||||
// the trigger runs again
|
||||
preloaders.push(Box::pin(ScopedFuture::new({
|
||||
let owner = owner.clone();
|
||||
let trigger = current.trigger.clone();
|
||||
let url = current.url.clone();
|
||||
let matched = Matched(matched_including_parents);
|
||||
let view_fn = Arc::clone(¤t.view_fn);
|
||||
let route_owner = Arc::clone(¤t.owner);
|
||||
let child = outlet.child.clone();
|
||||
async move {
|
||||
view.preload().await;
|
||||
let child = child.clone();
|
||||
*view_fn.lock().or_poisoned() =
|
||||
Box::new(move |owner_where_used| {
|
||||
let owner = owner.clone();
|
||||
let prev_owner = route_owner
|
||||
.lock()
|
||||
.or_poisoned()
|
||||
.replace(owner_where_used.clone());
|
||||
let view = view.clone();
|
||||
let full_tx =
|
||||
full_tx.lock().or_poisoned().take();
|
||||
let old_owner = old_owner.take();
|
||||
let child = child.clone();
|
||||
let params =
|
||||
params_including_parents.clone();
|
||||
@@ -841,15 +834,13 @@ where
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
let view = view.await;
|
||||
if let Some(old_owner) = old_owner {
|
||||
old_owner.cleanup();
|
||||
}
|
||||
|
||||
if let Some(tx) = full_tx {
|
||||
_ = tx.send(());
|
||||
_ = tx.send(prev_owner);
|
||||
}
|
||||
owner.with(|| {
|
||||
owner_where_used.with(|| {
|
||||
OwnedView::new(view).into_any()
|
||||
})
|
||||
}))
|
||||
@@ -868,9 +859,10 @@ where
|
||||
|
||||
// if this children has matches, then rebuild the lower section of the tree
|
||||
if let Some(child) = child {
|
||||
child.build_nested_route(
|
||||
url, base, preloaders, outlets, &owner,
|
||||
);
|
||||
child
|
||||
.build_nested_route(url, base, preloaders, outlets);
|
||||
} else {
|
||||
*outlets[*items].child.0.lock().or_poisoned() = None;
|
||||
}
|
||||
|
||||
return level;
|
||||
@@ -882,7 +874,6 @@ where
|
||||
current.params.set(new_params);
|
||||
current.url.set(url.to_owned());
|
||||
if let Some(child) = child {
|
||||
let owner = current.owner.clone();
|
||||
*items += 1;
|
||||
child.rebuild_nested_route(
|
||||
url,
|
||||
@@ -891,11 +882,11 @@ where
|
||||
preloaders,
|
||||
full_loaders,
|
||||
outlets,
|
||||
&owner,
|
||||
set_is_routing,
|
||||
level + 1,
|
||||
)
|
||||
} else {
|
||||
*current.child.0.lock().or_poisoned() = None;
|
||||
level
|
||||
}
|
||||
}
|
||||
@@ -933,13 +924,13 @@ fn top_level_outlet(outlets: &[RouteContext], outer_owner: &Owner) -> AnyView {
|
||||
let child = outlet.child.clone();
|
||||
let view_fn = outlet.view_fn.clone();
|
||||
let trigger = outlet.trigger.clone();
|
||||
let owner = outer_owner.child();
|
||||
outer_owner.clone().with(|| {
|
||||
provide_context(child.clone());
|
||||
let outer_owner = outer_owner.clone();
|
||||
(move || {
|
||||
trigger.track();
|
||||
let mut view_fn = view_fn.lock().or_poisoned();
|
||||
view_fn(owner.clone())
|
||||
view_fn(outer_owner.child())
|
||||
})
|
||||
.into_any()
|
||||
})
|
||||
@@ -953,13 +944,13 @@ where
|
||||
{
|
||||
let ChildRoute(child) = use_context()
|
||||
.expect("<Outlet/> used without RouteContext being provided.");
|
||||
let owner = Owner::new();
|
||||
let child = child.lock().or_poisoned().clone();
|
||||
let outer_owner = Owner::current().unwrap();
|
||||
child.map(|child| {
|
||||
move || {
|
||||
child.trigger.track();
|
||||
let mut view_fn = child.view_fn.lock().or_poisoned();
|
||||
view_fn(owner.clone())
|
||||
view_fn(outer_owner.child())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "leptos_router_macro"
|
||||
version = "0.8.2"
|
||||
version = "0.8.3"
|
||||
authors = ["Greg Johnston", "Ben Wishovich"]
|
||||
license = "MIT"
|
||||
readme = "../README.md"
|
||||
@@ -13,10 +13,10 @@ edition.workspace = true
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
proc-macro-error2 = { default-features = false , workspace = true }
|
||||
proc-macro-error2 = { default-features = false, workspace = true }
|
||||
proc-macro2 = { workspace = true, default-features = true }
|
||||
quote = { workspace = true, default-features = true }
|
||||
syn = { features = ["full"] , workspace = true, default-features = true }
|
||||
syn = { features = ["full"], workspace = true, default-features = true }
|
||||
|
||||
[dev-dependencies]
|
||||
leptos_router = { path = "../router" }
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "tachys"
|
||||
version = "0.2.3"
|
||||
version = "0.2.4"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
readme = "../README.md"
|
||||
|
||||
@@ -12,6 +12,7 @@ use crate::{
|
||||
Attribute,
|
||||
},
|
||||
hydration::Cursor,
|
||||
renderer::Rndr,
|
||||
ssr::StreamBuilder,
|
||||
};
|
||||
use futures::future::{join, join_all};
|
||||
@@ -90,6 +91,7 @@ pub struct AnyViewState {
|
||||
),
|
||||
insert_before_this: fn(&ErasedLocal, child: &mut dyn Mountable) -> bool,
|
||||
elements: fn(&ErasedLocal) -> Vec<crate::renderer::types::Element>,
|
||||
placeholder: Option<crate::renderer::types::Placeholder>,
|
||||
}
|
||||
|
||||
impl Debug for AnyViewState {
|
||||
@@ -214,6 +216,9 @@ where
|
||||
mark_branches,
|
||||
extra_attrs,
|
||||
);
|
||||
if !T::EXISTS {
|
||||
buf.push_str("<!>");
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "ssr")]
|
||||
@@ -232,6 +237,9 @@ where
|
||||
mark_branches,
|
||||
extra_attrs,
|
||||
);
|
||||
if !T::EXISTS {
|
||||
buf.push_sync("<!>");
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "ssr")]
|
||||
@@ -250,10 +258,14 @@ where
|
||||
mark_branches,
|
||||
extra_attrs,
|
||||
);
|
||||
if !T::EXISTS {
|
||||
buf.push_sync("<!>");
|
||||
}
|
||||
}
|
||||
|
||||
fn build<T: RenderHtml + 'static>(value: Erased) -> AnyViewState {
|
||||
let state = ErasedLocal::new(value.into_inner::<T>().build());
|
||||
let placeholder = (!T::EXISTS).then(Rndr::create_placeholder);
|
||||
AnyViewState {
|
||||
type_id: TypeId::of::<T>(),
|
||||
state,
|
||||
@@ -261,6 +273,7 @@ where
|
||||
unmount: unmount_any::<T>,
|
||||
insert_before_this: insert_before_this::<T>,
|
||||
elements: elements::<T>,
|
||||
placeholder,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -273,6 +286,8 @@ where
|
||||
let state = ErasedLocal::new(
|
||||
value.into_inner::<T>().hydrate::<true>(cursor, position),
|
||||
);
|
||||
let placeholder =
|
||||
(!T::EXISTS).then(|| cursor.next_placeholder(position));
|
||||
AnyViewState {
|
||||
type_id: TypeId::of::<T>(),
|
||||
state,
|
||||
@@ -280,6 +295,7 @@ where
|
||||
unmount: unmount_any::<T>,
|
||||
insert_before_this: insert_before_this::<T>,
|
||||
elements: elements::<T>,
|
||||
placeholder,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -327,7 +343,12 @@ impl Render for AnyView {
|
||||
(self.rebuild)(self.value, state)
|
||||
} else {
|
||||
let mut new = self.build();
|
||||
state.insert_before_this(&mut new);
|
||||
if let Some(placeholder) = &mut state.placeholder {
|
||||
placeholder.insert_before_this(&mut new);
|
||||
placeholder.unmount();
|
||||
} else {
|
||||
state.insert_before_this(&mut new);
|
||||
}
|
||||
state.unmount();
|
||||
*state = new;
|
||||
}
|
||||
@@ -554,7 +575,10 @@ impl RenderHtml for AnyView {
|
||||
|
||||
impl Mountable for AnyViewState {
|
||||
fn unmount(&mut self) {
|
||||
(self.unmount)(&mut self.state)
|
||||
(self.unmount)(&mut self.state);
|
||||
if let Some(placeholder) = &mut self.placeholder {
|
||||
placeholder.unmount();
|
||||
}
|
||||
}
|
||||
|
||||
fn mount(
|
||||
@@ -562,11 +586,23 @@ impl Mountable for AnyViewState {
|
||||
parent: &crate::renderer::types::Element,
|
||||
marker: Option<&crate::renderer::types::Node>,
|
||||
) {
|
||||
(self.mount)(&mut self.state, parent, marker)
|
||||
(self.mount)(&mut self.state, parent, marker);
|
||||
if let Some(placeholder) = &mut self.placeholder {
|
||||
placeholder.mount(parent, marker);
|
||||
}
|
||||
}
|
||||
|
||||
fn insert_before_this(&self, child: &mut dyn Mountable) -> bool {
|
||||
(self.insert_before_this)(&self.state, child)
|
||||
let before_view = (self.insert_before_this)(&self.state, child);
|
||||
if before_view {
|
||||
return true;
|
||||
}
|
||||
|
||||
if let Some(placeholder) = &self.placeholder {
|
||||
placeholder.insert_before_this(child)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn elements(&self) -> Vec<crate::renderer::types::Element> {
|
||||
|
||||
@@ -134,6 +134,7 @@ where
|
||||
type Owned = (A::Owned,);
|
||||
|
||||
const MIN_LENGTH: usize = A::MIN_LENGTH;
|
||||
const EXISTS: bool = A::EXISTS;
|
||||
|
||||
fn html_len(&self) -> usize {
|
||||
self.0.html_len()
|
||||
@@ -239,7 +240,6 @@ macro_rules! impl_view_for_tuples {
|
||||
{
|
||||
type State = ($first::State, $($ty::State,)*);
|
||||
|
||||
|
||||
fn build(self) -> Self::State {
|
||||
#[allow(non_snake_case)]
|
||||
let ($first, $($ty,)*) = self;
|
||||
@@ -267,7 +267,7 @@ macro_rules! impl_view_for_tuples {
|
||||
{
|
||||
type AsyncOutput = ($first::AsyncOutput, $($ty::AsyncOutput,)*);
|
||||
type Owned = ($first::Owned, $($ty::Owned,)*);
|
||||
|
||||
const EXISTS: bool = $first::EXISTS || $($ty::EXISTS || )* false;
|
||||
const MIN_LENGTH: usize = $first::MIN_LENGTH $(+ $ty::MIN_LENGTH)*;
|
||||
|
||||
#[inline(always)]
|
||||
|
||||
Reference in New Issue
Block a user