mirror of
https://github.com/leptos-rs/leptos.git
synced 2025-12-28 10:11:56 -05:00
Compare commits
38 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c336eb8769 | ||
|
|
0f5f0de410 | ||
|
|
a385f502b6 | ||
|
|
603eead12d | ||
|
|
8a73f3b879 | ||
|
|
5fc8907b85 | ||
|
|
678990194f | ||
|
|
b54a60213b | ||
|
|
ebeb1d69d1 | ||
|
|
cadb04b076 | ||
|
|
490b7a1596 | ||
|
|
f4d781e739 | ||
|
|
ebe5bf4600 | ||
|
|
d62046dc6f | ||
|
|
c7abb57168 | ||
|
|
bc0cd5d0ba | ||
|
|
7df67444f9 | ||
|
|
40155e91ea | ||
|
|
5c062fa6f1 | ||
|
|
3517820afd | ||
|
|
300cc4f54c | ||
|
|
586e9be99a | ||
|
|
6ed86d0ee9 | ||
|
|
1fe93fd588 | ||
|
|
2723871a80 | ||
|
|
70d92c7f42 | ||
|
|
e96d4b0687 | ||
|
|
ce0910caca | ||
|
|
81a937277d | ||
|
|
355e711964 | ||
|
|
27ec506fd5 | ||
|
|
79c76ae4cb | ||
|
|
e416815591 | ||
|
|
81bdd6788f | ||
|
|
ae0a243cc0 | ||
|
|
7893ff8b55 | ||
|
|
6130e708ce | ||
|
|
d049d2f36b |
18
Cargo.toml
18
Cargo.toml
@@ -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
|
||||
|
||||
@@ -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>
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
}
|
||||
|
||||
@@ -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>})}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
</>
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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());
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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>"
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)] {
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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>(
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "leptos_meta"
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
edition = "2021"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
|
||||
@@ -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\"/>")
|
||||
/// });
|
||||
/// # }
|
||||
/// ```
|
||||
|
||||
@@ -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."
|
||||
|
||||
|
||||
@@ -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., it’s a link to the page you’re on).
|
||||
/// This is helpful for accessibility and for styling. For example, maybe you want to set the link a
|
||||
/// different color if it’s a link to the page you’re currently on.
|
||||
#[component]
|
||||
pub fn A<H>(
|
||||
cx: Scope,
|
||||
|
||||
@@ -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>>,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user