Compare commits

..

38 Commits

Author SHA1 Message Date
Greg Johnston
c336eb8769 0.1.1 2023-01-20 13:24:05 -05:00
Greg Johnston
0f5f0de410 Merge pull request #346 from leptos-rs/suspense-comments
Change `<Suspense/>` to a specialized type that uses comments for SSR
2023-01-20 13:18:41 -05:00
Greg Johnston
a385f502b6 Merge pull request #345 from leptos-rs/provide-route-contexts-when-equal
Fix behavior of `RouteContext` in nested routes with different parameters
2023-01-20 12:13:43 -05:00
Greg Johnston
603eead12d cargo fmt 2023-01-20 12:06:04 -05:00
Greg Johnston
8a73f3b879 Use comment nodes for <Suspense/> to avoid both hydration and styling issues 2023-01-20 12:05:21 -05:00
Greg Johnston
5fc8907b85 Remove extraneous log 2023-01-20 10:37:14 -05:00
Greg Johnston
678990194f Update RouteContext.path() value when params change but route has not change (closes #340) 2023-01-20 10:36:48 -05:00
Greg Johnston
b54a60213b Merge pull request #333 from leptos-rs/minimize-runtime-panics
Minimize panics when runtime has already been disposed (e.g., in SSR)
2023-01-17 22:48:42 -05:00
Greg Johnston
ebeb1d69d1 Merge pull request #334 from leptos-rs/debug-meta
Fix `MetaContext` debug for wasm target
2023-01-17 14:23:41 -05:00
Greg Johnston
cadb04b076 Fix MetaContext debug for wasm target 2023-01-17 14:23:13 -05:00
Greg Johnston
490b7a1596 Merge pull request #332 from leptos-rs/programmatic-navigation-in-router-example
Add programmatic navigation in router example
2023-01-17 13:54:58 -05:00
Greg Johnston
f4d781e739 Merge pull request #331 from benwis/main
Path and Query for Axum
2023-01-17 13:54:49 -05:00
Greg Johnston
ebe5bf4600 Merge pull request #330 from martinfrances107/typed_builder
typed-builder inconsistent version.
2023-01-17 13:53:58 -05:00
Greg Johnston
d62046dc6f Merge pull request #329 from leptos-rs/meta-context-debug
impl `Debug` on `MetaContext`
2023-01-17 13:53:24 -05:00
Greg Johnston
c7abb57168 Merge pull request #328 from martinfrances107/crate_io_readme_issue
Minor: For each sub crate the landing page should be the root README.md.
2023-01-17 13:53:14 -05:00
Greg Johnston
bc0cd5d0ba Minimize panics when runtime has already been disposed (e.g., in streaming SSR) 2023-01-17 13:11:35 -05:00
Greg Johnston
7df67444f9 cargo fmt fix 2023-01-17 12:45:59 -05:00
Greg Johnston
40155e91ea cargo fmt fix 2023-01-17 12:43:27 -05:00
Greg Johnston
5c062fa6f1 Add use_navigate in router example 2023-01-17 12:40:54 -05:00
Greg Johnston
3517820afd Restore missing docs on <A/> component 2023-01-17 12:40:23 -05:00
benwis
300cc4f54c Actually Do It 2023-01-17 09:27:09 -08:00
Martin
586e9be99a Minor - type-builder version is inconsistent. 2023-01-17 17:23:05 +00:00
Greg Johnston
6ed86d0ee9 impl Debug on MetaContext 2023-01-17 12:17:16 -05:00
Martin
1fe93fd588 Minor: For each sub crate the landing page should be the root README.md. 2023-01-17 17:05:09 +00:00
Greg Johnston
2723871a80 Merge pull request #327 from ekanna/main
Updated example code and README to use latest syntax for data binding
2023-01-17 11:56:59 -05:00
benwis
70d92c7f42 Path and Query 2023-01-17 05:52:38 -08:00
Greg Johnston
e96d4b0687 Merge pull request #326 from benwis/main
Make sure Axum returns a relative URI for http and https requests
2023-01-17 06:34:04 -05:00
ekanna
ce0910caca Updated example code and README to use latest syntax for data binding 2023-01-17 12:08:44 +05:30
benwis
81a937277d Simplify URI matching solution 2023-01-16 22:35:22 -08:00
benwis
355e711964 Fix issue with https pathing for Axum integration 2023-01-16 22:18:39 -08:00
Greg Johnston
27ec506fd5 Merge pull request #321 from leptos-rs/for-ssr 2023-01-16 21:49:24 -05:00
Greg Johnston
79c76ae4cb Merge pull request #324 from leptos-rs/fix-fallback
Fix `<Router fallback>` (signature and functionality)
2023-01-16 20:51:01 -05:00
Greg Johnston
e416815591 clippy warning 2023-01-16 20:08:27 -05:00
Greg Johnston
81bdd6788f Fix hydration in release mode if _0-0-0 is a marker, not an element 2023-01-16 20:08:07 -05:00
Greg Johnston
ae0a243cc0 Fix meta doctests 2023-01-16 12:08:13 -05:00
Greg Johnston
7893ff8b55 Fix SSR doctests 2023-01-16 10:36:18 -05:00
Greg Johnston
6130e708ce cargo fmt 2023-01-16 09:26:40 -05:00
Greg Johnston
d049d2f36b Use comments instead of element markers for hydration -- fixes issue #320 2023-01-16 09:21:28 -05:00
31 changed files with 251 additions and 191 deletions

View File

@@ -24,17 +24,17 @@ members = [
exclude = ["benchmarks", "examples"]
[workspace.package]
version = "0.1.0"
version = "0.1.1"
[workspace.dependencies]
leptos = { path = "./leptos", default-features = false, version = "0.1.0" }
leptos_dom = { path = "./leptos_dom", default-features = false, version = "0.1.0" }
leptos_macro = { path = "./leptos_macro", default-features = false, version = "0.1.0" }
leptos_reactive = { path = "./leptos_reactive", default-features = false, version = "0.1.0" }
leptos_server = { path = "./leptos_server", default-features = false, version = "0.1.0" }
leptos_config = { path = "./leptos_config", default-features = false, version = "0.1.0" }
leptos_router = { path = "./router", version = "0.1.0" }
leptos_meta = { path = "./meta", default-feature = false, version = "0.1.0" }
leptos = { path = "./leptos", default-features = false, version = "0.1.1" }
leptos_dom = { path = "./leptos_dom", default-features = false, version = "0.1.1" }
leptos_macro = { path = "./leptos_macro", default-features = false, version = "0.1.1" }
leptos_reactive = { path = "./leptos_reactive", default-features = false, version = "0.1.1" }
leptos_server = { path = "./leptos_server", default-features = false, version = "0.1.1" }
leptos_config = { path = "./leptos_config", default-features = false, version = "0.1.1" }
leptos_router = { path = "./router", version = "0.1.1" }
leptos_meta = { path = "./meta", default-feature = false, version = "0.1.1" }
[profile.release]
codegen-units = 1

View File

@@ -29,7 +29,7 @@ pub fn SimpleCounter(cx: Scope, initial_value: i32) -> impl IntoView {
<div>
<button on:click=clear>"Clear"</button>
<button on:click=decrement>"-1"</button>
<span>"Value: " {move || value().to_string()} "!"</span>
<span>"Value: " {value} "!"</span>
<button on:click=increment>"+1"</button>
</div>
}

View File

@@ -17,7 +17,7 @@ pub fn SimpleCounter(
<div>
<button on:click=move |_| set_value(0)>"Clear"</button>
<button on:click=move |_| set_value.update(|value| *value -= step)>"-1"</button>
<span>"Value: " {move || value().to_string()} "!"</span>
<span>"Value: " {value} "!"</span>
<button on:click=move |_| set_value.update(|value| *value += step)>"+1"</button>
</div>
}

View File

@@ -116,7 +116,7 @@ pub fn Counter(cx: Scope) -> impl IntoView {
<div>
<button on:click=move |_| clear.dispatch(())>"Clear"</button>
<button on:click=move |_| dec.dispatch(())>"-1"</button>
<span>"Value: " {move || value().to_string()} "!"</span>
<span>"Value: " {value} "!"</span>
<button on:click=move |_| inc.dispatch(())>"+1"</button>
</div>
{move || error_msg().map(|msg| view! { cx, <p>"Error: " {msg.to_string()}</p>})}

View File

@@ -97,10 +97,10 @@ fn Counter(
<li>
<button on:click=move |_| set_value.update(move |value| *value -= 1)>"-1"</button>
<input type="text"
prop:value={move || value().to_string()}
prop:value={value}
on:input=input
/>
<span>{move || value().to_string()}</span>
<span>{value}</span>
<button on:click=move |_| set_value.update(move |value| *value += 1)>"+1"</button>
<button on:click=move |_| set_counters.update(move |counters| counters.retain(|(counter_id, _)| counter_id != &id))>"x"</button>
</li>

View File

@@ -12,6 +12,11 @@ pub fn RouterExample(cx: Scope) -> impl IntoView {
view! { cx,
<Router>
<nav>
// ordinary <a> elements can be used for client-side navigation
// using <A> has two effects:
// 1) ensuring that relative routing works properly for nested routes
// 2) setting the `aria-current` attribute on the current link,
// for a11y and styling purposes
<A exact=true href="/">"Contacts"</A>
<A href="about">"About"</A>
<A href="settings">"Settings"</A>
@@ -105,12 +110,15 @@ pub fn Contact(cx: Scope) -> impl IntoView {
// Some(None) => has loaded and found no contact
Some(None) => Some(view! { cx, <p>"No contact with this ID was found."</p> }.into_any()),
// Some(Some) => has loaded and found a contact
Some(Some(contact)) => Some(view! { cx,
<section class="card">
<h1>{contact.first_name} " " {contact.last_name}</h1>
<p>{contact.address_1}<br/>{contact.address_2}</p>
</section>
}.into_any()),
Some(Some(contact)) => Some(
view! { cx,
<section class="card">
<h1>{contact.first_name} " " {contact.last_name}</h1>
<p>{contact.address_1}<br/>{contact.address_2}</p>
</section>
}
.into_any(),
),
};
view! { cx,
@@ -125,9 +133,18 @@ pub fn Contact(cx: Scope) -> impl IntoView {
#[component]
pub fn About(cx: Scope) -> impl IntoView {
log::debug!("rendering <About/>");
// use_navigate allows you to navigate programmatically by calling a function
let navigate = use_navigate(cx);
view! { cx,
<>
// note: this is just an illustration of how to use `use_navigate`
// <button on:click> to navigate is an *anti-pattern*
// you should ordinarily use a link instead,
// both semantically and so your link will work before WASM loads
<button on:click=move |_| { _ = navigate("/", Default::default()); }>
"Home"
</button>
<h1>"About"</h1>
<p>"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."</p>
</>

View File

@@ -259,22 +259,20 @@ where
let options = options.clone();
let app_fn = app_fn.clone();
let res_options = ResponseOptions::default();
let status = RouterStatusContext::default();
async move {
let app = {
let app_fn = app_fn.clone();
let res_options = res_options.clone();
let status = status.clone();
move |cx| {
provide_contexts(cx, &req, res_options, status);
provide_contexts(cx, &req, res_options);
(app_fn)(cx).into_view(cx)
}
};
let (head, tail) = html_parts(&options);
stream_app(app, head, tail, res_options, status).await
stream_app(app, head, tail, res_options).await
}
})
}
@@ -338,7 +336,6 @@ where
let app_fn = app_fn.clone();
let data_fn = data_fn.clone();
let res_options = ResponseOptions::default();
let status = RouterStatusContext::default();
async move {
let data = match data_fn(req.clone()).await {
@@ -350,31 +347,24 @@ where
let app = {
let app_fn = app_fn.clone();
let res_options = res_options.clone();
let status = status.clone();
move |cx| {
provide_contexts(cx, &req, res_options, status);
provide_contexts(cx, &req, res_options);
(app_fn)(cx, data).into_view(cx)
}
};
let (head, tail) = html_parts(&options);
stream_app(app, head, tail, res_options, status).await
stream_app(app, head, tail, res_options).await
}
})
}
fn provide_contexts(
cx: leptos::Scope,
req: &HttpRequest,
res_options: ResponseOptions,
status: RouterStatusContext,
) {
fn provide_contexts(cx: leptos::Scope, req: &HttpRequest, res_options: ResponseOptions) {
let path = leptos_corrected_path(req);
let integration = ServerIntegration { path };
provide_context(cx, RouterIntegrationContext::new(integration));
provide_context(cx, status);
provide_context(cx, MetaContext::new());
provide_context(cx, res_options);
provide_context(cx, req.clone());
@@ -395,7 +385,6 @@ async fn stream_app(
head: String,
tail: String,
res_options: ResponseOptions,
router_status: RouterStatusContext,
) -> HttpResponse<BoxBody> {
let (stream, runtime, _) = render_to_stream_with_prefix_undisposed(app, move |cx| {
let head = use_context::<MetaContext>(cx)
@@ -422,15 +411,7 @@ async fn stream_app(
let res_options = res_options.0.read().await;
let (status, mut headers) = (res_options.status, res_options.headers.clone());
let status = status.unwrap_or_else(|| {
router_status
.status
.read()
.ok()
.and_then(|s| s.map(|s| StatusCode::from_u16(s).ok()))
.flatten()
.unwrap_or_default()
});
let status = status.unwrap_or_default();
let complete_stream = futures::stream::iter([
first_chunk.unwrap(),

View File

@@ -318,12 +318,12 @@ where
let default_res_options = ResponseOptions::default();
let res_options2 = default_res_options.clone();
let res_options3 = default_res_options.clone();
let router_status = RouterStatusContext::default();
let router_status2 = router_status.clone();
async move {
// Need to get the path and query string of the Request
let path = req.uri();
// For reasons that escape me, if the incoming URI protocol is https, it provides the absolute URI
// if http, it returns a relative path. Adding .path() seems to make it explicitly return the relative uri
let path = req.uri().path_and_query().unwrap().as_str();
let full_path = format!("http://leptos.dev{path}");
@@ -405,7 +405,6 @@ where
cx,
RouterIntegrationContext::new(integration),
);
provide_context(cx, router_status2);
provide_context(cx, MetaContext::new());
provide_context(cx, req_parts);
provide_context(cx, default_res_options);
@@ -474,16 +473,9 @@ where
Box::pin(complete_stream) as PinnedHtmlStream
));
let status = res_options.status.unwrap_or_else(|| {
router_status
.status
.read()
.ok()
.and_then(|s| s.map(|s| StatusCode::from_u16(s).ok()))
.flatten()
.unwrap_or_default()
});
*res.status_mut() = status;
if let Some(status) = res_options.status {
*res.status_mut() = status
}
let mut res_headers = res_options.headers.clone();
res.headers_mut().extend(res_headers.drain());

View File

@@ -1,5 +1,4 @@
use cfg_if::cfg_if;
#[cfg(not(any(feature = "csr", feature = "hydrate")))]
use leptos_dom::HydrationCtx;
use leptos_dom::{DynChild, Fragment, IntoView};
use leptos_macro::component;
@@ -73,11 +72,12 @@ where
let orig_child = Rc::new(children);
leptos_dom::custom(cx, leptos_dom::Custom::new("leptos-suspense")).child({
#[cfg(not(any(feature = "csr", feature = "hydrate")))]
let current_id = HydrationCtx::peek();
let current_id = HydrationCtx::peek();
DynChild::new(move || {
let child = DynChild::new({
#[cfg(not(any(feature = "csr", feature = "hydrate")))]
let current_id = current_id.clone();
move || {
cfg_if! {
if #[cfg(any(feature = "csr", feature = "hydrate"))] {
if context.ready() {
@@ -124,6 +124,13 @@ where
initial
}
}
})
}
})
.into_view(cx);
let core_component = match child {
leptos_dom::View::CoreComponent(repr) => repr,
_ => unreachable!(),
};
leptos_dom::View::Suspense(current_id.clone(), core_component)
}

View File

@@ -16,7 +16,7 @@ fn simple_ssr_test() {
assert_eq!(
rendered.into_view(cx).render_to_string(cx),
"<div id=\"_0-1\"><button id=\"_0-2\">-1</button><span id=\"_0-3\">Value: <leptos-dyn-child-start leptos id=\"_0-4o\"></leptos-dyn-child-start>0<leptos-dyn-child-end leptos id=\"_0-4c\"></leptos-dyn-child-end>!</span><button id=\"_0-5\">+1</button></div>"
"<div id=\"_0-1\"><button id=\"_0-2\">-1</button><span id=\"_0-3\">Value: <!--hk=_0-4o|leptos-dyn-child-start-->0<!--hk=_0-4c|leptos-dyn-child-end-->!</span><button id=\"_0-5\">+1</button></div>"
);
});
}
@@ -50,7 +50,7 @@ fn ssr_test_with_components() {
assert_eq!(
rendered.into_view(cx).render_to_string(cx),
"<div class=\"counters\" id=\"_0-1\"><leptos-counter-start leptos id=\"_0-1-0o\"></leptos-counter-start><div id=\"_0-1-1\"><button id=\"_0-1-2\">-1</button><span id=\"_0-1-3\">Value: <leptos-dyn-child-start leptos id=\"_0-1-4o\"></leptos-dyn-child-start>1<leptos-dyn-child-end leptos id=\"_0-1-4c\"></leptos-dyn-child-end>!</span><button id=\"_0-1-5\">+1</button></div><leptos-counter-end leptos id=\"_0-1-0c\"></leptos-counter-end><leptos-counter-start leptos id=\"_0-1-5-0o\"></leptos-counter-start><div id=\"_0-1-5-1\"><button id=\"_0-1-5-2\">-1</button><span id=\"_0-1-5-3\">Value: <leptos-dyn-child-start leptos id=\"_0-1-5-4o\"></leptos-dyn-child-start>2<leptos-dyn-child-end leptos id=\"_0-1-5-4c\"></leptos-dyn-child-end>!</span><button id=\"_0-1-5-5\">+1</button></div><leptos-counter-end leptos id=\"_0-1-5-0c\"></leptos-counter-end></div>"
"<div class=\"counters\" id=\"_0-1\"><!--hk=_0-1-0o|leptos-counter-start--><div id=\"_0-1-1\"><button id=\"_0-1-2\">-1</button><span id=\"_0-1-3\">Value: <!--hk=_0-1-4o|leptos-dyn-child-start-->1<!--hk=_0-1-4c|leptos-dyn-child-end-->!</span><button id=\"_0-1-5\">+1</button></div><!--hk=_0-1-0c|leptos-counter-end--><!--hk=_0-1-5-0o|leptos-counter-start--><div id=\"_0-1-5-1\"><button id=\"_0-1-5-2\">-1</button><span id=\"_0-1-5-3\">Value: <!--hk=_0-1-5-4o|leptos-dyn-child-start-->2<!--hk=_0-1-5-4c|leptos-dyn-child-end-->!</span><button id=\"_0-1-5-5\">+1</button></div><!--hk=_0-1-5-0c|leptos-counter-end--></div>"
);
});
}

View File

@@ -6,6 +6,7 @@ authors = ["Greg Johnston"]
license = "MIT"
repository = "https://github.com/leptos-rs/leptos"
description = "Configuraiton for the Leptos web framework."
readme = "../README.md"
[dependencies]
config = "0.13.3"
@@ -13,4 +14,4 @@ fs = "0.0.5"
regex = "1.7.0"
serde = { version = "1.0.151", features = ["derive"] }
thiserror = "1.0.38"
typed-builder = "0.11.0"
typed-builder = "0.11"

View File

@@ -39,6 +39,7 @@ features = [
"Range",
"Text",
"HtmlCollection",
"TreeWalker",
# Events we cast to in leptos_macro -- added here so we don't force users to import them
"AnimationEvent",

View File

@@ -1,21 +1,51 @@
use cfg_if::cfg_if;
use std::{cell::RefCell, fmt::Display};
#[cfg(all(target_arch = "wasm32", feature = "web"))]
use once_cell::unsync::Lazy as LazyCell;
cfg_if! {
if #[cfg(all(target_arch = "wasm32", feature = "web"))] {
use once_cell::unsync::Lazy as LazyCell;
use std::collections::HashMap;
use wasm_bindgen::JsCast;
// We can tell if we start in hydration mode by checking to see if the
// id "_0-0-0" is present in the DOM. If it is, we know we are hydrating from
// the server, if not, we are starting off in CSR
#[cfg(all(target_arch = "wasm32", feature = "web"))]
thread_local! {
static IS_HYDRATING: RefCell<LazyCell<bool>> = RefCell::new(LazyCell::new(|| {
#[cfg(debug_assertions)]
return crate::document().get_element_by_id("_0-0-0").is_some()
|| crate::document().get_element_by_id("_0-0-0o").is_some();
// We can tell if we start in hydration mode by checking to see if the
// id "_0-0-0" is present in the DOM. If it is, we know we are hydrating from
// the server, if not, we are starting off in CSR
thread_local! {
static HYDRATION_COMMENTS: LazyCell<HashMap<String, web_sys::Comment>> = LazyCell::new(|| {
let document = crate::document();
let body = document.body().unwrap();
let walker = document
.create_tree_walker_with_what_to_show(&body, 128)
.unwrap();
let mut map = HashMap::new();
while let Ok(Some(node)) = walker.next_node() {
if let Some(content) = node.text_content() {
if let Some(hk) = content.strip_prefix("hk=") {
if let Some(hk) = hk.split("|").next() {
map.insert(hk.into(), node.unchecked_into());
}
}
}
}
map
});
#[cfg(not(debug_assertions))]
return crate::document().get_element_by_id("_0-0-0").is_some();
}));
static IS_HYDRATING: RefCell<LazyCell<bool>> = RefCell::new(LazyCell::new(|| {
#[cfg(debug_assertions)]
return crate::document().get_element_by_id("_0-0-0").is_some()
|| crate::document().get_element_by_id("_0-0-0o").is_some()
|| HYDRATION_COMMENTS.with(|comments| comments.get("_0-0-0o").is_some());
#[cfg(not(debug_assertions))]
return crate::document().get_element_by_id("_0-0-0").is_some()
|| HYDRATION_COMMENTS.with(|comments| comments.get("_0-0-0").is_some());
}));
}
pub(crate) fn get_marker(id: &str) -> Option<web_sys::Comment> {
HYDRATION_COMMENTS.with(|comments| comments.get(id).cloned())
}
}
}
/// A stable identifer within the server-rendering or hydration process.

View File

@@ -303,7 +303,7 @@ impl Comment {
if HydrationCtx::is_hydrating() {
let id = HydrationCtx::to_string(id, closing);
if let Some(marker) = document().get_element_by_id(&id) {
if let Some(marker) = hydration::get_marker(&id) {
marker.before_with_node_1(&node).unwrap();
marker.remove();
@@ -377,6 +377,8 @@ pub enum View {
/// Wraps arbitrary data that's not part of the view but is
/// passed via the view tree.
Transparent(Transparent),
/// Marks the contents of Suspense component, which can be replaced in streaming SSR.
Suspense(HydrationKey, CoreComponent),
}
impl fmt::Debug for View {
@@ -389,6 +391,9 @@ impl fmt::Debug for View {
Self::Transparent(arg0) => {
f.debug_tuple("Transparent").field(arg0).finish()
}
Self::Suspense(id, c) => {
f.debug_tuple("Suspense").field(id).field(c).finish()
}
}
}
}
@@ -431,7 +436,7 @@ impl Mountable for View {
element.element.unchecked_ref::<web_sys::Node>().clone()
}
Self::Text(t) => t.node.clone(),
Self::CoreComponent(c) => match c {
Self::CoreComponent(c) | Self::Suspense(_, c) => match c {
CoreComponent::Unit(u) => u.get_mountable_node(),
CoreComponent::DynChild(dc) => dc.get_mountable_node(),
CoreComponent::Each(e) => e.get_mountable_node(),
@@ -445,7 +450,7 @@ impl Mountable for View {
match self {
Self::Text(t) => t.node.clone(),
Self::Element(el) => el.element.clone().unchecked_into(),
Self::CoreComponent(c) => match c {
Self::CoreComponent(c) | Self::Suspense(_, c) => match c {
CoreComponent::DynChild(dc) => dc.get_opening_node(),
CoreComponent::Each(e) => e.get_opening_node(),
CoreComponent::Unit(u) => u.get_opening_node(),
@@ -461,7 +466,7 @@ impl Mountable for View {
match self {
Self::Text(t) => t.node.clone(),
Self::Element(el) => el.element.clone().unchecked_into(),
Self::CoreComponent(c) => match c {
Self::CoreComponent(c) | Self::Suspense(_, c) => match c {
CoreComponent::DynChild(dc) => dc.get_closing_node(),
CoreComponent::Each(e) => e.get_closing_node(),
CoreComponent::Unit(u) => u.get_closing_node(),
@@ -487,6 +492,7 @@ impl View {
CoreComponent::Unit(..) => "Unit",
},
Self::Transparent(..) => "Transparent",
Self::Suspense(..) => "Suspense",
}
}
@@ -548,7 +554,7 @@ impl View {
pub fn on<E: ev::EventDescriptor + 'static>(
self,
event: E,
mut event_handler: impl FnMut(E::EventType) + 'static,
#[allow(unused_mut)] mut event_handler: impl FnMut(E::EventType) + 'static,
) -> Self {
cfg_if::cfg_if! {
if #[cfg(debug_assertions)] {

View File

@@ -16,7 +16,7 @@ use std::borrow::Cow;
/// <p>"Hello, world!"</p>
/// });
/// // static HTML includes some hydration info
/// assert_eq!(html, "<style>[leptos]{display:none;}</style><p id=\"_0-1\">Hello, world!</p>");
/// assert_eq!(html, "<p id=\"_0-1\">Hello, world!</p>");
/// # }}
/// ```
pub fn render_to_string<F, N>(f: F) -> String
@@ -33,13 +33,7 @@ where
runtime.dispose();
#[cfg(debug_assertions)]
{
format!("<style>[leptos]{{display:none;}}</style>{html}")
}
#[cfg(not(debug_assertions))]
format!("<style>l-m{{display:none;}}</style>{html}")
html.into()
}
/// Renders a function to a stream of HTML strings.
@@ -122,16 +116,6 @@ pub fn render_to_stream_with_prefix_undisposed(
let pending_resources = serde_json::to_string(&resources).unwrap();
let prefix = prefix(cx);
let shell = {
#[cfg(debug_assertions)]
{
format!("<style>[leptos]{{display:none;}}</style>{shell}")
}
#[cfg(not(debug_assertions))]
format!("<style>l-m{{display:none;}}</style>{shell}")
};
(
shell,
prefix,
@@ -155,10 +139,23 @@ pub fn render_to_stream_with_prefix_undisposed(
r#"
<template id="{fragment_id}f">{html}</template>
<script>
var placeholder = document.getElementById("_{fragment_id}");
var id = "{fragment_id}";
var open;
var close;
var walker = document.createTreeWalker(document.body, NodeFilter.SHOW_COMMENT);
while(walker.nextNode()) {{
if(walker.currentNode.textContent == `suspense-open-${{id}}`) {{
open = walker.currentNode;
}} else if(walker.currentNode.textContent == `suspense-close-${{id}}`) {{
close = walker.currentNode;
}}
}}
var range = new Range();
range.setStartAfter(open);
range.setEndBefore(close);
range.deleteContents();
var tpl = document.getElementById("{fragment_id}f");
placeholder.textContent = "";
placeholder.append(tpl.content.cloneNode(true));
close.parentNode.insertBefore(tpl.content.cloneNode(true), close);
</script>
"#
)
@@ -218,7 +215,7 @@ impl View {
};
cfg_if! {
if #[cfg(debug_assertions)] {
format!(r#"<leptos-{name}-start leptos id="{}"></leptos-{name}-start>{}<leptos-{name}-end leptos id="{}"></leptos-{name}-end>"#,
format!(r#"<!--hk={}|leptos-{name}-start-->{}<!--hk={}|leptos-{name}-end-->"#,
HydrationCtx::to_string(&node.id, false),
content(),
HydrationCtx::to_string(&node.id, true),
@@ -226,13 +223,18 @@ impl View {
).into()
} else {
format!(
r#"{}<l-m id="{}"></l-m>"#,
r#"{}<!--hk={}-->"#,
content(),
HydrationCtx::to_string(&node.id, true)
).into()
}
}
}
View::Suspense(id, node) => format!(
"<!--suspense-open-{id}-->{}<!--suspense-close-{id}-->",
View::CoreComponent(node).render_to_string_helper()
)
.into(),
View::CoreComponent(node) => {
let (id, name, wrap, content) = match node {
CoreComponent::Unit(u) => (
@@ -243,14 +245,14 @@ impl View {
#[cfg(debug_assertions)]
{
format!(
"<leptos-unit leptos id={}></leptos-unit>",
"<!--hk={}|leptos-unit-->",
HydrationCtx::to_string(&u.id, true)
)
.into()
}
#[cfg(not(debug_assertions))]
format!("<l-m id={}></l-m>", HydrationCtx::to_string(&u.id, true))
format!("<!--hk={}-->", HydrationCtx::to_string(&u.id, true))
.into()
}) as Box<dyn FnOnce() -> Cow<'static, str>>,
),
@@ -286,7 +288,6 @@ impl View {
}
CoreComponent::Each(node) => {
let children = node.children.take();
(
node.id,
"each",
@@ -303,10 +304,8 @@ impl View {
#[cfg(debug_assertions)]
{
format!(
"<leptos-each-item-start leptos \
id=\"{}\"></\
leptos-each-item-start>{}<leptos-each-item-end \
leptos id=\"{}\"></leptos-each-item-end>",
"<!--hk={}|leptos-each-item-start-->{}<!\
--hk={}|leptos-each-item-end-->",
HydrationCtx::to_string(&id, false),
content(),
HydrationCtx::to_string(&id, true),
@@ -315,7 +314,7 @@ impl View {
#[cfg(not(debug_assertions))]
format!(
"{}<l-m id=\"{}\"></l-m>",
"{}<!--hk={}-->",
content(),
HydrationCtx::to_string(&id, true)
)
@@ -331,7 +330,7 @@ impl View {
cfg_if! {
if #[cfg(debug_assertions)] {
format!(
r#"<leptos-{name}-start leptos id="{}"></leptos-{name}-start>{}<leptos-{name}-end leptos id="{}"></leptos-{name}-end>"#,
r#"<!--hk={}|leptos-{name}-start-->{}<!--hk={}|leptos-{name}-end-->"#,
HydrationCtx::to_string(&id, false),
content(),
HydrationCtx::to_string(&id, true),
@@ -340,7 +339,7 @@ impl View {
let _ = name;
format!(
r#"{}<l-m id="{}"></l-m>"#,
r#"{}<!--hk={}-->"#,
content(),
HydrationCtx::to_string(&id, true)
).into()

View File

@@ -6,6 +6,7 @@ authors = ["Greg Johnston"]
license = "MIT"
repository = "https://github.com/leptos-rs/leptos"
description = "view macro for the Leptos web framework."
readme = "../README.md"
[lib]
proc-macro = true
@@ -29,7 +30,7 @@ lazy_static = "1.4"
[dev-dependencies]
log = "0.4"
typed-builder = "0.10"
typed-builder = "0.11"
leptos = { path = "../leptos" }
[features]

View File

@@ -56,7 +56,7 @@ where
{
let id = value.type_id();
with_runtime(cx.runtime, |runtime| {
_ = with_runtime(cx.runtime, |runtime| {
let mut contexts = runtime.scope_contexts.borrow_mut();
let context = contexts.entry(cx.id).unwrap().or_insert_with(HashMap::new);
context.insert(id, Box::new(value) as Box<dyn Any>);
@@ -136,4 +136,6 @@ where
}),
}
})
.ok()
.flatten()
}

View File

@@ -180,7 +180,7 @@ where
)
)]
fn run(&self, id: EffectId, runtime: RuntimeId) {
with_runtime(runtime, |runtime| {
_ = with_runtime(runtime, |runtime| {
// clear previous dependencies
id.cleanup(runtime);
@@ -201,7 +201,7 @@ where
impl EffectId {
pub(crate) fn run<T>(&self, runtime_id: RuntimeId) {
with_runtime(runtime_id, |runtime| {
_ = with_runtime(runtime_id, |runtime| {
let effect = {
let effects = runtime.effects.borrow();
effects.get(*self).cloned()

View File

@@ -131,7 +131,8 @@ where
let id = with_runtime(cx.runtime, |runtime| {
runtime.create_serializable_resource(Rc::clone(&r))
});
})
.expect("tried to create a Resource in a Runtime that has been disposed.");
create_isomorphic_effect(cx, {
let r = Rc::clone(&r);
@@ -250,7 +251,8 @@ where
let id = with_runtime(cx.runtime, |runtime| {
runtime.create_unserializable_resource(Rc::clone(&r))
});
})
.expect("tried to create a Resource in a runtime that has been disposed.");
create_effect(cx, {
let r = Rc::clone(&r);
@@ -288,7 +290,7 @@ where
{
use wasm_bindgen::{JsCast, UnwrapThrowExt};
with_runtime(cx.runtime, |runtime| {
_ = with_runtime(cx.runtime, |runtime| {
let mut context = runtime.shared_context.borrow_mut();
if let Some(data) = context.resolved_resources.remove(&id) {
// The server already sent us the serialized resource value, so
@@ -363,6 +365,8 @@ where
with_runtime(self.runtime, |runtime| {
runtime.resource(self.id, |resource: &ResourceState<S, T>| resource.read())
})
.ok()
.flatten()
}
/// Applies a function to the current value of the resource, and subscribes
@@ -376,6 +380,8 @@ where
with_runtime(self.runtime, |runtime| {
runtime.resource(self.id, |resource: &ResourceState<S, T>| resource.with(f))
})
.ok()
.flatten()
}
/// Returns a signal that indicates whether the resource is currently loading.
@@ -383,11 +389,12 @@ where
with_runtime(self.runtime, |runtime| {
runtime.resource(self.id, |resource: &ResourceState<S, T>| resource.loading)
})
.expect("tried to call Resource::loading() in a runtime that has already been disposed.")
}
/// Re-runs the async function with the current source data.
pub fn refetch(&self) {
with_runtime(self.runtime, |runtime| {
_ = with_runtime(self.runtime, |runtime| {
runtime.resource(self.id, |resource: &ResourceState<S, T>| resource.refetch())
});
}
@@ -404,6 +411,7 @@ where
resource.to_serialization_resolver(self.id)
})
})
.expect("tried to serialize a Resource in a runtime that has already been disposed")
.await
}
}

View File

@@ -34,19 +34,19 @@ cfg_if! {
/// Get the selected runtime from the thread-local set of runtimes. On the server,
/// this will return the correct runtime. In the browser, there should only be one runtime.
pub(crate) fn with_runtime<T>(id: RuntimeId, f: impl FnOnce(&Runtime) -> T) -> T {
pub(crate) fn with_runtime<T>(id: RuntimeId, f: impl FnOnce(&Runtime) -> T) -> Result<T, ()> {
// in the browser, everything should exist under one runtime
cfg_if! {
if #[cfg(any(feature = "csr", feature = "hydrate"))] {
_ = id;
RUNTIME.with(|runtime| f(runtime))
Ok(RUNTIME.with(|runtime| f(runtime)))
} else {
RUNTIMES.with(|runtimes| {
let runtimes = runtimes.borrow();
let runtime = runtimes
.get(id)
.expect("Tried to access a Runtime that no longer exists.");
f(runtime)
match runtimes.get(id) {
None => Err(()),
Some(runtime) => Ok(f(runtime))
}
})
}
}
@@ -88,6 +88,7 @@ impl RuntimeId {
let disposer = ScopeDisposer(Box::new(move || scope.dispose()));
(scope, disposer)
})
.expect("tried to create raw scope in a runtime that has already been disposed")
}
pub(crate) fn run_scope_undisposed<T>(
@@ -105,6 +106,7 @@ impl RuntimeId {
let disposer = ScopeDisposer(Box::new(move || scope.dispose()));
(val, id, disposer)
})
.expect("tried to run scope in a runtime that has been disposed")
}
pub(crate) fn run_scope<T>(self, f: impl FnOnce(Scope) -> T, parent: Option<Scope>) -> T {
@@ -123,7 +125,8 @@ impl RuntimeId {
.signals
.borrow_mut()
.insert(Rc::new(RefCell::new(value)))
});
})
.expect("tried to create a signal in a runtime that has been disposed");
(
ReadSignal {
runtime: self,
@@ -151,7 +154,8 @@ impl RuntimeId {
.signals
.borrow_mut()
.insert(Rc::new(RefCell::new(value)))
});
})
.expect("tried to create a signal in a runtime that has been disposed");
RwSignal {
runtime: self,
id,
@@ -180,6 +184,7 @@ impl RuntimeId {
id.run::<T>(self);
id
})
.expect("tried to create an effect in a runtime that has been disposed")
}
#[track_caller]

View File

@@ -107,7 +107,7 @@ impl Scope {
/// has navigated away from the route.)
pub fn run_child_scope<T>(self, f: impl FnOnce(Scope) -> T) -> (T, ScopeDisposer) {
let (res, child_id, disposer) = self.runtime.run_scope_undisposed(f, Some(self));
with_runtime(self.runtime, |runtime| {
_ = with_runtime(self.runtime, |runtime| {
let mut children = runtime.scope_children.borrow_mut();
children
.entry(self.id)
@@ -150,6 +150,7 @@ impl Scope {
runtime.observer.set(prev_observer);
untracked_result
})
.expect("tried to run untracked function in a runtime that has been disposed")
}
}
@@ -163,7 +164,7 @@ impl Scope {
/// 2. run all cleanup functions defined for this scope by [on_cleanup](crate::on_cleanup).
/// 3. dispose of all signals, effects, and resources owned by this `Scope`.
pub fn dispose(self) {
with_runtime(self.runtime, |runtime| {
_ = with_runtime(self.runtime, |runtime| {
// dispose of all child scopes
let children = {
let mut children = runtime.scope_children.borrow_mut();
@@ -225,7 +226,7 @@ impl Scope {
}
pub(crate) fn with_scope_property(&self, f: impl FnOnce(&mut Vec<ScopeProperty>)) {
with_runtime(self.runtime, |runtime| {
_ = with_runtime(self.runtime, |runtime| {
let scopes = runtime.scopes.borrow();
let scope = scopes
.get(self.id)
@@ -240,7 +241,7 @@ impl Scope {
/// It runs after child scopes have been disposed, but before signals, effects, and resources
/// are invalidated.
pub fn on_cleanup(cx: Scope, cleanup_fn: impl FnOnce() + 'static) {
with_runtime(cx.runtime, |runtime| {
_ = with_runtime(cx.runtime, |runtime| {
let mut cleanups = runtime.scope_cleanups.borrow_mut();
let cleanups = cleanups
.entry(cx.id)
@@ -286,18 +287,18 @@ impl ScopeDisposer {
impl Scope {
/// Returns IDs for all [Resource](crate::Resource)s found on any scope.
pub fn all_resources(&self) -> Vec<ResourceId> {
with_runtime(self.runtime, |runtime| runtime.all_resources())
with_runtime(self.runtime, |runtime| runtime.all_resources()).unwrap_or_default()
}
/// Returns IDs for all [Resource](crate::Resource)s found on any scope that are
/// pending from the server.
pub fn pending_resources(&self) -> Vec<ResourceId> {
with_runtime(self.runtime, |runtime| runtime.pending_resources())
with_runtime(self.runtime, |runtime| runtime.pending_resources()).unwrap_or_default()
}
/// Returns IDs for all [Resource](crate::Resource)s found on any scope.
pub fn serialization_resolvers(&self) -> FuturesUnordered<PinnedFuture<(ResourceId, String)>> {
with_runtime(self.runtime, |runtime| runtime.serialization_resolvers())
with_runtime(self.runtime, |runtime| runtime.serialization_resolvers()).unwrap_or_default()
}
/// Registers the given [SuspenseContext](crate::SuspenseContext) with the current scope,
@@ -312,7 +313,7 @@ impl Scope {
use crate::create_isomorphic_effect;
use futures::StreamExt;
with_runtime(self.runtime, |runtime| {
_ = with_runtime(self.runtime, |runtime| {
let mut shared_context = runtime.shared_context.borrow_mut();
let (tx, mut rx) = futures::channel::mpsc::unbounded();
@@ -344,6 +345,7 @@ impl Scope {
let mut shared_context = runtime.shared_context.borrow_mut();
std::mem::take(&mut shared_context.pending_fragments)
})
.unwrap_or_default()
}
}

View File

@@ -246,7 +246,7 @@ where
#[cfg(feature = "hydrate")]
pub(crate) fn subscribe(&self) {
with_runtime(self.runtime, |runtime| self.id.subscribe(runtime))
_ = with_runtime(self.runtime, |runtime| self.id.subscribe(runtime))
}
/// Clones and returns the current value of the signal, and subscribes
@@ -286,7 +286,11 @@ where
/// Applies the function to the current Signal, if it exists, and subscribes
/// the running effect.
pub(crate) fn try_with<U>(&self, f: impl FnOnce(&T) -> U) -> Result<U, SignalError> {
with_runtime(self.runtime, |runtime| self.id.try_with(runtime, f))
match with_runtime(self.runtime, |runtime| self.id.try_with(runtime, f)) {
Ok(Ok(v)) => Ok(v),
Ok(Err(e)) => Err(e),
Err(_) => Err(SignalError::RuntimeDisposed),
}
}
/// Generates a [Stream] that emits the new value of the signal whenever it changes.
@@ -1154,6 +1158,8 @@ slotmap::new_key_type! {
#[derive(Debug, Error)]
pub(crate) enum SignalError {
#[error("tried to access a signal in a runtime that had been disposed")]
RuntimeDisposed,
#[error("tried to access a signal that had been disposed")]
Disposed,
#[error("error casting signal to type {0}")]
@@ -1235,6 +1241,7 @@ impl SignalId {
with_runtime(runtime, |runtime| {
self.try_with_no_subscription(runtime, f).unwrap()
})
.expect("tried to access a signal in a runtime that has been disposed")
}
pub(crate) fn with<T, U>(&self, runtime: RuntimeId, f: impl FnOnce(&T) -> U) -> U
@@ -1242,6 +1249,7 @@ impl SignalId {
T: 'static,
{
with_runtime(runtime, |runtime| self.try_with(runtime, f).unwrap())
.expect("tried to access a signal in a runtime that has been disposed")
}
fn update_value<T, U>(&self, runtime: RuntimeId, f: impl FnOnce(&mut T) -> U) -> Option<U>
@@ -1272,6 +1280,7 @@ impl SignalId {
None
}
})
.unwrap_or_default()
}
pub(crate) fn update<T, U>(
@@ -1307,6 +1316,7 @@ impl SignalId {
};
updated
})
.unwrap_or_default()
}
pub(crate) fn update_with_no_effect<T, U>(

View File

@@ -6,6 +6,7 @@ authors = ["Greg Johnston"]
license = "MIT"
repository = "https://github.com/gbj/leptos"
description = "RPC for the Leptos web framework."
readme = "../README.md"
[dependencies]
leptos_dom = { workspace = true }

View File

@@ -1,6 +1,6 @@
[package]
name = "leptos_meta"
version = "0.1.0"
version = "0.1.1"
edition = "2021"
authors = ["Greg Johnston"]
license = "MIT"

View File

@@ -64,7 +64,7 @@ pub use title::*;
///
/// This should generally by provided somewhere in the root of your application using
/// [provide_meta_context].
#[derive(Clone, Default)]
#[derive(Clone, Default, Debug)]
pub struct MetaContext {
pub(crate) title: TitleContext,
pub(crate) tags: MetaTagsContext,
@@ -78,6 +78,12 @@ pub(crate) struct MetaTagsContext {
els: Rc<RefCell<HashMap<String, (HtmlElement<AnyElement>, Scope, Option<web_sys::Element>)>>>,
}
impl std::fmt::Debug for MetaTagsContext {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("MetaTagsContext").finish()
}
}
impl MetaTagsContext {
#[cfg(feature = "ssr")]
pub fn as_string(&self) -> String {
@@ -208,10 +214,10 @@ impl MetaContext {
/// // `app` contains only the body content w/ hydration stuff, not the meta tags
/// assert_eq!(
/// app.into_view(cx).render_to_string(cx),
/// "<main id=\"_0-1\"><leptos-unit leptos id=_0-2c></leptos-unit><leptos-unit leptos id=_0-4c></leptos-unit><p id=\"_0-5\">Some text</p></main>"
/// "<main id=\"_0-1\"><!--hk=_0-2c|leptos-unit--><!--hk=_0-4c|leptos-unit--><p id=\"_0-5\">Some text</p></main>"
/// );
/// // `MetaContext::dehydrate()` gives you HTML that should be in the `<head>`
/// assert_eq!(use_head(cx).dehydrate(), r#"<title>my title</title><link id="leptos-link-1" href="/style.css" rel="stylesheet" leptos-hk="_0-3"/>"#)
/// assert_eq!(use_head(cx).dehydrate(), "<title>my title</title><link id=\"leptos-link-1\" href=\"/style.css\" rel=\"stylesheet\" leptos-hk=\"_0-3\"/>")
/// });
/// # }
/// ```

View File

@@ -1,9 +1,10 @@
[package]
name = "leptos_router"
version = "0.1.0"
version = "0.1.1"
edition = "2021"
authors = ["Greg Johnston"]
license = "MIT"
README = "../README.md"
repository = "https://github.com/leptos-rs/leptos"
description = "Router for the Leptos web framework."

View File

@@ -36,6 +36,14 @@ where
/// An HTML [`a`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a)
/// progressively enhanced to use client-side routing.
///
/// Client-side routing also works with ordinary HTML `<a>` tags, but `<A>` does two additional things:
/// 1) Correctly resolves relative nested routes. Relative routing with ordinary `<a>` tags can be tricky.
/// For example, if you have a route like `/post/:id`, `<A href="1">` will generate the correct relative
/// route, but `<a href="1">` likely will not (depending on where it appears in your view.)
/// 2) Sets the `aria-current` attribute if this link is the active link (i.e., its a link to the page youre on).
/// This is helpful for accessibility and for styling. For example, maybe you want to set the link a
/// different color if its a link to the page youre currently on.
#[component]
pub fn A<H>(
cx: Scope,

View File

@@ -1,4 +1,7 @@
use std::{borrow::Cow, cell::Cell, rc::Rc};
use std::{
cell::{Cell, RefCell},
rc::Rc,
};
use leptos::*;
@@ -98,7 +101,7 @@ impl RouteContext {
id,
base_path: base.to_string(),
child: Box::new(child),
path,
path: RefCell::new(path),
original_path: route.original_path.to_string(),
params,
outlet: Box::new(move || Some(element(cx))),
@@ -120,8 +123,12 @@ impl RouteContext {
///
/// e.g., this will return `/article/0` rather than `/article/:id`.
/// For the opposite behavior, see [RouteContext::original_path].
pub fn path(&self) -> &str {
&self.inner.path
pub fn path(&self) -> String {
self.inner.path.borrow().to_string()
}
pub(crate) fn set_path(&mut self, path: String) {
*self.inner.path.borrow_mut() = path;
}
/// Returns the original URL path of the current route,
@@ -145,7 +152,7 @@ impl RouteContext {
id: 0,
base_path: path.to_string(),
child: Box::new(|| None),
path: path.to_string(),
path: RefCell::new(path.to_string()),
original_path: path.to_string(),
params: create_memo(cx, |_| ParamsMap::new()),
outlet: Box::new(move || fallback.as_ref().map(move |f| f(cx))),
@@ -154,8 +161,8 @@ impl RouteContext {
}
/// Resolves a relative route, relative to the current route's path.
pub fn resolve_path<'a>(&'a self, to: &'a str) -> Option<Cow<'a, str>> {
resolve_path(&self.inner.base_path, to, Some(&self.inner.path))
pub fn resolve_path(&self, to: &str) -> Option<String> {
resolve_path(&self.inner.base_path, to, Some(&self.inner.path.borrow())).map(String::from)
}
/// The nested child route, if any.
@@ -174,7 +181,7 @@ pub(crate) struct RouteContextInner {
base_path: String,
pub(crate) id: usize,
pub(crate) child: Box<dyn Fn() -> Option<RouteContext>>,
pub(crate) path: String,
pub(crate) path: RefCell<String>,
pub(crate) original_path: String,
pub(crate) params: Memo<ParamsMap>,
pub(crate) outlet: Box<dyn Fn() -> Option<View>>,

View File

@@ -1,9 +1,5 @@
use cfg_if::cfg_if;
use std::{
cell::RefCell,
rc::Rc,
sync::{Arc, RwLock},
};
use std::{cell::RefCell, rc::Rc};
use leptos::*;
use thiserror::Error;
@@ -65,14 +61,6 @@ pub(crate) struct RouterContextInner {
set_state: WriteSignal<State>,
}
/// Context type that indicates the status of the last request
/// (i.e., whether it was not found, or had an error.)
#[derive(Debug, Clone, Default)]
pub struct RouterStatusContext {
pub status: Arc<RwLock<Option<u16>>>,
pub message: Arc<RwLock<Option<String>>>,
}
impl std::fmt::Debug for RouterContextInner {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("RouterContextInner")
@@ -222,7 +210,7 @@ impl RouterContextInner {
let resolved_to = if options.resolve {
this.base.resolve_path(to)
} else {
resolve_path("", to, None)
resolve_path("", to, None).map(String::from)
};
match resolved_to {

View File

@@ -12,7 +12,7 @@ use crate::{
expand_optionals, get_route_matches, join_paths, Branch, Matcher, RouteDefinition,
RouteMatch,
},
RouteContext, RouterContext, RouterStatusContext,
RouteContext, RouterContext,
};
/// Contains route definitions and manages the actual routing process.
@@ -96,7 +96,10 @@ pub fn Routes(
if next_match.route.key == prev_match.route.key
&& next_match.route.id == prev_match.route.id =>
{
let prev_one = { prev.borrow()[i].clone() };
let mut prev_one = { prev.borrow()[i].clone() };
if next_match.path_match.path != prev_one.path() {
prev_one.set_path(next_match.path_match.path.clone());
}
if i >= next.borrow().len() {
next.borrow_mut().push(prev_one);
} else {
@@ -190,24 +193,12 @@ pub fn Routes(
});
// show the root route
let router_status = use_context::<RouterStatusContext>(cx);
let root = create_memo(cx, move |prev| {
provide_context(cx, route_states);
let router_status = router_status.clone();
route_states.with(|state| {
if state.routes.borrow().is_empty() {
if let Some(status) = router_status {
if let Ok(mut lock) = status.status.write() {
*lock = Some(404);
}
}
Some(base_route.outlet().into_view(cx))
} else {
if let Some(status) = router_status {
if let Ok(mut lock) = status.status.write() {
*lock = None;
}
}
let root = state.routes.borrow();
let root = root.get(0);
if let Some(route) = root {

View File

@@ -55,10 +55,6 @@ impl History for BrowserIntegration {
let (location, set_location) = create_signal(cx, Self::current());
leptos_dom::window_event_listener("popstate", move |_| {
log::debug!(
"[BrowserIntegration::location] popstate fired {:#?}",
Self::current()
);
let router = use_context::<RouterContext>(cx);
if let Some(router) = router {
let change = Self::current();