mirror of
https://github.com/leptos-rs/leptos.git
synced 2025-12-28 12:31:55 -05:00
Compare commits
2 Commits
timings
...
PenguinWit
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
faa73ff4db | ||
|
|
7291efc077 |
@@ -21,16 +21,10 @@ pub fn Nav() -> impl IntoView {
|
||||
<A href="/job">
|
||||
<strong>"Jobs"</strong>
|
||||
</A>
|
||||
<a
|
||||
class="github"
|
||||
href="http://github.com/leptos-rs/leptos"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<a class="github" href="http://github.com/leptos-rs/leptos" target="_blank" rel="noreferrer">
|
||||
"Built with Leptos"
|
||||
</a>
|
||||
</nav>
|
||||
</header>
|
||||
}
|
||||
.into_any()
|
||||
}
|
||||
|
||||
@@ -50,42 +50,30 @@ pub fn Stories() -> impl IntoView {
|
||||
<div class="news-view">
|
||||
<div class="news-list-nav">
|
||||
<span>
|
||||
{move || {
|
||||
if page() > 1 {
|
||||
Either::Left(
|
||||
view! {
|
||||
<a
|
||||
class="page-link"
|
||||
href=move || {
|
||||
format!("/{}?page={}", story_type(), page() - 1)
|
||||
}
|
||||
aria-label="Previous Page"
|
||||
>
|
||||
"< prev"
|
||||
</a>
|
||||
},
|
||||
)
|
||||
} else {
|
||||
Either::Right(
|
||||
view! {
|
||||
<span class="page-link disabled" aria-hidden="true">
|
||||
"< prev"
|
||||
</span>
|
||||
},
|
||||
)
|
||||
}
|
||||
{move || if page() > 1 {
|
||||
Either::Left(view! {
|
||||
<a class="page-link"
|
||||
href=move || format!("/{}?page={}", story_type(), page() - 1)
|
||||
aria-label="Previous Page"
|
||||
>
|
||||
"< prev"
|
||||
</a>
|
||||
})
|
||||
} else {
|
||||
Either::Right(view! {
|
||||
<span class="page-link disabled" aria-hidden="true">
|
||||
"< prev"
|
||||
</span>
|
||||
})
|
||||
}}
|
||||
|
||||
</span>
|
||||
<span>"page " {page}</span>
|
||||
<Suspense>
|
||||
<span
|
||||
class="page-link"
|
||||
<span class="page-link"
|
||||
class:disabled=hide_more_link
|
||||
aria-hidden=hide_more_link
|
||||
>
|
||||
<a
|
||||
href=move || format!("/{}?page={}", story_type(), page() + 1)
|
||||
<a href=move || format!("/{}?page={}", story_type(), page() + 1)
|
||||
aria-label="Next Page"
|
||||
>
|
||||
"more >"
|
||||
@@ -95,10 +83,14 @@ pub fn Stories() -> impl IntoView {
|
||||
</div>
|
||||
<main class="news-list">
|
||||
<div>
|
||||
<Transition fallback=move || view! { <p>"Loading..."</p> } set_pending>
|
||||
<Show when=move || {
|
||||
stories.read().as_ref().map(Option::is_none).unwrap_or(false)
|
||||
}>> <p>"Error loading stories."</p></Show>
|
||||
<Transition
|
||||
fallback=move || view! { <p>"Loading..."</p> }
|
||||
set_pending
|
||||
>
|
||||
<Show when=move || stories.read().as_ref().map(Option::is_none).unwrap_or(false)>
|
||||
>
|
||||
<p>"Error loading stories."</p>
|
||||
</Show>
|
||||
<ul>
|
||||
<For
|
||||
each=move || stories.get().unwrap_or_default().unwrap_or_default()
|
||||
@@ -113,78 +105,54 @@ pub fn Stories() -> impl IntoView {
|
||||
</main>
|
||||
</div>
|
||||
}
|
||||
.into_any()
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn Story(story: api::Story) -> impl IntoView {
|
||||
view! {
|
||||
<li class="news-item">
|
||||
<li class="news-item">
|
||||
<span class="score">{story.points}</span>
|
||||
<span class="title">
|
||||
{if !story.url.starts_with("item?id=") {
|
||||
Either::Left(
|
||||
view! {
|
||||
<span>
|
||||
<a href=story.url target="_blank" rel="noreferrer">
|
||||
{story.title.clone()}
|
||||
</a>
|
||||
<span class="host">"(" {story.domain} ")"</span>
|
||||
</span>
|
||||
},
|
||||
)
|
||||
Either::Left(view! {
|
||||
<span>
|
||||
<a href=story.url target="_blank" rel="noreferrer">
|
||||
{story.title.clone()}
|
||||
</a>
|
||||
<span class="host">"("{story.domain}")"</span>
|
||||
</span>
|
||||
})
|
||||
} else {
|
||||
let title = story.title.clone();
|
||||
Either::Right(view! { <A href=format!("/stories/{}", story.id)>{title}</A> })
|
||||
}}
|
||||
|
||||
</span>
|
||||
<br/>
|
||||
<br />
|
||||
<span class="meta">
|
||||
{if story.story_type != "job" {
|
||||
Either::Left(
|
||||
view! {
|
||||
<span>
|
||||
{"by "}
|
||||
{story
|
||||
.user
|
||||
.map(|user| {
|
||||
view! {
|
||||
<A href=format!("/users/{user}")>{user.clone()}</A>
|
||||
}
|
||||
})} {format!(" {} | ", story.time_ago)}
|
||||
<A href=format!(
|
||||
"/stories/{}",
|
||||
story.id,
|
||||
)>
|
||||
{if story.comments_count.unwrap_or_default() > 0 {
|
||||
format!(
|
||||
"{} comments",
|
||||
story.comments_count.unwrap_or_default(),
|
||||
)
|
||||
} else {
|
||||
"discuss".into()
|
||||
}}
|
||||
|
||||
</A>
|
||||
</span>
|
||||
},
|
||||
)
|
||||
Either::Left(view! {
|
||||
<span>
|
||||
{"by "}
|
||||
{story.user.map(|user| view ! { <A href=format!("/users/{user}")>{user.clone()}</A>})}
|
||||
{format!(" {} | ", story.time_ago)}
|
||||
<A href=format!("/stories/{}", story.id)>
|
||||
{if story.comments_count.unwrap_or_default() > 0 {
|
||||
format!("{} comments", story.comments_count.unwrap_or_default())
|
||||
} else {
|
||||
"discuss".into()
|
||||
}}
|
||||
</A>
|
||||
</span>
|
||||
})
|
||||
} else {
|
||||
let title = story.title.clone();
|
||||
Either::Right(view! { <A href=format!("/item/{}", story.id)>{title}</A> })
|
||||
}}
|
||||
|
||||
</span>
|
||||
{(story.story_type != "link")
|
||||
.then(|| {
|
||||
view! {
|
||||
" "
|
||||
<span class="label">{story.story_type}</span>
|
||||
}
|
||||
})}
|
||||
|
||||
{(story.story_type != "link").then(|| view! {
|
||||
" "
|
||||
<span class="label">{story.story_type}</span>
|
||||
})}
|
||||
</li>
|
||||
}
|
||||
.into_any()
|
||||
}
|
||||
|
||||
@@ -28,21 +28,18 @@ pub fn Story() -> impl IntoView {
|
||||
<Meta name="description" content=story.title.clone()/>
|
||||
<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! {
|
||||
<p class="meta">
|
||||
{story.points} " points | by "
|
||||
<A href=format!("/users/{user}")>{user.clone()}</A>
|
||||
{format!(" {}", story.time_ago)}
|
||||
</p>
|
||||
}
|
||||
})}
|
||||
<a href=story.url target="_blank">
|
||||
<h1>{story.title}</h1>
|
||||
</a>
|
||||
<span class="host">
|
||||
"("{story.domain}")"
|
||||
</span>
|
||||
{story.user.map(|user| view! { <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">
|
||||
@@ -51,7 +48,6 @@ pub fn Story() -> impl IntoView {
|
||||
} else {
|
||||
"No comments yet.".into()
|
||||
}}
|
||||
|
||||
</p>
|
||||
<ul class="comment-children">
|
||||
<For
|
||||
@@ -59,7 +55,7 @@ pub fn Story() -> impl IntoView {
|
||||
key=|comment| comment.id
|
||||
let:comment
|
||||
>
|
||||
<Comment comment/>
|
||||
<Comment comment />
|
||||
</For>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -68,7 +64,6 @@ pub fn Story() -> impl IntoView {
|
||||
}
|
||||
}
|
||||
}))).build())
|
||||
.into_any()
|
||||
}
|
||||
|
||||
#[component]
|
||||
@@ -77,65 +72,43 @@ pub fn Comment(comment: api::Comment) -> impl IntoView {
|
||||
|
||||
view! {
|
||||
<li class="comment">
|
||||
<div class="by">
|
||||
<A href=format!(
|
||||
"/users/{}",
|
||||
comment.user.clone().unwrap_or_default(),
|
||||
)>{comment.user.clone()}</A>
|
||||
{format!(" {}", comment.time_ago)}
|
||||
</div>
|
||||
<div class="text" inner_html=comment.content></div>
|
||||
{(!comment.comments.is_empty())
|
||||
.then(|| {
|
||||
view! {
|
||||
<div>
|
||||
<div class="toggle" class:open=open>
|
||||
<a on:click=move |_| {
|
||||
set_open.update(|n| *n = !*n)
|
||||
}>
|
||||
|
||||
{
|
||||
let comments_len = comment.comments.len();
|
||||
move || {
|
||||
if open.get() {
|
||||
"[-]".into()
|
||||
} else {
|
||||
format!(
|
||||
"[+] {}{} collapsed",
|
||||
comments_len,
|
||||
pluralize(comments_len),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</a>
|
||||
</div>
|
||||
{move || {
|
||||
open
|
||||
.get()
|
||||
.then({
|
||||
let comments = comment.comments.clone();
|
||||
move || {
|
||||
view! {
|
||||
<ul class="comment-children">
|
||||
<For
|
||||
each=move || comments.clone()
|
||||
key=|comment| comment.id
|
||||
let:comment
|
||||
>
|
||||
<Comment comment/>
|
||||
</For>
|
||||
</ul>
|
||||
}
|
||||
}
|
||||
})
|
||||
}}
|
||||
|
||||
</div>
|
||||
}
|
||||
})}
|
||||
|
||||
<div class="by">
|
||||
<A href=format!("/users/{}", comment.user.clone().unwrap_or_default())>{comment.user.clone()}</A>
|
||||
{format!(" {}", comment.time_ago)}
|
||||
</div>
|
||||
<div class="text" inner_html=comment.content></div>
|
||||
{(!comment.comments.is_empty()).then(|| {
|
||||
view! {
|
||||
<div>
|
||||
<div class="toggle" class:open=open>
|
||||
<a on:click=move |_| set_open.update(|n| *n = !*n)>
|
||||
{
|
||||
let comments_len = comment.comments.len();
|
||||
move || if open.get() {
|
||||
"[-]".into()
|
||||
} else {
|
||||
format!("[+] {}{} collapsed", comments_len, pluralize(comments_len))
|
||||
}
|
||||
}
|
||||
</a>
|
||||
</div>
|
||||
{move || open.get().then({
|
||||
let comments = comment.comments.clone();
|
||||
move || view! {
|
||||
<ul class="comment-children">
|
||||
<For
|
||||
each=move || comments.clone()
|
||||
key=|comment| comment.id
|
||||
let:comment
|
||||
>
|
||||
<Comment comment />
|
||||
</For>
|
||||
</ul>
|
||||
}
|
||||
})}
|
||||
</div>
|
||||
}
|
||||
})}
|
||||
</li>
|
||||
}.into_any()
|
||||
}
|
||||
|
||||
@@ -18,48 +18,30 @@ pub fn User() -> impl IntoView {
|
||||
);
|
||||
view! {
|
||||
<div class="user-view">
|
||||
<Suspense fallback=|| {
|
||||
view! { "Loading..." }
|
||||
}>
|
||||
{move || Suspend::new(async move {
|
||||
match user.await.clone() {
|
||||
None => Either::Left(view! { <h1>"User not found."</h1> }),
|
||||
Some(user) => {
|
||||
Either::Right(
|
||||
view! {
|
||||
<div>
|
||||
<h1>"User: " {user.id.clone()}</h1>
|
||||
<ul class="meta">
|
||||
<li>
|
||||
<span class="label">"Created: "</span>
|
||||
{user.created}
|
||||
</li>
|
||||
<li>
|
||||
<span class="label">"Karma: "</span>
|
||||
{user.karma}
|
||||
</li>
|
||||
<li inner_html=user.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>
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
})}
|
||||
|
||||
<Suspense fallback=|| view! { "Loading..." }>
|
||||
{move || Suspend::new(async move { match user.await.clone() {
|
||||
None => Either::Left(view! { <h1>"User not found."</h1> }),
|
||||
Some(user) => Either::Right(view! {
|
||||
<div>
|
||||
<h1>"User: " {user.id.clone()}</h1>
|
||||
<ul class="meta">
|
||||
<li>
|
||||
<span class="label">"Created: "</span> {user.created}
|
||||
</li>
|
||||
<li>
|
||||
<span class="label">"Karma: "</span> {user.karma}
|
||||
</li>
|
||||
<li inner_html={user.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>
|
||||
})
|
||||
}})}
|
||||
</Suspense>
|
||||
</div>
|
||||
}
|
||||
.into_any()
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<link data-trunk rel="rust" data-wasm-opt="z"/>
|
||||
<link data-trunk rel="icon" type="image/ico" href="/public/favicon.ico"/>
|
||||
<link data-trunk rel="css" href="style.css"/>
|
||||
<link data-trunk rel="css" href="style.css"/>
|
||||
</head>
|
||||
<body></body>
|
||||
</html>
|
||||
|
||||
@@ -50,7 +50,7 @@ pub fn RouterExample() -> impl IntoView {
|
||||
}>{move || if logged_in.get() { "Log Out" } else { "Log In" }}</button>
|
||||
</nav>
|
||||
<main>
|
||||
<Routes transition=true fallback=|| "This page could not be found.">
|
||||
<Routes fallback=|| "This page could not be found.">
|
||||
// paths can be created using the path!() macro, or provided as types like
|
||||
// StaticSegment("about")
|
||||
<Route path=path!("about") view=About/>
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
.routing-progress {
|
||||
width: 100%;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
a[aria-current] {
|
||||
font-weight: bold;
|
||||
}
|
||||
@@ -17,8 +12,12 @@ a[aria-current] {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.contact {
|
||||
view-transition-name: contact;
|
||||
.fadeIn {
|
||||
animation: 0.5s fadeIn forwards;
|
||||
}
|
||||
|
||||
.fadeOut {
|
||||
animation: 0.5s fadeOut forwards;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
@@ -41,44 +40,12 @@ a[aria-current] {
|
||||
}
|
||||
}
|
||||
|
||||
.router-outlet-0 main {
|
||||
view-transition-name: main;
|
||||
.slideIn {
|
||||
animation: 0.25s slideIn forwards;
|
||||
}
|
||||
|
||||
.router-back main {
|
||||
view-transition-name: main-back;
|
||||
}
|
||||
|
||||
.router-outlet-1 .contact-list {
|
||||
view-transition-name: contact;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
::view-transition-old(contact) {
|
||||
animation: 0.5s fadeOut;
|
||||
}
|
||||
|
||||
::view-transition-new(contact) {
|
||||
animation: 0.5s fadeIn;
|
||||
}
|
||||
|
||||
::view-transition-old(main) {
|
||||
animation: 0.5s slideOut;
|
||||
}
|
||||
|
||||
::view-transition-new(main) {
|
||||
animation: 0.5s slideIn;
|
||||
}
|
||||
|
||||
::view-transition-old(main-back) {
|
||||
color: red;
|
||||
animation: 0.5s slideOutBack;
|
||||
}
|
||||
|
||||
::view-transition-new(main-back) {
|
||||
color: blue;
|
||||
animation: 0.5s slideInBack;
|
||||
}
|
||||
.slideOut {
|
||||
animation: 0.25s slideOut forwards;
|
||||
}
|
||||
|
||||
@keyframes slideIn {
|
||||
@@ -99,6 +66,14 @@ a[aria-current] {
|
||||
}
|
||||
}
|
||||
|
||||
.slideInBack {
|
||||
animation: 0.25s slideInBack forwards;
|
||||
}
|
||||
|
||||
.slideOutBack {
|
||||
animation: 0.25s slideOutBack forwards;
|
||||
}
|
||||
|
||||
@keyframes slideInBack {
|
||||
from {
|
||||
transform: translate(-100vw, 0);
|
||||
|
||||
@@ -1993,7 +1993,7 @@ where
|
||||
move |uri: Uri, State(options): State<S>, req: Request<Body>| {
|
||||
Box::pin(async move {
|
||||
let options = LeptosOptions::from_ref(&options);
|
||||
let res = get_static_file(uri, &options.site_root, req.headers());
|
||||
let res = get_static_file(uri, &options.site_root);
|
||||
let res = res.await.unwrap();
|
||||
|
||||
if res.status() == StatusCode::OK {
|
||||
@@ -2027,26 +2027,14 @@ where
|
||||
async fn get_static_file(
|
||||
uri: Uri,
|
||||
root: &str,
|
||||
headers: &HeaderMap<HeaderValue>,
|
||||
) -> Result<Response<Body>, (StatusCode, String)> {
|
||||
use axum::http::header::ACCEPT_ENCODING;
|
||||
|
||||
let req = Request::builder().uri(uri);
|
||||
|
||||
let req = match headers.get(ACCEPT_ENCODING) {
|
||||
Some(value) => req.header(ACCEPT_ENCODING, value),
|
||||
None => req,
|
||||
};
|
||||
|
||||
let req = req.body(Body::empty()).unwrap();
|
||||
let req = Request::builder()
|
||||
.uri(uri.clone())
|
||||
.body(Body::empty())
|
||||
.unwrap();
|
||||
// `ServeDir` implements `tower::Service` so we can call it with `tower::ServiceExt::oneshot`
|
||||
// This path is relative to the cargo root
|
||||
match ServeDir::new(root)
|
||||
.precompressed_gzip()
|
||||
.precompressed_br()
|
||||
.oneshot(req)
|
||||
.await
|
||||
{
|
||||
match ServeDir::new(root).oneshot(req).await {
|
||||
Ok(res) => Ok(res.into_response()),
|
||||
Err(err) => Err((
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
|
||||
@@ -28,7 +28,7 @@ paste = "1.0"
|
||||
rand = { version = "0.8.5", optional = true }
|
||||
reactive_graph = { workspace = true, features = ["serde"] }
|
||||
rustc-hash = "2.0"
|
||||
tachys = { workspace = true, features = ["reactive_graph", "reactive_stores", "oco"] }
|
||||
tachys = { workspace = true, features = ["reactive_graph", "oco"] }
|
||||
thiserror = "1.0"
|
||||
tracing = { version = "0.1.40", optional = true }
|
||||
typed-builder = "0.19.1"
|
||||
|
||||
@@ -926,7 +926,7 @@ pub fn server(args: proc_macro::TokenStream, s: TokenStream) -> TokenStream {
|
||||
|
||||
/// Derives a trait that parses a map of string keys and values into a typed
|
||||
/// data structure, e.g., for route params.
|
||||
#[proc_macro_derive(Params)]
|
||||
#[proc_macro_derive(Params, attributes(params))]
|
||||
pub fn params_derive(
|
||||
input: proc_macro::TokenStream,
|
||||
) -> proc_macro::TokenStream {
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
use super::{
|
||||
fragment_to_tokens, utils::is_nostrip_optional_and_update_key, TagType,
|
||||
};
|
||||
use super::{fragment_to_tokens, TagType};
|
||||
use crate::view::{attribute_absolute, utils::filter_prefixed_attrs};
|
||||
use proc_macro2::{Ident, TokenStream, TokenTree};
|
||||
use quote::{format_ident, quote, quote_spanned};
|
||||
@@ -46,10 +44,9 @@ pub(crate) fn component_to_tokens(
|
||||
})
|
||||
.unwrap_or_else(|| node.attributes().len());
|
||||
|
||||
// Initially using uncloned mutable reference, as the node.key might be mutated during prop extraction (for nostrip:)
|
||||
let mut attrs = node
|
||||
.attributes_mut()
|
||||
.iter_mut()
|
||||
let attrs = node
|
||||
.attributes()
|
||||
.iter()
|
||||
.filter_map(|node| {
|
||||
if let NodeAttribute::Attribute(node) = node {
|
||||
Some(node)
|
||||
@@ -57,46 +54,39 @@ pub(crate) fn component_to_tokens(
|
||||
None
|
||||
}
|
||||
})
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut required_props = vec![];
|
||||
let mut optional_props = vec![];
|
||||
for (_, attr) in attrs.iter_mut().enumerate().filter(|(idx, attr)| {
|
||||
idx < &spread_marker && {
|
||||
let attr_key = attr.key.to_string();
|
||||
!is_attr_let(&attr.key)
|
||||
&& !attr_key.starts_with("clone:")
|
||||
&& !attr_key.starts_with("class:")
|
||||
&& !attr_key.starts_with("style:")
|
||||
&& !attr_key.starts_with("attr:")
|
||||
&& !attr_key.starts_with("prop:")
|
||||
&& !attr_key.starts_with("on:")
|
||||
&& !attr_key.starts_with("use:")
|
||||
}
|
||||
}) {
|
||||
let optional = is_nostrip_optional_and_update_key(&mut attr.key);
|
||||
let name = &attr.key;
|
||||
let props = attrs
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(idx, attr)| {
|
||||
idx < &spread_marker && {
|
||||
let attr_key = attr.key.to_string();
|
||||
!is_attr_let(&attr.key)
|
||||
&& !attr_key.starts_with("clone:")
|
||||
&& !attr_key.starts_with("class:")
|
||||
&& !attr_key.starts_with("style:")
|
||||
&& !attr_key.starts_with("attr:")
|
||||
&& !attr_key.starts_with("prop:")
|
||||
&& !attr_key.starts_with("on:")
|
||||
&& !attr_key.starts_with("use:")
|
||||
}
|
||||
})
|
||||
.map(|(_, attr)| {
|
||||
let name = &attr.key;
|
||||
|
||||
let value = attr
|
||||
.value()
|
||||
.map(|v| {
|
||||
quote! { #v }
|
||||
})
|
||||
.unwrap_or_else(|| quote! { #name });
|
||||
let value = attr
|
||||
.value()
|
||||
.map(|v| {
|
||||
quote! { #v }
|
||||
})
|
||||
.unwrap_or_else(|| quote! { #name });
|
||||
|
||||
if optional {
|
||||
optional_props.push(quote! {
|
||||
props.#name = { #value }.map(Into::into);
|
||||
})
|
||||
} else {
|
||||
required_props.push(quote! {
|
||||
quote! {
|
||||
.#name(#[allow(unused_braces)] { #value })
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Drop the mutable reference to the node, go to an owned clone:
|
||||
let attrs = attrs.into_iter().map(|a| a.clone()).collect::<Vec<_>>();
|
||||
}
|
||||
});
|
||||
|
||||
let items_to_bind = attrs
|
||||
.iter()
|
||||
@@ -274,20 +264,14 @@ pub(crate) fn component_to_tokens(
|
||||
let mut component = quote! {
|
||||
{
|
||||
#[allow(unreachable_code)]
|
||||
#[allow(unused_mut)]
|
||||
#[allow(clippy::let_and_return)]
|
||||
::leptos::component::component_view(
|
||||
#[allow(clippy::needless_borrows_for_generic_args)]
|
||||
&#name,
|
||||
{
|
||||
let mut props = ::leptos::component::component_props_builder(&#name #generics)
|
||||
#(#required_props)*
|
||||
#(#slots)*
|
||||
#children
|
||||
.build();
|
||||
#(#optional_props)*
|
||||
props
|
||||
}
|
||||
::leptos::component::component_props_builder(&#name #generics)
|
||||
#(#props)*
|
||||
#(#slots)*
|
||||
#children
|
||||
.build()
|
||||
)
|
||||
#spreads
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use proc_macro2::Ident;
|
||||
use quote::format_ident;
|
||||
use rstml::node::{KeyedAttribute, NodeName};
|
||||
use syn::{spanned::Spanned, ExprPath};
|
||||
use rstml::node::KeyedAttribute;
|
||||
use syn::spanned::Spanned;
|
||||
|
||||
pub fn filter_prefixed_attrs<'a, A>(attrs: A, prefix: &str) -> Vec<Ident>
|
||||
where
|
||||
@@ -17,37 +17,3 @@ where
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Handle nostrip: prefix:
|
||||
/// if there strip from the name, and return true to indicate that
|
||||
/// the prop should be an Option<T> and shouldn't be called on the builder if None,
|
||||
/// if Some(T) then T supplied to the builder.
|
||||
pub fn is_nostrip_optional_and_update_key(key: &mut NodeName) -> bool {
|
||||
let maybe_cleaned_name_and_span = if let NodeName::Punctuated(punct) = &key
|
||||
{
|
||||
if punct.len() == 2 {
|
||||
if let Some(cleaned_name) = key.to_string().strip_prefix("nostrip:")
|
||||
{
|
||||
punct
|
||||
.get(1)
|
||||
.map(|segment| (cleaned_name.to_string(), segment.span()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
if let Some((cleaned_name, span)) = maybe_cleaned_name_and_span {
|
||||
*key = NodeName::Path(ExprPath {
|
||||
attrs: vec![],
|
||||
qself: None,
|
||||
path: format_ident!("{}", cleaned_name, span = span).into(),
|
||||
});
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ use leptos::prelude::*;
|
||||
#[component]
|
||||
fn Component(
|
||||
#[prop(optional)] optional: bool,
|
||||
#[prop(optional, into)] optional_into: Option<String>,
|
||||
#[prop(optional_no_strip)] optional_no_strip: Option<String>,
|
||||
#[prop(strip_option)] strip_option: Option<u8>,
|
||||
#[prop(default = NonZeroUsize::new(10).unwrap())] default: NonZeroUsize,
|
||||
@@ -12,7 +11,6 @@ fn Component(
|
||||
impl_trait: impl Fn() -> i32 + 'static,
|
||||
) -> impl IntoView {
|
||||
_ = optional;
|
||||
_ = optional_into;
|
||||
_ = optional_no_strip;
|
||||
_ = strip_option;
|
||||
_ = default;
|
||||
@@ -28,29 +26,9 @@ fn component() {
|
||||
.impl_trait(|| 42)
|
||||
.build();
|
||||
assert!(!cp.optional);
|
||||
assert_eq!(cp.optional_into, None);
|
||||
assert_eq!(cp.optional_no_strip, None);
|
||||
assert_eq!(cp.strip_option, Some(9));
|
||||
assert_eq!(cp.default, NonZeroUsize::new(10).unwrap());
|
||||
assert_eq!(cp.into, "");
|
||||
assert_eq!((cp.impl_trait)(), 42);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn component_nostrip() {
|
||||
// Should compile (using nostrip:optional_into in second <Component />)
|
||||
view! {
|
||||
<Component
|
||||
optional_into="foo"
|
||||
strip_option=9
|
||||
into=""
|
||||
impl_trait=|| 42
|
||||
/>
|
||||
<Component
|
||||
nostrip:optional_into=Some("foo")
|
||||
strip_option=9
|
||||
into=""
|
||||
impl_trait=|| 42
|
||||
/>
|
||||
};
|
||||
}
|
||||
|
||||
@@ -76,38 +76,6 @@ impl<T, Ser> Debug for ArcResource<T, Ser> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, Ser> From<ArcResource<T, Ser>> for Resource<T, Ser>
|
||||
where
|
||||
T: Send + Sync,
|
||||
{
|
||||
#[track_caller]
|
||||
fn from(arc_resource: ArcResource<T, Ser>) -> Self {
|
||||
Resource {
|
||||
ser: PhantomData,
|
||||
data: arc_resource.data.into(),
|
||||
refetch: arc_resource.refetch.into(),
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: Location::caller(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, Ser> From<Resource<T, Ser>> for ArcResource<T, Ser>
|
||||
where
|
||||
T: Send + Sync,
|
||||
{
|
||||
#[track_caller]
|
||||
fn from(resource: Resource<T, Ser>) -> Self {
|
||||
ArcResource {
|
||||
ser: PhantomData,
|
||||
data: resource.data.into(),
|
||||
refetch: resource.refetch.into(),
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: Location::caller(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, Ser> DefinedAt for ArcResource<T, Ser> {
|
||||
fn defined_at(&self) -> Option<&'static Location<'static>> {
|
||||
#[cfg(debug_assertions)]
|
||||
@@ -161,23 +129,22 @@ where
|
||||
#[cfg(all(feature = "hydration", debug_assertions))]
|
||||
{
|
||||
use reactive_graph::{
|
||||
computed::suspense::SuspenseContext, effect::in_effect_scope,
|
||||
owner::use_context,
|
||||
computed::suspense::SuspenseContext, owner::use_context,
|
||||
};
|
||||
if !in_effect_scope() && use_context::<SuspenseContext>().is_none()
|
||||
{
|
||||
let suspense = use_context::<SuspenseContext>();
|
||||
if suspense.is_none() {
|
||||
let location = std::panic::Location::caller();
|
||||
reactive_graph::log_warning(format_args!(
|
||||
"At {location}, you are reading a resource in `hydrate` \
|
||||
mode outside a <Suspense/> or <Transition/> or effect. \
|
||||
This can cause hydration mismatch errors and loses out \
|
||||
on a significant performance optimization. To fix this \
|
||||
issue, you can either: \n1. Wrap the place where you \
|
||||
read the resource in a <Suspense/> or <Transition/> \
|
||||
component, or \n2. Switch to using \
|
||||
ArcLocalResource::new(), which will wait to load the \
|
||||
resource until the app is hydrated on the client side. \
|
||||
(This will have worse performance in most cases.)",
|
||||
mode outside a <Suspense/> or <Transition/>. This can \
|
||||
cause hydration mismatch errors and loses out on a \
|
||||
significant performance optimization. To fix this issue, \
|
||||
you can either: \n1. Wrap the place where you read the \
|
||||
resource in a <Suspense/> or <Transition/> component, or \
|
||||
\n2. Switch to using ArcLocalResource::new(), which will \
|
||||
wait to load the resource until the app is hydrated on \
|
||||
the client side. (This will have worse performance in \
|
||||
most cases.)",
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -674,23 +641,22 @@ where
|
||||
#[cfg(all(feature = "hydration", debug_assertions))]
|
||||
{
|
||||
use reactive_graph::{
|
||||
computed::suspense::SuspenseContext, effect::in_effect_scope,
|
||||
owner::use_context,
|
||||
computed::suspense::SuspenseContext, owner::use_context,
|
||||
};
|
||||
if !in_effect_scope() && use_context::<SuspenseContext>().is_none()
|
||||
{
|
||||
let suspense = use_context::<SuspenseContext>();
|
||||
if suspense.is_none() {
|
||||
let location = std::panic::Location::caller();
|
||||
reactive_graph::log_warning(format_args!(
|
||||
"At {location}, you are reading a resource in `hydrate` \
|
||||
mode outside a <Suspense/> or <Transition/> or effect. \
|
||||
This can cause hydration mismatch errors and loses out \
|
||||
on a significant performance optimization. To fix this \
|
||||
issue, you can either: \n1. Wrap the place where you \
|
||||
read the resource in a <Suspense/> or <Transition/> \
|
||||
component, or \n2. Switch to using LocalResource::new(), \
|
||||
which will wait to load the resource until the app is \
|
||||
hydrated on the client side. (This will have worse \
|
||||
performance in most cases.)",
|
||||
mode outside a <Suspense/> or <Transition/>. This can \
|
||||
cause hydration mismatch errors and loses out on a \
|
||||
significant performance optimization. To fix this issue, \
|
||||
you can either: \n1. Wrap the place where you read the \
|
||||
resource in a <Suspense/> or <Transition/> component, or \
|
||||
\n2. Switch to using LocalResource::new(), which will \
|
||||
wait to load the resource until the app is hydrated on \
|
||||
the client side. (This will have worse performance in \
|
||||
most cases.)",
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,19 +109,6 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<AsyncDerived<T>> for ArcAsyncDerived<T>
|
||||
where
|
||||
T: Send + Sync + 'static,
|
||||
{
|
||||
#[track_caller]
|
||||
fn from(value: AsyncDerived<T>) -> Self {
|
||||
value
|
||||
.inner
|
||||
.try_get_value()
|
||||
.unwrap_or_else(unwrap_signal!(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> FromLocal<ArcAsyncDerived<T>> for AsyncDerived<T, LocalStorage>
|
||||
where
|
||||
T: 'static,
|
||||
|
||||
@@ -13,7 +13,7 @@ use futures::StreamExt;
|
||||
use or_poisoned::OrPoisoned;
|
||||
use std::{
|
||||
mem,
|
||||
sync::{atomic::AtomicBool, Arc, RwLock},
|
||||
sync::{Arc, RwLock},
|
||||
};
|
||||
|
||||
/// Effects run a certain chunk of code whenever the signals they depend on change.
|
||||
@@ -109,29 +109,6 @@ fn effect_base() -> (Receiver, Owner, Arc<RwLock<EffectInner>>) {
|
||||
(rx, owner, inner)
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
static EFFECT_SCOPE_ACTIVE: AtomicBool = const { AtomicBool::new(false) };
|
||||
}
|
||||
|
||||
/// Returns whether the current thread is currently running an effect.
|
||||
pub fn in_effect_scope() -> bool {
|
||||
EFFECT_SCOPE_ACTIVE
|
||||
.with(|scope| scope.load(std::sync::atomic::Ordering::Relaxed))
|
||||
}
|
||||
|
||||
/// Set a static to true whilst running the given function.
|
||||
/// [`is_in_effect_scope`] will return true whilst the function is running.
|
||||
fn run_in_effect_scope<T>(fun: impl FnOnce() -> T) -> T {
|
||||
// For the theoretical nested case, set back to initial value rather than false:
|
||||
let initial = EFFECT_SCOPE_ACTIVE
|
||||
.with(|scope| scope.swap(true, std::sync::atomic::Ordering::Relaxed));
|
||||
let result = fun();
|
||||
EFFECT_SCOPE_ACTIVE.with(|scope| {
|
||||
scope.store(initial, std::sync::atomic::Ordering::Relaxed)
|
||||
});
|
||||
result
|
||||
}
|
||||
|
||||
impl<S> Effect<S>
|
||||
where
|
||||
S: Storage<StoredEffect>,
|
||||
@@ -180,9 +157,7 @@ impl Effect<LocalStorage> {
|
||||
let old_value =
|
||||
mem::take(&mut *value.write().or_poisoned());
|
||||
let new_value = owner.with_cleanup(|| {
|
||||
subscriber.with_observer(|| {
|
||||
run_in_effect_scope(|| fun.run(old_value))
|
||||
})
|
||||
subscriber.with_observer(|| fun.run(old_value))
|
||||
});
|
||||
*value.write().or_poisoned() = Some(new_value);
|
||||
}
|
||||
@@ -400,9 +375,7 @@ impl Effect<SyncStorage> {
|
||||
let old_value =
|
||||
mem::take(&mut *value.write().or_poisoned());
|
||||
let new_value = owner.with_cleanup(|| {
|
||||
subscriber.with_observer(|| {
|
||||
run_in_effect_scope(|| fun.run(old_value))
|
||||
})
|
||||
subscriber.with_observer(|| fun.run(old_value))
|
||||
});
|
||||
*value.write().or_poisoned() = Some(new_value);
|
||||
}
|
||||
@@ -446,9 +419,7 @@ impl Effect<SyncStorage> {
|
||||
let old_value =
|
||||
mem::take(&mut *value.write().or_poisoned());
|
||||
let new_value = owner.with_cleanup(|| {
|
||||
subscriber.with_observer(|| {
|
||||
run_in_effect_scope(|| fun.run(old_value))
|
||||
})
|
||||
subscriber.with_observer(|| fun.run(old_value))
|
||||
});
|
||||
*value.write().or_poisoned() = Some(new_value);
|
||||
}
|
||||
|
||||
@@ -69,16 +69,16 @@ where
|
||||
Chil: IntoView,
|
||||
{
|
||||
#[cfg(feature = "ssr")]
|
||||
let (location_provider, current_url, redirect_hook) = {
|
||||
let (current_url, redirect_hook) = {
|
||||
let req = use_context::<RequestUrl>().expect("no RequestUrl provided");
|
||||
let parsed = req.parse().expect("could not parse RequestUrl");
|
||||
let current_url = ArcRwSignal::new(parsed);
|
||||
|
||||
(None, current_url, Box::new(move |_: &str| {}))
|
||||
(current_url, Box::new(move |_: &str| {}))
|
||||
};
|
||||
|
||||
#[cfg(not(feature = "ssr"))]
|
||||
let (location_provider, current_url, redirect_hook) = {
|
||||
let (current_url, redirect_hook) = {
|
||||
let location =
|
||||
BrowserUrl::new().expect("could not access browser navigation"); // TODO options here
|
||||
location.init(base.clone());
|
||||
@@ -87,7 +87,7 @@ where
|
||||
|
||||
let redirect_hook = Box::new(|loc: &str| BrowserUrl::redirect(loc));
|
||||
|
||||
(Some(location), current_url, redirect_hook)
|
||||
(current_url, redirect_hook)
|
||||
};
|
||||
// provide router context
|
||||
let state = ArcRwSignal::new(State::new(None));
|
||||
@@ -103,7 +103,6 @@ where
|
||||
state,
|
||||
set_is_routing,
|
||||
query_mutations: Default::default(),
|
||||
location_provider,
|
||||
});
|
||||
|
||||
let children = children.into_inner();
|
||||
@@ -119,7 +118,6 @@ pub(crate) struct RouterContext {
|
||||
pub set_is_routing: Option<SignalSetter<bool>>,
|
||||
pub query_mutations:
|
||||
ArcStoredValue<Vec<(Oco<'static, str>, Option<String>)>>,
|
||||
pub location_provider: Option<BrowserUrl>,
|
||||
}
|
||||
|
||||
impl RouterContext {
|
||||
@@ -181,14 +179,12 @@ impl RouterContext {
|
||||
self.current_url.set(url);
|
||||
}
|
||||
|
||||
if let Some(location_provider) = &self.location_provider {
|
||||
location_provider.complete_navigation(&LocationChange {
|
||||
value,
|
||||
replace: options.replace,
|
||||
scroll: options.scroll,
|
||||
state: options.state,
|
||||
});
|
||||
}
|
||||
BrowserUrl::complete_navigation(&LocationChange {
|
||||
value,
|
||||
replace: options.replace,
|
||||
scroll: options.scroll,
|
||||
state: options.state,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn resolve_path<'a>(
|
||||
@@ -232,9 +228,6 @@ where
|
||||
#[component(transparent)]
|
||||
pub fn Routes<Defs, FallbackFn, Fallback>(
|
||||
fallback: FallbackFn,
|
||||
/// Whether to use the View Transition API during navigation.
|
||||
#[prop(optional)]
|
||||
transition: bool,
|
||||
children: RouteChildren<Defs>,
|
||||
) -> impl IntoView
|
||||
where
|
||||
@@ -274,7 +267,6 @@ where
|
||||
base: base.clone(),
|
||||
fallback: fallback.clone(),
|
||||
set_is_routing,
|
||||
transition,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -282,9 +274,6 @@ where
|
||||
#[component(transparent)]
|
||||
pub fn FlatRoutes<Defs, FallbackFn, Fallback>(
|
||||
fallback: FallbackFn,
|
||||
/// Whether to use the View Transition API during navigation.
|
||||
#[prop(optional)]
|
||||
transition: bool,
|
||||
children: RouteChildren<Defs>,
|
||||
) -> impl IntoView
|
||||
where
|
||||
@@ -328,7 +317,6 @@ where
|
||||
fallback: fallback.clone(),
|
||||
outer_owner: outer_owner.clone(),
|
||||
set_is_routing,
|
||||
transition,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,18 +3,17 @@ use crate::{
|
||||
location::{LocationProvider, Url},
|
||||
matching::Routes,
|
||||
params::ParamsMap,
|
||||
view_transition::start_view_transition,
|
||||
ChooseView, MatchInterface, MatchNestedRoutes, MatchParams, PathSegment,
|
||||
RouteList, RouteListing, RouteMatchId,
|
||||
};
|
||||
use any_spawner::Executor;
|
||||
use either_of::Either;
|
||||
use either_of::{Either, EitherOf3};
|
||||
use futures::FutureExt;
|
||||
use reactive_graph::{
|
||||
computed::{ArcMemo, ScopedFuture},
|
||||
owner::{provide_context, Owner},
|
||||
signal::ArcRwSignal,
|
||||
traits::{GetUntracked, ReadUntracked, Set},
|
||||
traits::{ReadUntracked, Set},
|
||||
transition::AsyncTransition,
|
||||
wrappers::write::SignalSetter,
|
||||
};
|
||||
@@ -24,9 +23,8 @@ use tachys::{
|
||||
reactive_graph::OwnedView,
|
||||
ssr::StreamBuilder,
|
||||
view::{
|
||||
add_attr::AddAnyAttr,
|
||||
any_view::{AnyView, AnyViewState, IntoAny},
|
||||
Mountable, Position, PositionState, Render, RenderHtml,
|
||||
add_attr::AddAnyAttr, Mountable, Position, PositionState, Render,
|
||||
RenderHtml,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -37,21 +35,28 @@ pub(crate) struct FlatRoutesView<Loc, Defs, FalFn> {
|
||||
pub fallback: FalFn,
|
||||
pub outer_owner: Owner,
|
||||
pub set_is_routing: Option<SignalSetter<bool>>,
|
||||
pub transition: bool,
|
||||
}
|
||||
|
||||
pub struct FlatRoutesViewState {
|
||||
pub struct FlatRoutesViewState<Defs, Fal>
|
||||
where
|
||||
Defs: MatchNestedRoutes + 'static,
|
||||
Fal: Render + 'static,
|
||||
{
|
||||
#[allow(clippy::type_complexity)]
|
||||
view: AnyViewState,
|
||||
view: <EitherOf3<(), Fal, OwnedView<<Defs::Match as MatchInterface>::View>> as Render>::State,
|
||||
id: Option<RouteMatchId>,
|
||||
owner: Owner,
|
||||
params: ArcRwSignal<ParamsMap>,
|
||||
path: String,
|
||||
url: ArcRwSignal<Url>,
|
||||
matched: ArcRwSignal<String>,
|
||||
matched: ArcRwSignal<String>
|
||||
}
|
||||
|
||||
impl Mountable for FlatRoutesViewState {
|
||||
impl<Defs, Fal> Mountable for FlatRoutesViewState<Defs, Fal>
|
||||
where
|
||||
Defs: MatchNestedRoutes + 'static,
|
||||
Fal: Render + 'static,
|
||||
{
|
||||
fn unmount(&mut self) {
|
||||
self.view.unmount();
|
||||
}
|
||||
@@ -74,9 +79,9 @@ where
|
||||
Loc: LocationProvider,
|
||||
Defs: MatchNestedRoutes + 'static,
|
||||
FalFn: FnOnce() -> Fal + Send,
|
||||
Fal: IntoAny,
|
||||
Fal: Render + 'static,
|
||||
{
|
||||
type State = Rc<RefCell<FlatRoutesViewState>>;
|
||||
type State = Rc<RefCell<FlatRoutesViewState<Defs, Fal>>>;
|
||||
|
||||
fn build(self) -> Self::State {
|
||||
let FlatRoutesView {
|
||||
@@ -116,7 +121,7 @@ where
|
||||
|
||||
match new_match {
|
||||
None => Rc::new(RefCell::new(FlatRoutesViewState {
|
||||
view: fallback().into_any().build(),
|
||||
view: EitherOf3::B(fallback()).build(),
|
||||
id,
|
||||
owner,
|
||||
params,
|
||||
@@ -149,7 +154,7 @@ where
|
||||
|
||||
match view.as_mut().now_or_never() {
|
||||
Some(view) => Rc::new(RefCell::new(FlatRoutesViewState {
|
||||
view: view.into_any().build(),
|
||||
view: EitherOf3::C(view).build(),
|
||||
id,
|
||||
owner,
|
||||
params,
|
||||
@@ -160,7 +165,7 @@ where
|
||||
None => {
|
||||
let state =
|
||||
Rc::new(RefCell::new(FlatRoutesViewState {
|
||||
view: ().into_any().build(),
|
||||
view: EitherOf3::A(()).build(),
|
||||
id,
|
||||
owner,
|
||||
params,
|
||||
@@ -173,7 +178,7 @@ where
|
||||
let state = Rc::clone(&state);
|
||||
async move {
|
||||
let view = view.await;
|
||||
view.into_any()
|
||||
EitherOf3::C(view)
|
||||
.rebuild(&mut state.borrow_mut().view);
|
||||
}
|
||||
});
|
||||
@@ -193,7 +198,6 @@ where
|
||||
fallback,
|
||||
outer_owner,
|
||||
set_is_routing,
|
||||
transition,
|
||||
} = self;
|
||||
let url_snapshot = current_url.read_untracked();
|
||||
|
||||
@@ -263,7 +267,8 @@ where
|
||||
provide_context(url);
|
||||
provide_context(params_memo);
|
||||
provide_context(Matched(ArcMemo::from(new_matched)));
|
||||
fallback().into_any().rebuild(&mut state.borrow_mut().view)
|
||||
EitherOf3::B(fallback())
|
||||
.rebuild(&mut state.borrow_mut().view)
|
||||
});
|
||||
}
|
||||
Some(new_match) => {
|
||||
@@ -278,10 +283,6 @@ where
|
||||
|
||||
let spawned_path = url_snapshot.path().to_string();
|
||||
|
||||
let is_back = location
|
||||
.as_ref()
|
||||
.map(|nav| nav.is_back().get_untracked())
|
||||
.unwrap_or(false);
|
||||
Executor::spawn_local(owner.with(|| {
|
||||
ScopedFuture::new({
|
||||
let state = Rc::clone(state);
|
||||
@@ -309,15 +310,8 @@ where
|
||||
if current_url.read_untracked().path()
|
||||
== spawned_path
|
||||
{
|
||||
let rebuild = move || {
|
||||
view.into_any()
|
||||
.rebuild(&mut state.borrow_mut().view);
|
||||
};
|
||||
if transition {
|
||||
start_view_transition(0, is_back, rebuild);
|
||||
} else {
|
||||
rebuild();
|
||||
}
|
||||
EitherOf3::C(view)
|
||||
.rebuild(&mut state.borrow_mut().view);
|
||||
}
|
||||
|
||||
if let Some(location) = location {
|
||||
@@ -363,7 +357,9 @@ where
|
||||
FalFn: FnOnce() -> Fal + Send,
|
||||
Fal: RenderHtml + 'static,
|
||||
{
|
||||
fn choose_ssr(self) -> OwnedView<AnyView> {
|
||||
fn choose_ssr(
|
||||
self,
|
||||
) -> OwnedView<Either<Fal, <Defs::Match as MatchInterface>::View>> {
|
||||
let current_url = self.current_url.read_untracked();
|
||||
let new_match = self.routes.match_route(current_url.path());
|
||||
let owner = self.outer_owner.child();
|
||||
@@ -386,7 +382,7 @@ where
|
||||
drop(current_url);
|
||||
|
||||
let view = match new_match {
|
||||
None => (self.fallback)().into_any(),
|
||||
None => Either::Left((self.fallback)()),
|
||||
Some(new_match) => {
|
||||
let (view, _) = new_match.into_view_and_child();
|
||||
let view = owner
|
||||
@@ -400,7 +396,7 @@ where
|
||||
})
|
||||
.now_or_never()
|
||||
.expect("async route used in SSR");
|
||||
view.into_any()
|
||||
Either::Right(view)
|
||||
}
|
||||
};
|
||||
|
||||
@@ -417,7 +413,10 @@ where
|
||||
{
|
||||
type AsyncOutput = Self;
|
||||
|
||||
const MIN_LENGTH: usize = <Either<Fal, AnyView> as RenderHtml>::MIN_LENGTH;
|
||||
const MIN_LENGTH: usize = <Either<
|
||||
Fal,
|
||||
<Defs::Match as MatchInterface>::View,
|
||||
> as RenderHtml>::MIN_LENGTH;
|
||||
|
||||
fn dry_resolve(&mut self) {}
|
||||
|
||||
@@ -547,8 +546,7 @@ where
|
||||
|
||||
match new_match {
|
||||
None => Rc::new(RefCell::new(FlatRoutesViewState {
|
||||
view: fallback()
|
||||
.into_any()
|
||||
view: EitherOf3::B(fallback())
|
||||
.hydrate::<FROM_SERVER>(cursor, position),
|
||||
id,
|
||||
owner,
|
||||
@@ -582,8 +580,7 @@ where
|
||||
|
||||
match view.as_mut().now_or_never() {
|
||||
Some(view) => Rc::new(RefCell::new(FlatRoutesViewState {
|
||||
view: view
|
||||
.into_any()
|
||||
view: EitherOf3::C(view)
|
||||
.hydrate::<FROM_SERVER>(cursor, position),
|
||||
id,
|
||||
owner,
|
||||
|
||||
@@ -272,12 +272,37 @@ pub fn use_navigate() -> impl Fn(&str, NavigateOptions) + Clone {
|
||||
move |path: &str, options: NavigateOptions| cx.navigate(path, options)
|
||||
}
|
||||
|
||||
/// Returns a reactive string that contains the route that was matched for
|
||||
/// this [`Route`](crate::components::Route).
|
||||
#[track_caller]
|
||||
pub fn use_matched() -> Memo<String> {
|
||||
use_context::<Matched>()
|
||||
.expect("use_matched called outside a matched Route")
|
||||
.0
|
||||
.into()
|
||||
/*
|
||||
/// Returns a signal that tells you whether you are currently navigating backwards.
|
||||
pub(crate) fn use_is_back_navigation() -> ReadSignal<bool> {
|
||||
let router = use_router();
|
||||
router.inner.is_back.read_only()
|
||||
}
|
||||
*/
|
||||
|
||||
/* TODO check how this is used in 0.6 and use it
|
||||
/// Resolves a redirect location to an (absolute) URL.
|
||||
pub(crate) fn resolve_redirect_url(loc: &str) -> Option<web_sys::Url> {
|
||||
let origin = match window().location().origin() {
|
||||
Ok(origin) => origin,
|
||||
Err(e) => {
|
||||
leptos::logging::error!("Failed to get origin: {:#?}", e);
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
// TODO: Use server function's URL as base instead.
|
||||
let base = origin;
|
||||
|
||||
match web_sys::Url::new_with_base(loc, &base) {
|
||||
Ok(url) => Some(url),
|
||||
Err(e) => {
|
||||
leptos::logging::error!(
|
||||
"Invalid redirect location: {}",
|
||||
e.as_string().unwrap_or_default(),
|
||||
);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
@@ -24,71 +24,3 @@ pub use matching::*;
|
||||
pub use method::*;
|
||||
pub use navigate::*;
|
||||
pub use ssr_mode::*;
|
||||
|
||||
pub(crate) mod view_transition {
|
||||
use js_sys::{Function, Promise, Reflect};
|
||||
use leptos::leptos_dom::helpers::document;
|
||||
use wasm_bindgen::{closure::Closure, intern, JsCast, JsValue};
|
||||
|
||||
pub fn start_view_transition(
|
||||
level: u8,
|
||||
is_back_navigation: bool,
|
||||
fun: impl FnOnce() + 'static,
|
||||
) {
|
||||
let document = document();
|
||||
let document_element = document.document_element().unwrap();
|
||||
let class_list = document_element.class_list();
|
||||
let svt = Reflect::get(
|
||||
&document,
|
||||
&JsValue::from_str(intern("startViewTransition")),
|
||||
)
|
||||
.and_then(|svt| svt.dyn_into::<Function>());
|
||||
_ = class_list.add_1(&format!("router-outlet-{level}"));
|
||||
if is_back_navigation {
|
||||
_ = class_list.add_1("router-back");
|
||||
}
|
||||
match svt {
|
||||
Ok(svt) => {
|
||||
let cb = Closure::once_into_js(Box::new(move || {
|
||||
fun();
|
||||
}));
|
||||
match svt.call1(
|
||||
document.unchecked_ref(),
|
||||
cb.as_ref().unchecked_ref(),
|
||||
) {
|
||||
Ok(view_transition) => {
|
||||
let class_list = document_element.class_list();
|
||||
let finished = Reflect::get(
|
||||
&view_transition,
|
||||
&JsValue::from_str("finished"),
|
||||
)
|
||||
.expect("no `finished` property on ViewTransition")
|
||||
.unchecked_into::<Promise>();
|
||||
let cb = Closure::new(Box::new(move |_| {
|
||||
if is_back_navigation {
|
||||
class_list.remove_1("router-back").unwrap();
|
||||
}
|
||||
class_list
|
||||
.remove_1(&format!("router-outlet-{level}"))
|
||||
.unwrap();
|
||||
})
|
||||
as Box<dyn FnMut(JsValue)>);
|
||||
_ = finished.then(&cb);
|
||||
cb.into_js_value();
|
||||
}
|
||||
Err(e) => {
|
||||
web_sys::console::log_1(&e);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
leptos::logging::warn!(
|
||||
"NOTE: View transitions are not supported in this \
|
||||
browser; unless you provide a polyfill, view transitions \
|
||||
will not be applied."
|
||||
);
|
||||
fun();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,9 +22,7 @@ use web_sys::{Event, UrlSearchParams};
|
||||
#[derive(Clone)]
|
||||
pub struct BrowserUrl {
|
||||
url: ArcRwSignal<Url>,
|
||||
pub(crate) pending_navigation: Arc<Mutex<Option<oneshot::Sender<()>>>>,
|
||||
pub(crate) path_stack: ArcStoredValue<Vec<Url>>,
|
||||
pub(crate) is_back: ArcRwSignal<bool>,
|
||||
pending_navigation: Arc<Mutex<Option<oneshot::Sender<()>>>>,
|
||||
}
|
||||
|
||||
impl fmt::Debug for BrowserUrl {
|
||||
@@ -61,14 +59,10 @@ impl LocationProvider for BrowserUrl {
|
||||
|
||||
fn new() -> Result<Self, JsValue> {
|
||||
let url = ArcRwSignal::new(Self::current()?);
|
||||
let path_stack = ArcStoredValue::new(
|
||||
Self::current().map(|n| vec![n]).unwrap_or_default(),
|
||||
);
|
||||
let pending_navigation = Default::default();
|
||||
Ok(Self {
|
||||
url,
|
||||
pending_navigation: Default::default(),
|
||||
path_stack,
|
||||
is_back: Default::default(),
|
||||
pending_navigation,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -120,7 +114,6 @@ impl LocationProvider for BrowserUrl {
|
||||
let navigate = {
|
||||
let url = self.url.clone();
|
||||
let pending = Arc::clone(&self.pending_navigation);
|
||||
let this = self.clone();
|
||||
move |new_url: Url, loc| {
|
||||
let same_path = {
|
||||
let curr = url.read_untracked();
|
||||
@@ -130,7 +123,7 @@ impl LocationProvider for BrowserUrl {
|
||||
|
||||
url.set(new_url.clone());
|
||||
if same_path {
|
||||
this.complete_navigation(&loc);
|
||||
Self::complete_navigation(&loc);
|
||||
}
|
||||
let pending = Arc::clone(&pending);
|
||||
let (tx, rx) = oneshot::channel::<()>();
|
||||
@@ -138,7 +131,6 @@ impl LocationProvider for BrowserUrl {
|
||||
*pending.lock().or_poisoned() = Some(tx);
|
||||
}
|
||||
let url = url.clone();
|
||||
let this = this.clone();
|
||||
async move {
|
||||
if !same_path {
|
||||
// if it has been canceled, ignore
|
||||
@@ -149,7 +141,7 @@ impl LocationProvider for BrowserUrl {
|
||||
// browser URL
|
||||
let curr = url.read_untracked();
|
||||
if curr == new_url {
|
||||
this.complete_navigation(&loc);
|
||||
Self::complete_navigation(&loc);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -181,19 +173,8 @@ impl LocationProvider for BrowserUrl {
|
||||
// handle popstate event (forward/back navigation)
|
||||
let cb = {
|
||||
let url = self.url.clone();
|
||||
let path_stack = self.path_stack.clone();
|
||||
let is_back = self.is_back.clone();
|
||||
move || match Self::current() {
|
||||
Ok(new_url) => {
|
||||
let stack = path_stack.read_value();
|
||||
let is_navigating_back = stack.len() == 1
|
||||
|| (stack.len() >= 2
|
||||
&& stack.get(stack.len() - 2) == Some(&new_url));
|
||||
|
||||
is_back.set(is_navigating_back);
|
||||
|
||||
url.set(new_url);
|
||||
}
|
||||
Ok(new_url) => url.set(new_url),
|
||||
Err(e) => {
|
||||
#[cfg(feature = "tracing")]
|
||||
tracing::error!("{e:?}");
|
||||
@@ -218,7 +199,7 @@ impl LocationProvider for BrowserUrl {
|
||||
}
|
||||
}
|
||||
|
||||
fn complete_navigation(&self, loc: &LocationChange) {
|
||||
fn complete_navigation(loc: &LocationChange) {
|
||||
let history = window().history().unwrap();
|
||||
|
||||
if loc.replace {
|
||||
@@ -236,14 +217,6 @@ impl LocationProvider for BrowserUrl {
|
||||
.push_state_with_url(state, "", Some(&loc.value))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// add this URL to the "path stack" for detecting back navigations, and
|
||||
// unset "navigating back" state
|
||||
if let Ok(url) = Self::current() {
|
||||
self.path_stack.write_value().push(url);
|
||||
self.is_back.set(false);
|
||||
}
|
||||
|
||||
// scroll to el
|
||||
Self::scroll_to_el(loc.scroll);
|
||||
}
|
||||
@@ -265,10 +238,6 @@ impl LocationProvider for BrowserUrl {
|
||||
leptos::logging::error!("Failed to redirect: {e:#?}");
|
||||
}
|
||||
}
|
||||
|
||||
fn is_back(&self) -> ReadSignal<bool> {
|
||||
self.is_back.read_only().into()
|
||||
}
|
||||
}
|
||||
|
||||
fn search_params_from_web_url(
|
||||
|
||||
@@ -191,7 +191,7 @@ pub trait LocationProvider: Clone + 'static {
|
||||
fn ready_to_complete(&self);
|
||||
|
||||
/// Update the browser's history to reflect a new location.
|
||||
fn complete_navigation(&self, loc: &LocationChange);
|
||||
fn complete_navigation(loc: &LocationChange);
|
||||
|
||||
fn parse(url: &str) -> Result<Url, Self::Error> {
|
||||
Self::parse_with_base(url, BASE)
|
||||
@@ -200,9 +200,6 @@ pub trait LocationProvider: Clone + 'static {
|
||||
fn parse_with_base(url: &str, base: &str) -> Result<Url, Self::Error>;
|
||||
|
||||
fn redirect(loc: &str);
|
||||
|
||||
/// Whether we are currently in a "back" navigation.
|
||||
fn is_back(&self) -> ReadSignal<bool>;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
use either_of::*;
|
||||
use std::{future::Future, marker::PhantomData};
|
||||
use tachys::view::any_view::{AnyView, IntoAny};
|
||||
use tachys::view::{any_view::AnyView, Render};
|
||||
|
||||
pub trait ChooseView
|
||||
where
|
||||
Self: Send + Clone + 'static,
|
||||
{
|
||||
fn choose(self) -> impl Future<Output = AnyView>;
|
||||
type Output;
|
||||
|
||||
fn choose(self) -> impl Future<Output = Self::Output>;
|
||||
|
||||
fn preload(&self) -> impl Future<Output = ()>;
|
||||
}
|
||||
@@ -14,10 +16,12 @@ where
|
||||
impl<F, View> ChooseView for F
|
||||
where
|
||||
F: Fn() -> View + Send + Clone + 'static,
|
||||
View: IntoAny,
|
||||
View: Render + Send,
|
||||
{
|
||||
async fn choose(self) -> AnyView {
|
||||
self().into_any()
|
||||
type Output = View;
|
||||
|
||||
async fn choose(self) -> Self::Output {
|
||||
self()
|
||||
}
|
||||
|
||||
async fn preload(&self) {}
|
||||
@@ -27,8 +31,10 @@ impl<T> ChooseView for Lazy<T>
|
||||
where
|
||||
T: LazyRoute,
|
||||
{
|
||||
async fn choose(self) -> AnyView {
|
||||
T::data().view().await.into_any()
|
||||
type Output = AnyView;
|
||||
|
||||
async fn choose(self) -> Self::Output {
|
||||
T::data().view().await
|
||||
}
|
||||
|
||||
async fn preload(&self) {
|
||||
@@ -68,9 +74,9 @@ impl<T> Default for Lazy<T> {
|
||||
}
|
||||
|
||||
impl ChooseView for () {
|
||||
async fn choose(self) -> AnyView {
|
||||
().into_any()
|
||||
}
|
||||
type Output = ();
|
||||
|
||||
async fn choose(self) -> Self::Output {}
|
||||
|
||||
async fn preload(&self) {}
|
||||
}
|
||||
@@ -80,10 +86,12 @@ where
|
||||
A: ChooseView,
|
||||
B: ChooseView,
|
||||
{
|
||||
async fn choose(self) -> AnyView {
|
||||
type Output = Either<A::Output, B::Output>;
|
||||
|
||||
async fn choose(self) -> Self::Output {
|
||||
match self {
|
||||
Either::Left(f) => f.choose().await.into_any(),
|
||||
Either::Right(f) => f.choose().await.into_any(),
|
||||
Either::Left(f) => Either::Left(f.choose().await),
|
||||
Either::Right(f) => Either::Right(f.choose().await),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,9 +109,11 @@ macro_rules! tuples {
|
||||
where
|
||||
$($ty: ChooseView,)*
|
||||
{
|
||||
async fn choose(self ) -> AnyView {
|
||||
type Output = $either<$($ty::Output,)*>;
|
||||
|
||||
async fn choose(self ) -> Self::Output {
|
||||
match self {
|
||||
$($either::$ty(f) => f.choose().await.into_any(),)*
|
||||
$($either::$ty(f) => $either::$ty(f.choose().await),)*
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ use crate::{static_routes::RegenerationFn, Method, SsrMode};
|
||||
pub use horizontal::*;
|
||||
pub use nested::*;
|
||||
use std::{borrow::Cow, collections::HashSet};
|
||||
use tachys::view::{Render, RenderHtml};
|
||||
pub use vertical::*;
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -94,12 +95,15 @@ pub struct RouteMatchId(pub(crate) u16);
|
||||
|
||||
pub trait MatchInterface {
|
||||
type Child: MatchInterface + MatchParams + 'static;
|
||||
type View: Render + RenderHtml + Send + 'static;
|
||||
|
||||
fn as_id(&self) -> RouteMatchId;
|
||||
|
||||
fn as_matched(&self) -> &str;
|
||||
|
||||
fn into_view_and_child(self) -> (impl ChooseView, Option<Self::Child>);
|
||||
fn into_view_and_child(
|
||||
self,
|
||||
) -> (impl ChooseView<Output = Self::View>, Option<Self::Child>);
|
||||
}
|
||||
|
||||
pub trait MatchParams {
|
||||
@@ -110,12 +114,13 @@ pub trait MatchParams {
|
||||
|
||||
pub trait MatchNestedRoutes {
|
||||
type Data;
|
||||
type View;
|
||||
type Match: MatchInterface + MatchParams;
|
||||
|
||||
fn match_nested<'a>(
|
||||
&'a self,
|
||||
path: &'a str,
|
||||
) -> (Option<(RouteMatchId, Self::Match)>, &'a str);
|
||||
) -> (Option<(RouteMatchId, Self::Match)>, &str);
|
||||
|
||||
fn generate_routes(
|
||||
&self,
|
||||
|
||||
@@ -10,6 +10,7 @@ use std::{
|
||||
collections::HashSet,
|
||||
sync::atomic::{AtomicU16, Ordering},
|
||||
};
|
||||
use tachys::view::{Render, RenderHtml};
|
||||
|
||||
mod tuples;
|
||||
|
||||
@@ -140,8 +141,10 @@ impl<ParamsIter, Child, View> MatchInterface
|
||||
where
|
||||
Child: MatchInterface + MatchParams + 'static,
|
||||
View: ChooseView,
|
||||
View::Output: Render + RenderHtml + Send + 'static,
|
||||
{
|
||||
type Child = Child;
|
||||
type View = View::Output;
|
||||
|
||||
fn as_id(&self) -> RouteMatchId {
|
||||
self.id
|
||||
@@ -151,7 +154,9 @@ where
|
||||
&self.matched
|
||||
}
|
||||
|
||||
fn into_view_and_child(self) -> (impl ChooseView, Option<Self::Child>) {
|
||||
fn into_view_and_child(
|
||||
self,
|
||||
) -> (impl ChooseView<Output = Self::View>, Option<Self::Child>) {
|
||||
(self.view_fn, self.child)
|
||||
}
|
||||
}
|
||||
@@ -168,8 +173,10 @@ where
|
||||
Children: 'static,
|
||||
<Children::Match as MatchParams>::Params: Clone,
|
||||
View: ChooseView + Clone,
|
||||
View::Output: Render + RenderHtml + Send + 'static,
|
||||
{
|
||||
type Data = Data;
|
||||
type View = View::Output;
|
||||
type Match = NestedMatch<iter::Chain<
|
||||
<Segments::ParamsIter as IntoIterator>::IntoIter,
|
||||
Either<iter::Empty::<
|
||||
|
||||
@@ -14,6 +14,7 @@ impl MatchParams for () {
|
||||
|
||||
impl MatchInterface for () {
|
||||
type Child = ();
|
||||
type View = ();
|
||||
|
||||
fn as_id(&self) -> RouteMatchId {
|
||||
RouteMatchId(0)
|
||||
@@ -23,13 +24,16 @@ impl MatchInterface for () {
|
||||
""
|
||||
}
|
||||
|
||||
fn into_view_and_child(self) -> (impl ChooseView, Option<Self::Child>) {
|
||||
fn into_view_and_child(
|
||||
self,
|
||||
) -> (impl ChooseView<Output = Self::View>, Option<Self::Child>) {
|
||||
((), None)
|
||||
}
|
||||
}
|
||||
|
||||
impl MatchNestedRoutes for () {
|
||||
type Data = ();
|
||||
type View = ();
|
||||
type Match = ();
|
||||
|
||||
fn match_nested<'a>(
|
||||
@@ -65,6 +69,7 @@ where
|
||||
A: MatchInterface + 'static,
|
||||
{
|
||||
type Child = A::Child;
|
||||
type View = A::View;
|
||||
|
||||
fn as_id(&self) -> RouteMatchId {
|
||||
self.0.as_id()
|
||||
@@ -74,7 +79,9 @@ where
|
||||
self.0.as_matched()
|
||||
}
|
||||
|
||||
fn into_view_and_child(self) -> (impl ChooseView, Option<Self::Child>) {
|
||||
fn into_view_and_child(
|
||||
self,
|
||||
) -> (impl ChooseView<Output = Self::View>, Option<Self::Child>) {
|
||||
self.0.into_view_and_child()
|
||||
}
|
||||
}
|
||||
@@ -84,6 +91,7 @@ where
|
||||
A: MatchNestedRoutes + 'static,
|
||||
{
|
||||
type Data = A::Data;
|
||||
type View = A::View;
|
||||
type Match = A::Match;
|
||||
|
||||
fn match_nested<'a>(
|
||||
@@ -124,6 +132,7 @@ where
|
||||
B: MatchInterface,
|
||||
{
|
||||
type Child = Either<A::Child, B::Child>;
|
||||
type View = Either<A::View, B::View>;
|
||||
|
||||
fn as_id(&self) -> RouteMatchId {
|
||||
match self {
|
||||
@@ -139,7 +148,9 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
fn into_view_and_child(self) -> (impl ChooseView, Option<Self::Child>) {
|
||||
fn into_view_and_child(
|
||||
self,
|
||||
) -> (impl ChooseView<Output = Self::View>, Option<Self::Child>) {
|
||||
match self {
|
||||
Either::Left(i) => {
|
||||
let (view, child) = i.into_view_and_child();
|
||||
@@ -159,6 +170,7 @@ where
|
||||
B: MatchNestedRoutes,
|
||||
{
|
||||
type Data = (A::Data, B::Data);
|
||||
type View = Either<A::View, B::View>;
|
||||
type Match = Either<A::Match, B::Match>;
|
||||
|
||||
fn match_nested<'a>(
|
||||
@@ -224,6 +236,7 @@ macro_rules! tuples {
|
||||
$($ty: MatchInterface + 'static),*,
|
||||
{
|
||||
type Child = $either<$($ty::Child,)*>;
|
||||
type View = $either<$($ty::View,)*>;
|
||||
|
||||
fn as_id(&self) -> RouteMatchId {
|
||||
match self {
|
||||
@@ -240,7 +253,7 @@ macro_rules! tuples {
|
||||
fn into_view_and_child(
|
||||
self,
|
||||
) -> (
|
||||
impl ChooseView,
|
||||
impl ChooseView<Output = Self::View>,
|
||||
Option<Self::Child>,
|
||||
) {
|
||||
match self {
|
||||
@@ -257,6 +270,7 @@ macro_rules! tuples {
|
||||
$($ty: MatchNestedRoutes + 'static),*,
|
||||
{
|
||||
type Data = ($($ty::Data,)*);
|
||||
type View = $either<$($ty::View,)*>;
|
||||
type Match = $either<$($ty::Match,)*>;
|
||||
|
||||
fn match_nested<'a>(&'a self, path: &'a str) -> (Option<(RouteMatchId, Self::Match)>, &'a str) {
|
||||
|
||||
@@ -3,7 +3,6 @@ use crate::{
|
||||
location::{LocationProvider, Url},
|
||||
matching::Routes,
|
||||
params::ParamsMap,
|
||||
view_transition::start_view_transition,
|
||||
ChooseView, MatchInterface, MatchNestedRoutes, MatchParams, PathSegment,
|
||||
RouteList, RouteListing, RouteMatchId,
|
||||
};
|
||||
@@ -50,7 +49,6 @@ pub(crate) struct NestedRoutesView<Loc, Defs, FalFn> {
|
||||
pub base: Option<Oco<'static, str>>,
|
||||
pub fallback: FalFn,
|
||||
pub set_is_routing: Option<SignalSetter<bool>>,
|
||||
pub transition: bool,
|
||||
}
|
||||
|
||||
pub struct NestedRouteViewState<Fal>
|
||||
@@ -163,7 +161,7 @@ where
|
||||
|
||||
let mut preloaders = Vec::new();
|
||||
let mut full_loaders = Vec::new();
|
||||
let different_level = route.rebuild_nested_route(
|
||||
route.rebuild_nested_route(
|
||||
&self.current_url.read_untracked(),
|
||||
self.base,
|
||||
&mut 0,
|
||||
@@ -172,26 +170,14 @@ where
|
||||
&mut state.outlets,
|
||||
&self.outer_owner,
|
||||
self.set_is_routing.is_some(),
|
||||
0,
|
||||
);
|
||||
|
||||
let location = self.location.clone();
|
||||
let is_back = location
|
||||
.as_ref()
|
||||
.map(|nav| nav.is_back().get_untracked())
|
||||
.unwrap_or(false);
|
||||
Executor::spawn_local(async move {
|
||||
let triggers = join_all(preloaders).await;
|
||||
// tell each one of the outlet triggers that it's ready
|
||||
let notify = move || {
|
||||
for trigger in triggers {
|
||||
trigger.notify();
|
||||
}
|
||||
};
|
||||
if self.transition {
|
||||
start_view_transition(different_level, is_back, notify);
|
||||
} else {
|
||||
notify();
|
||||
for trigger in triggers {
|
||||
trigger.notify();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -523,8 +509,7 @@ trait AddNestedRoute {
|
||||
outlets: &mut Vec<RouteContext>,
|
||||
parent: &Owner,
|
||||
set_is_routing: bool,
|
||||
level: u8,
|
||||
) -> u8;
|
||||
);
|
||||
}
|
||||
|
||||
impl<Match> AddNestedRoute for Match
|
||||
@@ -670,8 +655,7 @@ where
|
||||
outlets: &mut Vec<RouteContext>,
|
||||
parent: &Owner,
|
||||
set_is_routing: bool,
|
||||
level: u8,
|
||||
) -> u8 {
|
||||
) {
|
||||
let (parent_params, parent_matches): (Vec<_>, Vec<_>) = outlets
|
||||
.iter()
|
||||
.take(*items)
|
||||
@@ -682,7 +666,6 @@ where
|
||||
// if there's nothing currently in the routes at this point, build from here
|
||||
None => {
|
||||
self.build_nested_route(url, base, preloaders, outlets, parent);
|
||||
level
|
||||
}
|
||||
Some(current) => {
|
||||
// a unique ID for each route, which allows us to compare when we get new matches
|
||||
@@ -819,7 +802,7 @@ where
|
||||
);
|
||||
}
|
||||
|
||||
return level;
|
||||
return;
|
||||
}
|
||||
|
||||
// otherwise, set the params and URL signals,
|
||||
@@ -839,10 +822,7 @@ where
|
||||
outlets,
|
||||
&owner,
|
||||
set_is_routing,
|
||||
level + 1,
|
||||
)
|
||||
} else {
|
||||
level
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -353,7 +353,7 @@ impl ResolvedStaticPath {
|
||||
eprintln!("{e}");
|
||||
}
|
||||
}
|
||||
owner.unset();
|
||||
drop(owner);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -17,7 +17,6 @@ either_of = { workspace = true }
|
||||
next_tuple = { workspace = true }
|
||||
or_poisoned = { workspace = true }
|
||||
reactive_graph = { workspace = true, optional = true }
|
||||
reactive_stores = { workspace = true, optional = true }
|
||||
slotmap = { version = "1.0", optional = true }
|
||||
oco_ref = { workspace = true, optional = true }
|
||||
once_cell = "1.19"
|
||||
@@ -176,10 +175,9 @@ oco = ["dep:oco_ref"]
|
||||
nightly = ["reactive_graph/nightly"]
|
||||
testing = ["dep:slotmap"]
|
||||
reactive_graph = ["dep:reactive_graph", "dep:any_spawner"]
|
||||
reactive_stores = ["reactive_graph", "dep:reactive_stores"]
|
||||
sledgehammer = ["dep:sledgehammer_bindgen", "dep:sledgehammer_utils"]
|
||||
tracing = ["dep:tracing"]
|
||||
|
||||
[package.metadata.cargo-all-features]
|
||||
denylist = ["tracing", "sledgehammer"]
|
||||
skip_feature_sets = [["ssr", "hydrate"], ["hydrate", "islands"], ["ssr", "delegation"]]
|
||||
skip_feature_sets = [["ssr", "hydrate"]]
|
||||
|
||||
@@ -14,8 +14,6 @@ use reactive_graph::{
|
||||
traits::{Get, Update},
|
||||
wrappers::read::Signal,
|
||||
};
|
||||
#[cfg(feature = "reactive_stores")]
|
||||
use reactive_stores::{KeyedSubfield, Subfield};
|
||||
use send_wrapper::SendWrapper;
|
||||
use wasm_bindgen::JsValue;
|
||||
|
||||
@@ -344,35 +342,6 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "reactive_stores")]
|
||||
impl<Inner, Prev, T> IntoSplitSignal for Subfield<Inner, Prev, T>
|
||||
where
|
||||
Self: Get<Value = T> + Update<Value = T> + Clone,
|
||||
{
|
||||
type Value = T;
|
||||
type Read = Self;
|
||||
type Write = Self;
|
||||
|
||||
fn into_split_signal(self) -> (Self::Read, Self::Write) {
|
||||
(self.clone(), self.clone())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "reactive_stores")]
|
||||
impl<Inner, Prev, K, T> IntoSplitSignal for KeyedSubfield<Inner, Prev, K, T>
|
||||
where
|
||||
Self: Get<Value = T> + Update<Value = T> + Clone,
|
||||
for<'a> &'a T: IntoIterator,
|
||||
{
|
||||
type Value = T;
|
||||
type Read = Self;
|
||||
type Write = Self;
|
||||
|
||||
fn into_split_signal(self) -> (Self::Read, Self::Write) {
|
||||
(self.clone(), self.clone())
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns self from an event target.
|
||||
pub trait FromEventTarget {
|
||||
/// Returns self from an event target.
|
||||
|
||||
Reference in New Issue
Block a user