mirror of
https://github.com/leptos-rs/leptos
synced 2024-11-10 23:04:24 +00:00
Merge branch 'leptos_dom_v2' of https://github.com/jquesada2016/leptos into leptos_dom_v2
This commit is contained in:
commit
4340fbfc78
31 changed files with 240 additions and 243 deletions
|
@ -1,7 +1,7 @@
|
|||
use wasm_bindgen_test::*;
|
||||
|
||||
wasm_bindgen_test_configure!(run_in_browser);
|
||||
use leptos::{wasm_bindgen::JsValue, *};
|
||||
use leptos::*;
|
||||
use web_sys::HtmlElement;
|
||||
|
||||
use counters::{Counters, CountersProps};
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
use std::time::Duration;
|
||||
|
||||
use leptos::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
|
|
|
@ -7,28 +7,31 @@ edition = "2021"
|
|||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1"
|
||||
console_log = "0.2"
|
||||
console_error_panic_hook = "0.1"
|
||||
futures = "0.3"
|
||||
cfg-if = "1"
|
||||
anyhow = "1.0.66"
|
||||
console_log = "0.2.0"
|
||||
console_error_panic_hook = "0.1.7"
|
||||
futures = "0.3.25"
|
||||
cfg-if = "1.0.0"
|
||||
leptos = { path = "../../leptos", default-features = false, features = [
|
||||
"serde",
|
||||
] }
|
||||
leptos_axum = { path = "../../integrations/axum", optional = true }
|
||||
leptos_meta = { path = "../../meta", default-features = false }
|
||||
leptos_router = { path = "../../router", default-features = false }
|
||||
log = "0.4"
|
||||
simple_logger = "2"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
gloo-net = { version = "0.2", features = ["http"] }
|
||||
reqwest = { version = "0.11", features = ["json"] }
|
||||
axum = { version = "0.5.17", optional = true }
|
||||
log = "0.4.17"
|
||||
simple_logger = "4.0.0"
|
||||
serde = { version = "1.0.148", features = ["derive"] }
|
||||
serde_json = "1.0.89"
|
||||
gloo-net = { version = "0.2.5", features = ["http"] }
|
||||
reqwest = { version = "0.11.13", features = ["json"] }
|
||||
axum = { version = "0.6.1", optional = true }
|
||||
tower = { version = "0.4.13", optional = true }
|
||||
tower-http = { version = "0.3.4", features = ["fs"], optional = true }
|
||||
tokio = { version = "1.0", features = ["full"], optional = true }
|
||||
tokio = { version = "1.22.0", features = ["full"], optional = true }
|
||||
http = { version = "0.2.8", optional = true }
|
||||
web-sys = { version = "0.3", features = ["AbortController", "AbortSignal"] }
|
||||
wasm-bindgen = "0.2"
|
||||
tracing = "0.1"
|
||||
|
||||
[features]
|
||||
default = ["csr"]
|
||||
|
|
|
@ -9,7 +9,7 @@ app into one CRS bundle
|
|||
## Server Side Rendering With Hydration
|
||||
To run it as a server side app with hydration, first you should run
|
||||
```bash
|
||||
wasm-pack build --target=web --no-default-features --features=hydrate
|
||||
wasm-pack build --target=web --debug --no-default-features --features=hydrate
|
||||
```
|
||||
to generate the Webassembly to provide hydration features for the server.
|
||||
Then run the server with `cargo run` to serve the server side rendered HTML and the WASM bundle for hydration.
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use leptos::Serializable;
|
||||
use leptos::{on_cleanup, Scope, Serializable};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub fn story(path: &str) -> String {
|
||||
|
@ -10,11 +10,15 @@ pub fn user(path: &str) -> String {
|
|||
}
|
||||
|
||||
#[cfg(not(feature = "ssr"))]
|
||||
pub async fn fetch_api<T>(path: &str) -> Option<T>
|
||||
pub async fn fetch_api<T>(cx: Scope, path: &str) -> Option<T>
|
||||
where
|
||||
T: Serializable,
|
||||
{
|
||||
let abort_controller = web_sys::AbortController::new().ok();
|
||||
let abort_signal = abort_controller.as_ref().map(|a| a.signal());
|
||||
|
||||
let json = gloo_net::http::Request::get(path)
|
||||
.abort_signal(abort_signal.as_ref())
|
||||
.send()
|
||||
.await
|
||||
.map_err(|e| log::error!("{e}"))
|
||||
|
@ -22,11 +26,19 @@ where
|
|||
.text()
|
||||
.await
|
||||
.ok()?;
|
||||
|
||||
// abort in-flight requests if the Scope is disposed
|
||||
// i.e., if we've navigated away from this page
|
||||
on_cleanup(cx, move || {
|
||||
if let Some(abort_controller) = abort_controller {
|
||||
abort_controller.abort()
|
||||
}
|
||||
});
|
||||
T::from_json(&json).ok()
|
||||
}
|
||||
|
||||
#[cfg(feature = "ssr")]
|
||||
pub async fn fetch_api<T>(path: &str) -> Option<T>
|
||||
pub async fn fetch_api<T>(cx: Scope, path: &str) -> Option<T>
|
||||
where
|
||||
T: Serializable,
|
||||
{
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use cfg_if::cfg_if;
|
||||
use leptos::*;
|
||||
use leptos::{component, Scope, IntoView, provide_context, view};
|
||||
use leptos_meta::*;
|
||||
use leptos_router::*;
|
||||
mod api;
|
||||
|
@ -11,13 +11,12 @@ use routes::story::*;
|
|||
use routes::users::*;
|
||||
|
||||
#[component]
|
||||
pub fn App(cx: Scope) -> Element {
|
||||
provide_context(cx, MetaContext::default());
|
||||
|
||||
pub fn App(cx: Scope) -> impl IntoView {
|
||||
view! {
|
||||
cx,
|
||||
<div>
|
||||
<Stylesheet href="/static/style.css"/>
|
||||
<>
|
||||
<Stylesheet href="/style.css"/>
|
||||
<Meta name="description" content="Leptos implementation of a HackerNews demo."/>
|
||||
<Router>
|
||||
<Nav />
|
||||
<main>
|
||||
|
@ -28,7 +27,7 @@ pub fn App(cx: Scope) -> Element {
|
|||
</Routes>
|
||||
</main>
|
||||
</Router>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -41,7 +40,7 @@ cfg_if! {
|
|||
pub fn hydrate() {
|
||||
_ = console_log::init_with_level(log::Level::Debug);
|
||||
console_error_panic_hook::set_once();
|
||||
leptos::hydrate(body().unwrap(), move |cx| {
|
||||
leptos::mount_to_body(move |cx| {
|
||||
view! { cx, <App/> }
|
||||
});
|
||||
}
|
||||
|
|
|
@ -4,32 +4,47 @@ use leptos::*;
|
|||
// boilerplate to run in different modes
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "ssr")] {
|
||||
// use actix_files::{Files, NamedFile};
|
||||
// use actix_web::*;
|
||||
use axum::{
|
||||
routing::{get},
|
||||
Router,
|
||||
handler::Handler,
|
||||
error_handling::HandleError,
|
||||
};
|
||||
use http::StatusCode;
|
||||
use std::net::SocketAddr;
|
||||
use leptos_hackernews_axum::handlers::{file_handler, get_static_file_handler};
|
||||
use tower_http::services::ServeDir;
|
||||
use std::env;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
use leptos_hackernews_axum::*;
|
||||
|
||||
let addr = SocketAddr::from(([127, 0, 0, 1], 8082));
|
||||
let addr = SocketAddr::from(([127, 0, 0, 1], 3002));
|
||||
|
||||
log::debug!("serving at {addr}");
|
||||
|
||||
simple_logger::init_with_level(log::Level::Debug).expect("couldn't initialize logging");
|
||||
|
||||
// These are Tower Services that will serve files from the static and pkg repos.
|
||||
// HandleError is needed as Axum requires services to implement Infallible Errors
|
||||
// because all Errors are converted into Responses
|
||||
let static_service = HandleError::new( ServeDir::new("./static"), handle_file_error);
|
||||
let pkg_service =HandleError::new( ServeDir::new("./pkg"), handle_file_error);
|
||||
|
||||
/// Convert the Errors from ServeDir to a type that implements IntoResponse
|
||||
async fn handle_file_error(err: std::io::Error) -> (StatusCode, String) {
|
||||
(
|
||||
StatusCode::NOT_FOUND,
|
||||
format!("File Not Found: {}", err),
|
||||
)
|
||||
}
|
||||
|
||||
let render_options: RenderOptions = RenderOptions::builder().pkg_path("/pkg/leptos_hackernews_axum").socket_address(addr).reload_port(3001).environment(&env::var("RUST_ENV")).build();
|
||||
render_options.write_to_file();
|
||||
// build our application with a route
|
||||
let app = Router::new()
|
||||
// `GET /` goes to `root`
|
||||
.nest("/pkg", get(file_handler))
|
||||
.nest("/static", get(get_static_file_handler))
|
||||
.fallback(leptos_axum::render_app_to_stream("leptos_hackernews_axum", |cx| view! { cx, <App/> }).into_service());
|
||||
.nest_service("/pkg", pkg_service)
|
||||
.nest_service("/static", static_service)
|
||||
.fallback(leptos_axum::render_app_to_stream(render_options, |cx| view! { cx, <App/> }));
|
||||
|
||||
// run our app with hyper
|
||||
// `axum::Server` is a re-export of `hyper::Server`
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
use leptos::*;
|
||||
use leptos::{component, Scope, IntoView, view};
|
||||
use leptos_router::*;
|
||||
|
||||
#[component]
|
||||
pub fn Nav(cx: Scope) -> Element {
|
||||
pub fn Nav(cx: Scope) -> impl IntoView {
|
||||
view! { cx,
|
||||
<header class="header">
|
||||
<nav class="inner">
|
||||
|
|
|
@ -14,7 +14,7 @@ fn category(from: &str) -> &'static str {
|
|||
}
|
||||
|
||||
#[component]
|
||||
pub fn Stories(cx: Scope) -> Element {
|
||||
pub fn Stories(cx: Scope) -> impl IntoView {
|
||||
let query = use_query_map(cx);
|
||||
let params = use_params_map(cx);
|
||||
let page = move || {
|
||||
|
@ -32,11 +32,13 @@ pub fn Stories(cx: Scope) -> Element {
|
|||
move || (page(), story_type()),
|
||||
move |(page, story_type)| async move {
|
||||
let path = format!("{}?page={}", category(&story_type), page);
|
||||
api::fetch_api::<Vec<api::Story>>(&api::story(&path)).await
|
||||
api::fetch_api::<Vec<api::Story>>(cx, &api::story(&path)).await
|
||||
},
|
||||
);
|
||||
let (pending, set_pending) = create_signal(cx, false);
|
||||
|
||||
let hide_more_link = move || stories.read().unwrap_or(None).unwrap_or_default().len() < 28;
|
||||
let hide_more_link =
|
||||
move || pending() || stories.read().unwrap_or(None).unwrap_or_default().len() < 28;
|
||||
|
||||
view! {
|
||||
cx,
|
||||
|
@ -52,14 +54,14 @@ pub fn Stories(cx: Scope) -> Element {
|
|||
>
|
||||
"< prev"
|
||||
</a>
|
||||
}
|
||||
}.into_any()
|
||||
} else {
|
||||
view! {
|
||||
cx,
|
||||
<span class="page-link disabled" aria-hidden="true">
|
||||
"< prev"
|
||||
</span>
|
||||
}
|
||||
}.into_any()
|
||||
}}
|
||||
</span>
|
||||
<span>"page " {page}</span>
|
||||
|
@ -76,25 +78,30 @@ pub fn Stories(cx: Scope) -> Element {
|
|||
</div>
|
||||
<main class="news-list">
|
||||
<div>
|
||||
<Suspense fallback=view! { cx, <p>"Loading..."</p> }>
|
||||
<Transition
|
||||
fallback=move || view! { cx, <p>"Loading..."</p> }
|
||||
set_pending=set_pending.into()
|
||||
>
|
||||
{move || match stories.read() {
|
||||
None => None,
|
||||
Some(None) => Some(view! { cx, <p>"Error loading stories."</p> }),
|
||||
Some(None) => Some(view! { cx, <p>"Error loading stories."</p> }.into_any()),
|
||||
Some(Some(stories)) => {
|
||||
Some(view! { cx,
|
||||
<ul>
|
||||
<For each=move || stories.clone() key=|story| story.id>{
|
||||
move |cx: Scope, story: &api::Story| {
|
||||
<For
|
||||
each=move || stories.clone()
|
||||
key=|story| story.id
|
||||
view=move |story: api::Story| {
|
||||
view! { cx,
|
||||
<Story story=story.clone() />
|
||||
<Story story/>
|
||||
}
|
||||
}
|
||||
}</For>
|
||||
/>
|
||||
</ul>
|
||||
})
|
||||
}.into_any())
|
||||
}
|
||||
}}
|
||||
</Suspense>
|
||||
</Transition>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
@ -102,7 +109,7 @@ pub fn Stories(cx: Scope) -> Element {
|
|||
}
|
||||
|
||||
#[component]
|
||||
fn Story(cx: Scope, story: api::Story) -> Element {
|
||||
fn Story(cx: Scope, story: api::Story) -> impl IntoView {
|
||||
view! { cx,
|
||||
<li class="news-item">
|
||||
<span class="score">{story.points}</span>
|
||||
|
@ -115,10 +122,10 @@ fn Story(cx: Scope, story: api::Story) -> Element {
|
|||
</a>
|
||||
<span class="host">"("{story.domain}")"</span>
|
||||
</span>
|
||||
}
|
||||
}.into_view(cx)
|
||||
} else {
|
||||
let title = story.title.clone();
|
||||
view! { cx, <A href=format!("/stories/{}", story.id)>{title.clone()}</A> }
|
||||
view! { cx, <A href=format!("/stories/{}", story.id)>{title.clone()}</A> }.into_view(cx)
|
||||
}}
|
||||
</span>
|
||||
<br />
|
||||
|
@ -137,17 +144,15 @@ fn Story(cx: Scope, story: api::Story) -> Element {
|
|||
}}
|
||||
</A>
|
||||
</span>
|
||||
}
|
||||
}.into_view(cx)
|
||||
} else {
|
||||
let title = story.title.clone();
|
||||
view! { cx, <A href=format!("/item/{}", story.id)>{title.clone()}</A> }
|
||||
view! { cx, <A href=format!("/item/{}", story.id)>{title.clone()}</A> }.into_view(cx)
|
||||
}}
|
||||
</span>
|
||||
{(story.story_type != "link").then(|| view! { cx,
|
||||
<span>
|
||||
//{" "}
|
||||
<span class="label">{story.story_type}</span>
|
||||
</span>
|
||||
" "
|
||||
<span class="label">{story.story_type}</span>
|
||||
})}
|
||||
</li>
|
||||
}
|
||||
|
|
|
@ -1,18 +1,27 @@
|
|||
use crate::api;
|
||||
use leptos::*;
|
||||
use leptos_meta::*;
|
||||
use leptos_router::*;
|
||||
|
||||
#[component]
|
||||
pub fn Story(cx: Scope) -> Element {
|
||||
pub fn Story(cx: Scope) -> impl IntoView {
|
||||
let params = use_params_map(cx);
|
||||
let story = create_resource(
|
||||
cx,
|
||||
move || params().get("id").cloned().unwrap_or_default(),
|
||||
move |id| async move { api::fetch_api::<api::Story>(&api::story(&format!("item/{id}"))).await },
|
||||
move |id| async move {
|
||||
if id.is_empty() {
|
||||
None
|
||||
} else {
|
||||
api::fetch_api::<api::Story>(cx, &api::story(&format!("item/{id}"))).await
|
||||
}
|
||||
},
|
||||
);
|
||||
let meta_description = move || story.read().and_then(|story| story.map(|story| story.title.clone())).unwrap_or_else(|| "Loading story...".to_string());
|
||||
|
||||
view! { cx,
|
||||
<div>
|
||||
<>
|
||||
<Meta name="description" content=meta_description/>
|
||||
{move || story.read().map(|story| match story {
|
||||
None => view! { cx, <div class="item-view">"Error loading this story."</div> },
|
||||
Some(story) => view! { cx,
|
||||
|
@ -40,19 +49,21 @@ pub fn Story(cx: Scope) -> Element {
|
|||
}}
|
||||
</p>
|
||||
<ul class="comment-children">
|
||||
<For each=move || story.comments.clone().unwrap_or_default() key=|comment| comment.id>
|
||||
{move |cx, comment: &api::Comment| view! { cx, <Comment comment=comment.clone() /> }}
|
||||
</For>
|
||||
<For
|
||||
each=move || story.comments.clone().unwrap_or_default()
|
||||
key=|comment| comment.id
|
||||
view=move |comment| view! { cx, <Comment comment /> }
|
||||
/>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
}})}
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn Comment(cx: Scope, comment: api::Comment) -> Element {
|
||||
pub fn Comment(cx: Scope, comment: api::Comment) -> impl IntoView {
|
||||
let (open, set_open) = create_signal(cx, true);
|
||||
|
||||
view! { cx,
|
||||
|
@ -81,9 +92,11 @@ pub fn Comment(cx: Scope, comment: api::Comment) -> Element {
|
|||
let comments = comment.comments.clone();
|
||||
move || view! { cx,
|
||||
<ul class="comment-children">
|
||||
<For each=move || comments.clone() key=|comment| comment.id>
|
||||
{|cx, comment: &api::Comment| view! { cx, <Comment comment=comment.clone() /> }}
|
||||
</For>
|
||||
<For
|
||||
each=move || comments.clone()
|
||||
key=|comment| comment.id
|
||||
view=move |comment: api::Comment| view! { cx, <Comment comment /> }
|
||||
/>
|
||||
</ul>
|
||||
}
|
||||
})}
|
||||
|
|
|
@ -3,17 +3,23 @@ use leptos::*;
|
|||
use leptos_router::*;
|
||||
|
||||
#[component]
|
||||
pub fn User(cx: Scope) -> Element {
|
||||
pub fn User(cx: Scope) -> impl IntoView {
|
||||
let params = use_params_map(cx);
|
||||
let user = create_resource(
|
||||
cx,
|
||||
move || params().get("id").cloned().unwrap_or_default(),
|
||||
move |id| async move { api::fetch_api::<User>(&api::user(&id)).await },
|
||||
move |id| async move {
|
||||
if id.is_empty() {
|
||||
None
|
||||
} else {
|
||||
api::fetch_api::<User>(cx, &api::user(&id)).await
|
||||
}
|
||||
},
|
||||
);
|
||||
view! { cx,
|
||||
<div class="user-view">
|
||||
{move || user.read().map(|user| match user {
|
||||
None => view! { cx, <h1>"User not found."</h1> },
|
||||
None => view! { cx, <h1>"User not found."</h1> }.into_any(),
|
||||
Some(user) => view! { cx,
|
||||
<div>
|
||||
<h1>"User: " {&user.id}</h1>
|
||||
|
@ -32,7 +38,7 @@ pub fn User(cx: Scope) -> Element {
|
|||
<a href=format!("https://news.ycombinator.com/threads?id={}", user.id)>"comments"</a>
|
||||
</p>
|
||||
</div>
|
||||
}
|
||||
}.into_any()
|
||||
})}
|
||||
</div>
|
||||
}
|
||||
|
|
|
@ -307,8 +307,8 @@ pub fn Todo(cx: Scope, todo: Todo) -> impl IntoView {
|
|||
class="edit"
|
||||
class:hidden={move || !(editing)()}
|
||||
prop:value={move || todo.title.get()}
|
||||
on:focusout=move |ev| save(&event_target_value(&ev))
|
||||
on:keyup={move |ev| {
|
||||
on:focusout=move |ev: web_sys::FocusEvent| save(&event_target_value(&ev))
|
||||
on:keyup={move |ev: web_sys::KeyboardEvent| {
|
||||
let key_code = ev.key_code();
|
||||
if key_code == ENTER_KEY {
|
||||
save(&event_target_value(&ev));
|
||||
|
|
|
@ -228,7 +228,7 @@ pub type PinnedHtmlStream = Pin<Box<dyn Stream<Item = io::Result<Bytes>> + Send>
|
|||
/// use leptos::*;
|
||||
///
|
||||
/// #[component]
|
||||
/// fn MyApp(cx: Scope) -> Element {
|
||||
/// fn MyApp(cx: Scope) -> impl IntoView {
|
||||
/// view! { cx, <main>"Hello, world!"</main> }
|
||||
/// }
|
||||
///
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
use cfg_if::cfg_if;
|
||||
use leptos_macro::component;
|
||||
use std::rc::Rc;
|
||||
use leptos_dom::{DynChild, Fragment, IntoView, Component, HydrationCtx};
|
||||
use leptos_dom::{DynChild, Fragment, IntoView, Component};
|
||||
use leptos_reactive::{provide_context, Scope, SuspenseContext};
|
||||
#[cfg(not(any(feature = "csr", feature = "hydrate")))]
|
||||
use leptos_dom::HydrationKey;
|
||||
use leptos_dom::{HydrationCtx, HydrationKey};
|
||||
|
||||
/// If any [Resources](leptos_reactive::Resource) are read in the `children` of this
|
||||
/// component, it will show the `fallback` while they are loading. Once all are resolved,
|
||||
|
|
|
@ -61,7 +61,7 @@ fn test_classes() {
|
|||
use leptos::*;
|
||||
|
||||
_ = create_scope(create_runtime(), |cx| {
|
||||
let (value, set_value) = create_signal(cx, 5);
|
||||
let (value, _set_value) = create_signal(cx, 5);
|
||||
let rendered = view! {
|
||||
cx,
|
||||
<div class="my big" class:a={move || value.get() > 10} class:red=true class:car={move || value.get() > 1}></div>
|
||||
|
|
|
@ -26,6 +26,9 @@ tracing = "0.1"
|
|||
wasm-bindgen = { version = "0.2", features = ["enable-interning"] }
|
||||
wasm-bindgen-futures = "0.4.31"
|
||||
|
||||
[dev-dependencies]
|
||||
leptos = { path = "../leptos" }
|
||||
|
||||
[dependencies.web-sys]
|
||||
version = "0.3"
|
||||
features = [
|
||||
|
|
|
@ -542,7 +542,10 @@ impl<El: ElementDescriptor> HtmlElement<El> {
|
|||
pub fn on<E: EventDescriptor + 'static>(
|
||||
self,
|
||||
event: E,
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
mut event_handler: impl EventHandler<E>,
|
||||
#[cfg(not(all(target_arch = "wasm32", feature = "web")))]
|
||||
event_handler: impl EventHandler<E>,
|
||||
) -> Self {
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
{
|
||||
|
|
|
@ -539,34 +539,40 @@ impl View {
|
|||
event: E,
|
||||
event_handler: impl FnMut(E::EventType) + 'static,
|
||||
) -> Self {
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
match &self {
|
||||
Self::Element(el) => {
|
||||
if event.bubbles() {
|
||||
add_event_listener(&el.element, event.name(), event_handler);
|
||||
} else {
|
||||
add_event_listener_undelegated(
|
||||
&el.element,
|
||||
&event.name(),
|
||||
event_handler,
|
||||
);
|
||||
cfg_if! {
|
||||
if #[cfg(all(target_arch = "wasm32", feature = "web"))] {
|
||||
match &self {
|
||||
Self::Element(el) => {
|
||||
if event.bubbles() {
|
||||
add_event_listener(&el.element, event.name(), event_handler);
|
||||
} else {
|
||||
add_event_listener_undelegated(
|
||||
&el.element,
|
||||
&event.name(),
|
||||
event_handler,
|
||||
);
|
||||
}
|
||||
}
|
||||
Self::Component(c) => {
|
||||
let event_handler = Rc::new(RefCell::new(event_handler));
|
||||
|
||||
c.children.iter().cloned().for_each(|c| {
|
||||
let event_handler = event_handler.clone();
|
||||
|
||||
c.on(event.clone(), move |e| event_handler.borrow_mut()(e));
|
||||
});
|
||||
}
|
||||
Self::CoreComponent(c) => match c {
|
||||
CoreComponent::DynChild(_) => {}
|
||||
CoreComponent::Each(_) => {}
|
||||
_ => {}
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
} else {
|
||||
_ = event;
|
||||
_ = event_handler;
|
||||
}
|
||||
Self::Component(c) => {
|
||||
let event_handler = Rc::new(RefCell::new(event_handler));
|
||||
|
||||
c.children.iter().cloned().for_each(|c| {
|
||||
let event_handler = event_handler.clone();
|
||||
|
||||
c.on(event.clone(), move |e| event_handler.borrow_mut()(e));
|
||||
});
|
||||
}
|
||||
Self::CoreComponent(c) => match c {
|
||||
CoreComponent::DynChild(_) => {}
|
||||
CoreComponent::Each(_) => {}
|
||||
_ => {}
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
|
||||
self
|
||||
|
@ -751,7 +757,7 @@ pub const fn is_browser() -> bool {
|
|||
/// Returns true if `debug_assertions` are enabled.
|
||||
/// ```
|
||||
/// # use leptos_dom::is_dev;
|
||||
/// if is_dev!() {
|
||||
/// if is_dev() {
|
||||
/// // log something or whatever
|
||||
/// }
|
||||
/// ```
|
||||
|
|
|
@ -6,14 +6,15 @@ use leptos_reactive::{create_rw_signal, RwSignal, Scope};
|
|||
/// ```
|
||||
/// # use leptos::*;
|
||||
/// #[component]
|
||||
/// pub fn MyComponent(cx: Scope) -> Element {
|
||||
/// let input_ref = NodeRef::new(cx);
|
||||
/// pub fn MyComponent(cx: Scope) -> impl IntoView {
|
||||
/// let input_ref = NodeRef::<HtmlElement<Input>>::new(cx);
|
||||
///
|
||||
/// let on_click = move |_| {
|
||||
/// let node = input_ref
|
||||
/// .get()
|
||||
/// .expect("input_ref should be loaded by now")
|
||||
/// .unchecked_into::<web_sys::HtmlInputElement>();
|
||||
/// .expect("input_ref should be loaded by now");
|
||||
/// // `node` is strongly typed
|
||||
/// // it is dereferenced to an `HtmlInputElement` automatically
|
||||
/// log!("value is {:?}", node.value())
|
||||
/// };
|
||||
///
|
||||
|
|
|
@ -11,11 +11,12 @@ use std::borrow::Cow;
|
|||
///
|
||||
/// ```
|
||||
/// # cfg_if::cfg_if! { if #[cfg(not(any(feature = "csr", feature = "hydrate")))] {
|
||||
/// # use leptos_reactive::*; use leptos_dom::*; use leptos_macro::view;
|
||||
/// # use leptos::*;
|
||||
/// let html = render_to_string(|cx| view! { cx,
|
||||
/// <p>"Hello, world!"</p>
|
||||
/// });
|
||||
/// assert_eq!(html, r#"<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>");
|
||||
/// # }}
|
||||
/// ```
|
||||
pub fn render_to_string<F, N>(f: F) -> String
|
||||
|
@ -151,6 +152,7 @@ pub fn render_to_stream_with_prefix_undisposed(
|
|||
let fragments = fragments.map(|(fragment_id, id_before_suspense, html)| {
|
||||
cfg_if! {
|
||||
if #[cfg(debug_assertions)] {
|
||||
_ = id_before_suspense;
|
||||
// Debug-mode <Suspense/>-replacement code
|
||||
format!(
|
||||
r#"
|
||||
|
@ -447,89 +449,4 @@ fn to_kebab_case(name: &str) -> String {
|
|||
}
|
||||
|
||||
new_name
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn simple_ssr_test() {
|
||||
use leptos::*;
|
||||
|
||||
_ = create_scope(create_runtime(), |cx| {
|
||||
let (value, set_value) = create_signal(cx, 0);
|
||||
let rendered = view! {
|
||||
cx,
|
||||
<div>
|
||||
<button on:click=move |_| set_value.update(|value| *value -= 1)>"-1"</button>
|
||||
<span>"Value: " {move || value.get().to_string()} "!"</span>
|
||||
<button on:click=move |_| set_value.update(|value| *value += 1)>"+1"</button>
|
||||
</div>
|
||||
}.render_to_string();
|
||||
|
||||
assert_eq!(
|
||||
rendered,
|
||||
"<div><button id=\"1-1-2\">-1</button><span>Value: <template \
|
||||
id=\"2-4-6o\"/> <template id=\"2-4-6c\"/>!</span><button \
|
||||
id=\"1-3-4\">+1</button></div>"
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ssr_test_with_components() {
|
||||
use leptos::*;
|
||||
|
||||
#[component]
|
||||
fn Counter(cx: Scope, initial_value: i32) -> View {
|
||||
let (value, set_value) = create_signal(cx, initial_value);
|
||||
view! {
|
||||
cx,
|
||||
<div>
|
||||
<button on:click=move |_| set_value.update(|value| *value -= 1)>"-1"</button>
|
||||
<span>"Value: " {move || value.get().to_string()} "!"</span>
|
||||
<button on:click=move |_| set_value.update(|value| *value += 1)>"+1"</button>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
_ = create_scope(create_runtime(), |cx| {
|
||||
let rendered = view! {
|
||||
cx,
|
||||
<div class="counters">
|
||||
<Counter initial_value=1/>
|
||||
<Counter initial_value=2/>
|
||||
</div>
|
||||
}
|
||||
.render_to_string();
|
||||
|
||||
assert_eq!(
|
||||
rendered,
|
||||
"<div class=\"counters\"><template id=\"1-1-2o\"/><div><button \
|
||||
id=\"3-1-4\">-1</button><span>Value: <template id=\"4-4-8o\"/> \
|
||||
<template id=\"4-4-8c\"/>!</span><button \
|
||||
id=\"3-3-6\">+1</button></div><template id=\"1-1-2c\"/><template \
|
||||
id=\"1-2-3o\"/><div><button id=\"3-1-4\">-1</button><span>Value: \
|
||||
<template id=\"4-4-8o\"/> <template id=\"4-4-8c\"/>!</span><button \
|
||||
id=\"3-3-6\">+1</button></div><template id=\"1-2-3c\"/></div>"
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_classes() {
|
||||
use leptos::*;
|
||||
|
||||
_ = create_scope(create_runtime(), |cx| {
|
||||
let (value, set_value) = create_signal(cx, 5);
|
||||
let rendered = view! {
|
||||
cx,
|
||||
<div class="my big" class:a={move || value.get() > 10} class:red=true class:car={move || value.get() > 1}></div>
|
||||
}.render_to_string();
|
||||
|
||||
assert_eq!(
|
||||
rendered,
|
||||
"<div class=\"my big red car\" id=\"0-0-0\"></div>"
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -29,6 +29,7 @@ lazy_static = "1.4"
|
|||
[dev-dependencies]
|
||||
log = "0.4"
|
||||
typed-builder = "0.10"
|
||||
leptos = { path = "../leptos" }
|
||||
|
||||
[features]
|
||||
default = ["ssr"]
|
||||
|
|
|
@ -38,7 +38,7 @@ mod server;
|
|||
///
|
||||
/// 1. Text content should be provided as a Rust string, i.e., double-quoted:
|
||||
/// ```rust
|
||||
/// # use leptos_reactive::*; use leptos_dom::*; use leptos_macro::view; use leptos_dom::wasm_bindgen::JsCast;
|
||||
/// # use leptos::*;
|
||||
/// # run_scope(create_runtime(), |cx| {
|
||||
/// # if !cfg!(any(feature = "csr", feature = "hydrate")) {
|
||||
/// view! { cx, <p>"Here’s some text"</p> };
|
||||
|
@ -48,7 +48,7 @@ mod server;
|
|||
///
|
||||
/// 2. Self-closing tags need an explicit `/` as in XML/XHTML
|
||||
/// ```rust,compile_fail
|
||||
/// # use leptos_reactive::*; use leptos_dom::*; use leptos_macro::view; use leptos_dom::wasm_bindgen::JsCast;
|
||||
/// # use leptos::*;
|
||||
/// # run_scope(create_runtime(), |cx| {
|
||||
/// # if !cfg!(any(feature = "csr", feature = "hydrate")) {
|
||||
/// // ❌ not like this
|
||||
|
@ -58,7 +58,7 @@ mod server;
|
|||
/// # });
|
||||
/// ```
|
||||
/// ```rust
|
||||
/// # use leptos_reactive::*; use leptos_dom::*; use leptos_macro::view; use leptos_dom::wasm_bindgen::JsCast;
|
||||
/// # use leptos::*;
|
||||
/// # run_scope(create_runtime(), |cx| {
|
||||
/// # if !cfg!(any(feature = "csr", feature = "hydrate")) {
|
||||
/// // ✅ add that slash
|
||||
|
@ -70,9 +70,9 @@ mod server;
|
|||
///
|
||||
/// 3. Components (functions annotated with `#[component]`) can be inserted as camel-cased tags
|
||||
/// ```rust
|
||||
/// # use leptos_reactive::*; use leptos_dom::*; use leptos_macro::*; use typed_builder::TypedBuilder; use leptos_dom::wasm_bindgen::JsCast; use leptos_dom as leptos; use leptos_dom::Marker;
|
||||
/// # #[derive(TypedBuilder)] struct CounterProps { initial_value: i32 }
|
||||
/// # fn Counter(cx: Scope, props: CounterProps) -> impl IntoView { view! { cx, <p></p>} }
|
||||
/// # use leptos::*;
|
||||
/// # #[component]
|
||||
/// # fn Counter(cx: Scope, initial_value: i32) -> impl IntoView { view! { cx, <p></p>} }
|
||||
/// # run_scope(create_runtime(), |cx| {
|
||||
/// # if !cfg!(any(feature = "csr", feature = "hydrate")) {
|
||||
/// view! { cx, <div><Counter initial_value=3 /></div> }
|
||||
|
@ -90,7 +90,7 @@ mod server;
|
|||
/// take an `Option`, in which case `Some` sets the attribute and `None` removes the attribute.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use leptos_reactive::*; use leptos_dom::*; use leptos_macro::view; use leptos_dom::wasm_bindgen::JsCast; use leptos_dom as leptos; use leptos_dom::Marker;
|
||||
/// # use leptos::*;
|
||||
/// # run_scope(create_runtime(), |cx| {
|
||||
/// # if !cfg!(any(feature = "csr", feature = "hydrate")) {
|
||||
/// let (count, set_count) = create_signal(cx, 0);
|
||||
|
@ -112,7 +112,7 @@ mod server;
|
|||
/// 5. Event handlers can be added with `on:` attributes. In most cases, the events are given the correct type
|
||||
/// based on the event name.
|
||||
/// ```rust
|
||||
/// # use leptos_reactive::*; use leptos_dom::*; use leptos_macro::view; use leptos_dom::wasm_bindgen::JsCast;
|
||||
/// # use leptos::*;
|
||||
/// # run_scope(create_runtime(), |cx| {
|
||||
/// # if !cfg!(any(feature = "csr", feature = "hydrate")) {
|
||||
/// view! {
|
||||
|
@ -132,7 +132,7 @@ mod server;
|
|||
/// that returns a primitive or JsValue). They can also take an `Option`, in which case `Some` sets the property
|
||||
/// and `None` deletes the property.
|
||||
/// ```rust
|
||||
/// # use leptos_reactive::*; use leptos_dom::*; use leptos_macro::view; use leptos_dom::wasm_bindgen::JsCast;
|
||||
/// # use leptos::*;
|
||||
/// # run_scope(create_runtime(), |cx| {
|
||||
/// # if !cfg!(any(feature = "csr", feature = "hydrate")) {
|
||||
/// let (name, set_name) = create_signal(cx, "Alice".to_string());
|
||||
|
@ -154,7 +154,7 @@ mod server;
|
|||
///
|
||||
/// 7. Classes can be toggled with `class:` attributes, which take a `bool` (or a signal that returns a `bool`).
|
||||
/// ```rust
|
||||
/// # use leptos_reactive::*; use leptos_dom::*; use leptos_macro::view; use leptos_dom::wasm_bindgen::JsCast;
|
||||
/// # use leptos::*;
|
||||
/// # run_scope(create_runtime(), |cx| {
|
||||
/// # if !cfg!(any(feature = "csr", feature = "hydrate")) {
|
||||
/// let (count, set_count) = create_signal(cx, 2);
|
||||
|
@ -166,7 +166,7 @@ mod server;
|
|||
///
|
||||
/// Class names can include dashes, but cannot (at the moment) include a dash-separated segment of only numbers.
|
||||
/// ```rust,compile_fail
|
||||
/// # use leptos_reactive::*; use leptos_dom::*; use leptos_macro::view; use leptos_dom::wasm_bindgen::JsCast;
|
||||
/// # use leptos::*;
|
||||
/// # run_scope(create_runtime(), |cx| {
|
||||
/// # if !cfg!(any(feature = "csr", feature = "hydrate")) {
|
||||
/// let (count, set_count) = create_signal(cx, 2);
|
||||
|
@ -180,7 +180,7 @@ mod server;
|
|||
/// 8. You can use the `_ref` attribute to store a reference to its DOM element in a
|
||||
/// [NodeRef](leptos_reactive::NodeRef) to use later.
|
||||
/// ```rust
|
||||
/// # use leptos_reactive::*; use leptos_dom::*; use leptos_macro::view; use leptos_dom::wasm_bindgen::JsCast;
|
||||
/// # use leptos::*;
|
||||
/// # run_scope(create_runtime(), |cx| {
|
||||
/// # if !cfg!(any(feature = "csr", feature = "hydrate")) {
|
||||
/// let (value, set_value) = create_signal(cx, 0);
|
||||
|
@ -194,7 +194,7 @@ mod server;
|
|||
///
|
||||
/// Here’s a simple example that shows off several of these features, put together
|
||||
/// ```rust
|
||||
/// # use leptos_reactive::*; use leptos_dom::*; use leptos_macro::*; use leptos_dom as leptos; use leptos_dom::Marker; use leptos_dom::wasm_bindgen::JsCast;
|
||||
/// # use leptos::*;
|
||||
///
|
||||
/// # if !cfg!(any(feature = "csr", feature = "hydrate")) {
|
||||
/// pub fn SimpleCounter(cx: Scope) -> impl IntoView {
|
||||
|
@ -342,7 +342,7 @@ pub fn view(tokens: TokenStream) -> TokenStream {
|
|||
/// // ❌ This won't work.
|
||||
/// # use leptos::*;
|
||||
/// #[component]
|
||||
/// fn MyComponent<T: Fn() -> impl IntoView>(cx: Scope, render_prop: T) -> impl IntoView {
|
||||
/// fn MyComponent<T: Fn() -> HtmlElement<Div>>(cx: Scope, render_prop: T) -> impl IntoView {
|
||||
/// todo!()
|
||||
/// }
|
||||
/// ```
|
||||
|
@ -351,18 +351,19 @@ pub fn view(tokens: TokenStream) -> TokenStream {
|
|||
/// // ✅ Do this instead
|
||||
/// # use leptos::*;
|
||||
/// #[component]
|
||||
/// fn MyComponent<T>(cx: Scope, render_prop: T) -> impl IntoView where T: Fn() -> impl IntoView {
|
||||
/// fn MyComponent<T>(cx: Scope, render_prop: T) -> impl IntoView
|
||||
/// where T: Fn() -> HtmlElement<Div> {
|
||||
/// todo!()
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// 5. You can access the children passed into the component with the `children` property, which takes
|
||||
/// an argument of the form `Box<dyn Fn(Scope) -> [Fragment](leptos_dom::Fragment)>`.
|
||||
/// an argument of the form `Box<dyn Fn(Scope) -> Fragment>`.
|
||||
///
|
||||
/// ```
|
||||
/// # use leptos::*;
|
||||
/// #[component]
|
||||
/// fn ComponentWithChildren(cx: Scope, children: Box<dyn Fn() -> Fragment>) -> impl IntoView {
|
||||
/// fn ComponentWithChildren(cx: Scope, children: Box<dyn Fn(Scope) -> Fragment>) -> impl IntoView {
|
||||
/// view! {
|
||||
/// cx,
|
||||
/// <ul>
|
||||
|
|
|
@ -133,7 +133,7 @@ pub fn server_macro_impl(args: proc_macro::TokenStream, s: TokenStream2) -> Resu
|
|||
#(#fields),*
|
||||
}
|
||||
|
||||
impl ServerFn for #struct_name {
|
||||
impl leptos::ServerFn for #struct_name {
|
||||
type Output = #output_ty;
|
||||
|
||||
fn prefix() -> &'static str {
|
||||
|
|
|
@ -33,6 +33,7 @@ cfg-if = "1.0.0"
|
|||
|
||||
[dev-dependencies]
|
||||
tokio-test = "0.4"
|
||||
leptos = { path = "../leptos" }
|
||||
|
||||
[features]
|
||||
default = []
|
||||
|
|
|
@ -27,7 +27,7 @@ use crate::{runtime::with_runtime, Scope};
|
|||
/// struct ValueSetter(WriteSignal<i32>);
|
||||
///
|
||||
/// #[component]
|
||||
/// pub fn Provider(cx: Scope) -> Element {
|
||||
/// pub fn Provider(cx: Scope) -> impl IntoView {
|
||||
/// let (value, set_value) = create_signal(cx, 0);
|
||||
///
|
||||
/// // the newtype pattern isn't *necessary* here but is a good practice
|
||||
|
@ -41,7 +41,7 @@ use crate::{runtime::with_runtime, Scope};
|
|||
/// }
|
||||
///
|
||||
/// #[component]
|
||||
/// pub fn Consumer(cx: Scope) -> Element {
|
||||
/// pub fn Consumer(cx: Scope) -> impl IntoView {
|
||||
/// // consume the provided context of type `ValueSetter` using `use_context`
|
||||
/// // this traverses up the tree of `Scope`s and gets the nearest provided `ValueSetter`
|
||||
/// let set_value = use_context::<ValueSetter>(cx).unwrap().0;
|
||||
|
@ -85,7 +85,7 @@ where
|
|||
/// struct ValueSetter(WriteSignal<i32>);
|
||||
///
|
||||
/// #[component]
|
||||
/// pub fn Provider(cx: Scope) -> Element {
|
||||
/// pub fn Provider(cx: Scope) -> impl IntoView {
|
||||
/// let (value, set_value) = create_signal(cx, 0);
|
||||
///
|
||||
/// // the newtype pattern isn't *necessary* here but is a good practice
|
||||
|
@ -99,7 +99,7 @@ where
|
|||
/// }
|
||||
///
|
||||
/// #[component]
|
||||
/// pub fn Consumer(cx: Scope) -> Element {
|
||||
/// pub fn Consumer(cx: Scope) -> impl IntoView {
|
||||
/// // consume the provided context of type `ValueSetter` using `use_context`
|
||||
/// // this traverses up the tree of `Scope`s and gets the nearest provided `ValueSetter`
|
||||
/// let set_value = use_context::<ValueSetter>(cx).unwrap().0;
|
||||
|
|
|
@ -27,11 +27,11 @@ impl PartialEq for SharedContext {
|
|||
|
||||
impl Eq for SharedContext {}
|
||||
|
||||
#[allow(clippy::derivable_impls)]
|
||||
impl Default for SharedContext {
|
||||
|
||||
fn default() -> Self {
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "hydrate")] {
|
||||
if #[cfg(all(feature = "hydrate", target_arch = "wasm32"))] {
|
||||
let pending_resources = js_sys::Reflect::get(
|
||||
&web_sys::window().unwrap(),
|
||||
&wasm_bindgen::JsValue::from_str("__LEPTOS_PENDING_RESOURCES"),
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
//! use leptos_meta::*;
|
||||
//!
|
||||
//! #[component]
|
||||
//! fn MyApp(cx: Scope) -> Element {
|
||||
//! fn MyApp(cx: Scope) -> impl IntoView {
|
||||
//! let (name, set_name) = create_signal(cx, "Alice".to_string());
|
||||
//!
|
||||
//! view! { cx,
|
||||
|
@ -58,6 +58,15 @@ pub struct MetaContext {
|
|||
pub(crate) meta_tags: MetaTagsContext
|
||||
}
|
||||
|
||||
/// Provides a [MetaContext], if there is not already one provided. This ensures that you can provide it
|
||||
/// at the highest possible level, without overwriting a [MetaContext] that has already been provided
|
||||
/// (for example, by a server-rendering integration.)
|
||||
pub fn provide_meta_context(cx: Scope) {
|
||||
if use_context::<MetaContext>(cx).is_none() {
|
||||
provide_context(cx, MetaContext::new());
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the current [MetaContext].
|
||||
///
|
||||
/// If there is no [MetaContext] in this [Scope](leptos::Scope) or any parent scope, this will
|
||||
|
@ -107,7 +116,10 @@ impl MetaContext {
|
|||
/// };
|
||||
///
|
||||
/// // `app` contains only the body content w/ hydration stuff, not the meta tags
|
||||
/// assert_eq!(app, r#"<main data-hk="0-0"><!--#--><!--/--><!--#--><!--/--><p>Some text</p></main>"#);
|
||||
/// 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-3c></leptos-unit><p id=\"_0-4\">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 rel="stylesheet" href="/style.css">"#)
|
||||
/// });
|
||||
|
|
|
@ -90,8 +90,8 @@ pub struct MetaProps {
|
|||
/// use leptos_meta::*;
|
||||
///
|
||||
/// #[component]
|
||||
/// fn MyApp(cx: Scope) -> Element {
|
||||
/// provide_context(cx, MetaContext::new());
|
||||
/// fn MyApp(cx: Scope) -> impl IntoView {
|
||||
/// provide_meta_context(cx);
|
||||
///
|
||||
/// view! { cx,
|
||||
/// <main>
|
||||
|
@ -121,7 +121,7 @@ pub fn Meta(cx: Scope, props: MetaProps) {
|
|||
let meta_tags = meta.meta_tags;
|
||||
let id = meta_tags.get_next_id();
|
||||
|
||||
let el = if let Ok(Some(el)) = document().query_selector(&format!("[data-leptos-meta={}]", id.0)) {
|
||||
let el = if let Ok(Some(el)) = document().query_selector(&format!("[data-leptos-meta='{}']", id.0)) {
|
||||
el
|
||||
} else {
|
||||
document().create_element("meta").unwrap_throw()
|
||||
|
|
|
@ -37,8 +37,8 @@ pub struct StylesheetProps {
|
|||
/// use leptos_meta::*;
|
||||
///
|
||||
/// #[component]
|
||||
/// fn MyApp(cx: Scope) -> Element {
|
||||
/// provide_context(cx, MetaContext::new());
|
||||
/// fn MyApp(cx: Scope) -> impl IntoView {
|
||||
/// provide_meta_context(cx);
|
||||
///
|
||||
/// view! { cx,
|
||||
/// <main>
|
||||
|
|
|
@ -66,7 +66,7 @@ pub struct TitleProps {
|
|||
/// use leptos_meta::*;
|
||||
///
|
||||
/// #[component]
|
||||
/// fn MyApp(cx: Scope) -> Element {
|
||||
/// fn MyApp(cx: Scope) -> impl IntoView {
|
||||
/// provide_context(cx, MetaContext::new());
|
||||
/// let formatter = |text| format!("{text} — Leptos Online");
|
||||
///
|
||||
|
@ -79,7 +79,7 @@ pub struct TitleProps {
|
|||
/// }
|
||||
///
|
||||
/// #[component]
|
||||
/// fn PageA(cx: Scope) -> Element {
|
||||
/// fn PageA(cx: Scope) -> impl IntoView {
|
||||
/// view! { cx,
|
||||
/// <main>
|
||||
/// <Title text="Page A"/> // sets title to "Page A — Leptos Online"
|
||||
|
@ -88,7 +88,7 @@ pub struct TitleProps {
|
|||
/// }
|
||||
///
|
||||
/// #[component]
|
||||
/// fn PageB(cx: Scope) -> Element {
|
||||
/// fn PageB(cx: Scope) -> impl IntoView {
|
||||
/// view! { cx,
|
||||
/// <main>
|
||||
/// <Title text="Page B"/> // sets title to "Page B — Leptos Online"
|
||||
|
|
|
@ -39,7 +39,8 @@
|
|||
//! use leptos::*;
|
||||
//! use leptos_router::*;
|
||||
//!
|
||||
//! pub fn router_example(cx: Scope) -> View {
|
||||
//! #[component]
|
||||
//! pub fn RouterExample(cx: Scope) -> impl IntoView {
|
||||
//! view! {
|
||||
//! cx,
|
||||
//! <div id="root">
|
||||
|
@ -60,23 +61,23 @@
|
|||
//! // our root route: the contact list is always shown
|
||||
//! <Route
|
||||
//! path=""
|
||||
//! element=move |cx| view! { cx, <ContactList/> }
|
||||
//! view=move |cx| view! { cx, <ContactList/> }
|
||||
//! >
|
||||
//! // users like /gbj or /bob
|
||||
//! <Route
|
||||
//! path=":id"
|
||||
//! element=move |cx| view! { cx, <Contact/> }
|
||||
//! view=move |cx| view! { cx, <Contact/> }
|
||||
//! />
|
||||
//! // a fallback if the /:id segment is missing from the URL
|
||||
//! <Route
|
||||
//! path=""
|
||||
//! element=move |_| view! { cx, <p class="contact">"Select a contact."</p> }
|
||||
//! view=move |_| view! { cx, <p class="contact">"Select a contact."</p> }
|
||||
//! />
|
||||
//! </Route>
|
||||
//! // LR will automatically use this for /about, not the /:id match above
|
||||
//! <Route
|
||||
//! path="about"
|
||||
//! element=move |cx| view! { cx, <About/> }
|
||||
//! view=move |cx| view! { cx, <About/> }
|
||||
//! />
|
||||
//! </Routes>
|
||||
//! </main>
|
||||
|
@ -100,7 +101,7 @@
|
|||
//! }
|
||||
//!
|
||||
//! #[component]
|
||||
//! fn ContactList(cx: Scope) -> View {
|
||||
//! fn ContactList(cx: Scope) -> impl IntoView {
|
||||
//! // loads the contact list data once; doesn't reload when nested routes change
|
||||
//! let contacts = create_resource(cx, || (), |_| contact_list_data());
|
||||
//! view! {
|
||||
|
@ -118,7 +119,7 @@
|
|||
//! }
|
||||
//!
|
||||
//! #[component]
|
||||
//! fn Contact(cx: Scope) -> View {
|
||||
//! fn Contact(cx: Scope) -> impl IntoView {
|
||||
//! let params = use_params_map(cx);
|
||||
//! let data = create_resource(
|
||||
//! cx,
|
||||
|
@ -129,7 +130,7 @@
|
|||
//! }
|
||||
//!
|
||||
//! #[component]
|
||||
//! fn About(cx: Scope) -> View {
|
||||
//! fn About(cx: Scope) -> impl IntoView {
|
||||
//! todo!()
|
||||
//! }
|
||||
//!
|
||||
|
|
Loading…
Reference in a new issue