diff --git a/integrations/actix/src/lib.rs b/integrations/actix/src/lib.rs index 393fece88..f432faa7a 100644 --- a/integrations/actix/src/lib.rs +++ b/integrations/actix/src/lib.rs @@ -32,11 +32,19 @@ pub struct ResponseParts { impl ResponseParts { /// Insert a header, overwriting any previous value with the same key - pub fn insert_header(&mut self, key: header::HeaderName, value: header::HeaderValue) { + pub fn insert_header( + &mut self, + key: header::HeaderName, + value: header::HeaderValue, + ) { self.headers.insert(key, value); } /// Append a header, leaving any header with the same key intact - pub fn append_header(&mut self, key: header::HeaderName, value: header::HeaderValue) { + pub fn append_header( + &mut self, + key: header::HeaderName, + value: header::HeaderValue, + ) { self.headers.append(key, value); } } @@ -60,13 +68,21 @@ impl ResponseOptions { res_parts.status = Some(status); } /// Insert a header, overwriting any previous value with the same key - pub fn insert_header(&self, key: header::HeaderName, value: header::HeaderValue) { + pub fn insert_header( + &self, + key: header::HeaderName, + value: header::HeaderValue, + ) { let mut writeable = self.0.write(); let res_parts = &mut *writeable; res_parts.headers.insert(key, value); } /// Append a header, leaving any header with the same key intact - pub fn append_header(&self, key: header::HeaderName, value: header::HeaderValue) { + pub fn append_header( + &self, + key: header::HeaderName, + value: header::HeaderValue, + ) { let mut writeable = self.0.write(); let res_parts = &mut *writeable; res_parts.headers.append(key, value); @@ -81,7 +97,8 @@ pub fn redirect(cx: leptos::Scope, path: &str) { response_options.set_status(StatusCode::FOUND); response_options.insert_header( header::LOCATION, - header::HeaderValue::from_str(path).expect("Failed to create HeaderValue"), + header::HeaderValue::from_str(path) + .expect("Failed to create HeaderValue"), ); } @@ -173,7 +190,8 @@ pub fn handle_server_fns_with_context( match server_fn(cx, body).await { Ok(serialized) => { - let res_options = use_context::(cx).unwrap(); + let res_options = + use_context::(cx).unwrap(); // clean up the scope, which we only needed to run the server fn disposer.dispose(); @@ -183,7 +201,8 @@ pub fn handle_server_fns_with_context( let mut res_parts = res_options.0.write(); if accept_header == Some("application/json") - || accept_header == Some("application/x-www-form-urlencoded") + || accept_header + == Some("application/x-www-form-urlencoded") || accept_header == Some("application/cbor") { res = HttpResponse::Ok(); @@ -221,7 +240,9 @@ pub fn handle_server_fns_with_context( res.body(Bytes::from(data)) } Payload::Url(data) => { - res.content_type("application/x-www-form-urlencoded"); + res.content_type( + "application/x-www-form-urlencoded", + ); res.body(data) } Payload::Json(data) => { @@ -230,13 +251,15 @@ pub fn handle_server_fns_with_context( } } } - Err(e) => HttpResponse::InternalServerError().body(e.to_string()), + Err(e) => HttpResponse::InternalServerError() + .body(e.to_string()), } } else { HttpResponse::BadRequest().body(format!( "Could not find a server function at the route {:?}. \ - \n\nIt's likely that you need to call ServerFn::register() on the \ - server function type, somewhere in your `main` function.", + \n\nIt's likely that you need to call \ + ServerFn::register() on the server function type, \ + somewhere in your `main` function.", req.path() )) } @@ -256,13 +279,13 @@ pub fn handle_server_fns_with_context( /// /// This can then be set up at an appropriate route in your application: /// ``` -/// use actix_web::{HttpServer, App}; +/// use actix_web::{App, HttpServer}; /// use leptos::*; -/// use std::{env,net::SocketAddr}; +/// use std::{env, net::SocketAddr}; /// /// #[component] /// fn MyApp(cx: Scope) -> impl IntoView { -/// view! { cx,
"Hello, world!"
} +/// view! { cx,
"Hello, world!"
} /// } /// /// # if false { // don't actually try to run a server in a doctest... @@ -272,11 +295,17 @@ pub fn handle_server_fns_with_context( /// let addr = conf.leptos_options.site_addr.clone(); /// HttpServer::new(move || { /// let leptos_options = &conf.leptos_options; -/// +/// /// App::new() /// // {tail:.*} passes the remainder of the URL as the route /// // the actual routing will be handled by `leptos_router` -/// .route("/{tail:.*}", leptos_actix::render_app_to_stream(leptos_options.to_owned(), |cx| view! { cx, })) +/// .route( +/// "/{tail:.*}", +/// leptos_actix::render_app_to_stream( +/// leptos_options.to_owned(), +/// |cx| view! { cx, }, +/// ), +/// ) /// }) /// .bind(&addr)? /// .run() @@ -353,14 +382,14 @@ where /// /// This can then be set up at an appropriate route in your application: /// ``` -/// use actix_web::{HttpServer, App}; +/// use actix_web::{App, HttpServer}; /// use leptos::*; -/// use std::{env,net::SocketAddr}; /// use leptos_actix::DataResponse; +/// use std::{env, net::SocketAddr}; /// /// #[component] /// fn MyApp(cx: Scope, data: &'static str) -> impl IntoView { -/// view! { cx,
"Hello, world!"
} +/// view! { cx,
"Hello, world!"
} /// } /// /// # if false { // don't actually try to run a server in a doctest... @@ -370,14 +399,21 @@ where /// let addr = conf.leptos_options.site_addr.clone(); /// HttpServer::new(move || { /// let leptos_options = &conf.leptos_options; -/// +/// /// App::new() /// // {tail:.*} passes the remainder of the URL as the route /// // the actual routing will be handled by `leptos_router` -/// .route("/{tail:.*}", leptos_actix::render_preloaded_data_app( -/// leptos_options.to_owned(), -/// |req| async move { Ok(DataResponse::Data("async func that can preload data")) }, -/// |cx, data| view! { cx, }) +/// .route( +/// "/{tail:.*}", +/// leptos_actix::render_preloaded_data_app( +/// leptos_options.to_owned(), +/// |req| async move { +/// Ok(DataResponse::Data( +/// "async func that can preload data", +/// )) +/// }, +/// |cx, data| view! { cx, }, +/// ), /// ) /// }) /// .bind(&addr)? @@ -430,7 +466,11 @@ where }) } -fn provide_contexts(cx: leptos::Scope, req: &HttpRequest, res_options: ResponseOptions) { +fn provide_contexts( + cx: leptos::Scope, + req: &HttpRequest, + res_options: ResponseOptions, +) { let path = leptos_corrected_path(req); let integration = ServerIntegration { path }; @@ -457,25 +497,27 @@ async fn stream_app( res_options: ResponseOptions, additional_context: impl Fn(leptos::Scope) + 'static + Clone + Send, ) -> HttpResponse { - let (stream, runtime, scope) = render_to_stream_with_prefix_undisposed_with_context( - app, - move |cx| { - let meta = use_context::(cx); - let head = meta - .as_ref() - .map(|meta| meta.dehydrate()) - .unwrap_or_default(); - let body_meta = meta - .as_ref() - .and_then(|meta| meta.body.as_string()) - .unwrap_or_default(); - format!("{head}").into() - }, - additional_context, - ); + let (stream, runtime, scope) = + render_to_stream_with_prefix_undisposed_with_context( + app, + move |cx| { + let meta = use_context::(cx); + let head = meta + .as_ref() + .map(|meta| meta.dehydrate()) + .unwrap_or_default(); + let body_meta = meta + .as_ref() + .and_then(|meta| meta.body.as_string()) + .unwrap_or_default(); + format!("{head}").into() + }, + additional_context, + ); let cx = leptos::Scope { runtime, id: scope }; - let (head, tail) = html_parts(options, use_context::(cx).as_ref()); + let (head, tail) = + html_parts(options, use_context::(cx).as_ref()); let mut stream = Box::pin( futures::stream::once(async move { head.clone() }) @@ -493,11 +535,13 @@ async fn stream_app( let res_options = res_options.0.read(); - let (status, mut headers) = (res_options.status, res_options.headers.clone()); + let (status, mut headers) = + (res_options.status, res_options.headers.clone()); let status = status.unwrap_or_default(); let complete_stream = - futures::stream::iter([first_chunk.unwrap(), second_chunk.unwrap()]).chain(stream); + futures::stream::iter([first_chunk.unwrap(), second_chunk.unwrap()]) + .chain(stream); let mut res = HttpResponse::Ok() .content_type("text/html") .streaming(complete_stream); @@ -514,7 +558,10 @@ async fn stream_app( res } -fn html_parts(options: &LeptosOptions, meta_context: Option<&MetaContext>) -> (String, String) { +fn html_parts( + options: &LeptosOptions, + meta_context: Option<&MetaContext>, +) -> (String, String) { // Because wasm-pack adds _bg to the end of the WASM filename, and we want to mantain compatibility with it's default options // we add _bg to the wasm files if cargo-leptos doesn't set the env var LEPTOS_OUTPUT_NAME // Otherwise we need to add _bg because wasm_pack always does. This is not the same as options.output_name, which is set regardless @@ -578,7 +625,9 @@ fn html_parts(options: &LeptosOptions, meta_context: Option<&MetaContext>) -> (S /// Generates a list of all routes defined in Leptos's Router in your app. We can then use this to automatically /// create routes in Actix's App without having to use wildcard matching or fallbacks. Takes in your root app Element /// as an argument so it can walk you app tree. This version is tailored to generated Actix compatible paths. -pub fn generate_route_list(app_fn: impl FnOnce(leptos::Scope) -> IV + 'static) -> Vec +pub fn generate_route_list( + app_fn: impl FnOnce(leptos::Scope) -> IV + 'static, +) -> Vec where IV: IntoView + 'static, { @@ -658,7 +707,12 @@ pub trait LeptosRoutes { /// to those paths to Leptos's renderer. impl LeptosRoutes for actix_web::App where - T: ServiceFactory, + T: ServiceFactory< + ServiceRequest, + Config = (), + Error = Error, + InitError = (), + >, { fn leptos_routes( self, @@ -671,7 +725,10 @@ where { let mut router = self; for path in paths.iter() { - router = router.route(path, render_app_to_stream(options.clone(), app_fn.clone())); + router = router.route( + path, + render_app_to_stream(options.clone(), app_fn.clone()), + ); } router } @@ -693,7 +750,11 @@ where for path in paths.iter() { router = router.route( path, - render_preloaded_data_app(options.clone(), data_fn.clone(), app_fn.clone()), + render_preloaded_data_app( + options.clone(), + data_fn.clone(), + app_fn.clone(), + ), ); } router diff --git a/integrations/axum/src/lib.rs b/integrations/axum/src/lib.rs index c4b81fbd6..c5eafdd91 100644 --- a/integrations/axum/src/lib.rs +++ b/integrations/axum/src/lib.rs @@ -9,7 +9,10 @@ use axum::{ body::{Body, Bytes, Full, StreamBody}, extract::Path, - http::{header::HeaderName, header::HeaderValue, HeaderMap, Request, StatusCode}, + http::{ + header::{HeaderName, HeaderValue}, + HeaderMap, Request, StatusCode, + }, response::IntoResponse, routing::get, }; @@ -21,7 +24,7 @@ use leptos_meta::MetaContext; use leptos_router::*; use parking_lot::RwLock; use std::{io, pin::Pin, sync::Arc}; -use tokio::{task::spawn_blocking, task::LocalSet}; +use tokio::task::{spawn_blocking, LocalSet}; /// A struct to hold the parts of the incoming Request. Since `http::Request` isn't cloneable, we're forced /// to construct this for Leptos to use in Axum @@ -92,7 +95,8 @@ pub fn redirect(cx: leptos::Scope, path: &str) { response_options.set_status(StatusCode::FOUND); response_options.insert_header( header::LOCATION, - header::HeaderValue::from_str(path).expect("Failed to create HeaderValue"), + header::HeaderValue::from_str(path) + .expect("Failed to create HeaderValue"), ); } @@ -118,8 +122,8 @@ pub async fn generate_request_parts(req: Request) -> RequestParts { /// /// ``` /// use axum::{handler::Handler, routing::post, Router}; -/// use std::net::SocketAddr; /// use leptos::*; +/// use std::net::SocketAddr; /// /// # if false { // don't actually try to run a server in a doctest... /// #[tokio::main] @@ -128,7 +132,7 @@ pub async fn generate_request_parts(req: Request) -> RequestParts { /// /// // build our application with a route /// let app = Router::new() -/// .route("/api/*fn_name", post(leptos_axum::handle_server_fns)); +/// .route("/api/*fn_name", post(leptos_axum::handle_server_fns)); /// /// // run our app with hyper /// // `axum::Server` is a re-export of `hyper::Server` @@ -196,9 +200,12 @@ async fn handle_server_fns_inner( .expect("couldn't spawn runtime") .block_on({ async move { - let res = if let Some(server_fn) = server_fn_by_path(fn_name.as_str()) { + let res = if let Some(server_fn) = + server_fn_by_path(fn_name.as_str()) + { let runtime = create_runtime(); - let (cx, disposer) = raw_scope_and_disposer(runtime); + let (cx, disposer) = + raw_scope_and_disposer(runtime); additional_context(cx); @@ -211,34 +218,43 @@ async fn handle_server_fns_inner( match server_fn(cx, &req_parts.body).await { Ok(serialized) => { // If ResponseOptions are set, add the headers and status to the request - let res_options = use_context::(cx); + let res_options = + use_context::(cx); // clean up the scope, which we only needed to run the server fn disposer.dispose(); runtime.dispose(); // if this is Accept: application/json then send a serialized JSON response - let accept_header = - headers.get("Accept").and_then(|value| value.to_str().ok()); + let accept_header = headers + .get("Accept") + .and_then(|value| value.to_str().ok()); let mut res = Response::builder(); // Add headers from ResponseParts if they exist. These should be added as long // as the server function returns an OK response - let res_options_outer = res_options.unwrap().0; - let res_options_inner = res_options_outer.read(); + let res_options_outer = + res_options.unwrap().0; + let res_options_inner = + res_options_outer.read(); let (status, mut res_headers) = ( res_options_inner.status, res_options_inner.headers.clone(), ); - if let Some(header_ref) = res.headers_mut() { - header_ref.extend(res_headers.drain()); + if let Some(header_ref) = res.headers_mut() + { + header_ref.extend(res_headers.drain()); }; if accept_header == Some("application/json") || accept_header - == Some("application/x-www-form-urlencoded") - || accept_header == Some("application/cbor") + == Some( + "application/\ + x-www-form-urlencoded", + ) + || accept_header + == Some("application/cbor") { res = res.status(StatusCode::OK); } @@ -246,7 +262,9 @@ async fn handle_server_fns_inner( else { let referer = headers .get("Referer") - .and_then(|value| value.to_str().ok()) + .and_then(|value| { + value.to_str().ok() + }) .unwrap_or("/"); res = res @@ -260,16 +278,23 @@ async fn handle_server_fns_inner( }; match serialized { Payload::Binary(data) => res - .header("Content-Type", "application/cbor") + .header( + "Content-Type", + "application/cbor", + ) .body(Full::from(data)), Payload::Url(data) => res .header( "Content-Type", - "application/x-www-form-urlencoded", + "application/\ + x-www-form-urlencoded", ) .body(Full::from(data)), Payload::Json(data) => res - .header("Content-Type", "application/json") + .header( + "Content-Type", + "application/json", + ) .body(Full::from(data)), } } @@ -280,11 +305,13 @@ async fn handle_server_fns_inner( } else { Response::builder() .status(StatusCode::BAD_REQUEST) - .body(Full::from( - format!("Could not find a server function at the route {fn_name}. \ - \n\nIt's likely that you need to call ServerFn::register() on the \ - server function type, somewhere in your `main` function." ) - )) + .body(Full::from(format!( + "Could not find a server function at the \ + route {fn_name}. \n\nIt's likely that \ + you need to call ServerFn::register() on \ + the server function type, somewhere in \ + your `main` function." + ))) } .expect("could not build Response"); @@ -297,7 +324,8 @@ async fn handle_server_fns_inner( rx.await.unwrap() } -pub type PinnedHtmlStream = Pin> + Send>>; +pub type PinnedHtmlStream = + Pin> + Send>>; /// Returns an Axum [Handler](axum::handler::Handler) that listens for a `GET` request and tries /// to route it using [leptos_router], serving an HTML stream of your application. @@ -310,28 +338,28 @@ pub type PinnedHtmlStream = Pin> + Send> /// /// This can then be set up at an appropriate route in your application: /// ``` -/// use axum::handler::Handler; -/// use axum::Router; -/// use std::{net::SocketAddr, env}; +/// use axum::{handler::Handler, Router}; /// use leptos::*; /// use leptos_config::get_configuration; +/// use std::{env, net::SocketAddr}; /// /// #[component] /// fn MyApp(cx: Scope) -> impl IntoView { -/// view! { cx,
"Hello, world!"
} +/// view! { cx,
"Hello, world!"
} /// } /// /// # if false { // don't actually try to run a server in a doctest... /// #[tokio::main] /// async fn main() { -/// /// let conf = get_configuration(Some("Cargo.toml")).await.unwrap(); /// let leptos_options = conf.leptos_options; /// let addr = leptos_options.site_addr.clone(); -/// +/// /// // build our application with a route -/// let app = Router::new() -/// .fallback(leptos_axum::render_app_to_stream(leptos_options, |cx| view! { cx, })); +/// let app = Router::new().fallback(leptos_axum::render_app_to_stream( +/// leptos_options, +/// |cx| view! { cx, }, +/// )); /// /// // run our app with hyper /// // `axum::Server` is a re-export of `hyper::Server` @@ -354,8 +382,13 @@ pub fn render_app_to_stream( app_fn: impl Fn(leptos::Scope) -> IV + Clone + Send + 'static, ) -> impl Fn( Request, -) -> Pin>> + Send + 'static>> - + Clone +) -> Pin< + Box< + dyn Future>> + + Send + + 'static, + >, +> + Clone + Send + 'static where @@ -395,8 +428,13 @@ pub fn render_app_to_stream_with_context( app_fn: impl Fn(leptos::Scope) -> IV + Clone + Send + 'static, ) -> impl Fn( Request, -) -> Pin>> + Send + 'static>> - + Clone +) -> Pin< + Box< + dyn Future>> + + Send + + 'static, + >, +> + Clone + Send + 'static where @@ -502,13 +540,16 @@ where // Extract the resources now that they've been rendered let res_options = res_options3.0.read(); - let complete_stream = - futures::stream::iter([first_chunk.unwrap(), second_chunk.unwrap()]) - .chain(stream); + let complete_stream = futures::stream::iter([ + first_chunk.unwrap(), + second_chunk.unwrap(), + ]) + .chain(stream); - let mut res = Response::new(StreamBody::new( - Box::pin(complete_stream) as PinnedHtmlStream - )); + let mut res = Response::new(StreamBody::new(Box::pin( + complete_stream, + ) + as PinnedHtmlStream)); if let Some(status) = res_options.status { *res.status_mut() = status @@ -522,7 +563,10 @@ where } } -fn html_parts(options: &LeptosOptions, meta: Option<&MetaContext>) -> (String, &'static str) { +fn html_parts( + options: &LeptosOptions, + meta: Option<&MetaContext>, +) -> (String, &'static str) { let pkg_path = &options.site_pkg_dir; let output_name = &options.output_name; @@ -564,7 +608,8 @@ fn html_parts(options: &LeptosOptions, meta: Option<&MetaContext>) -> (String, & false => "".to_string(), }; - let html_metadata = meta.and_then(|mc| mc.html.as_string()).unwrap_or_default(); + let html_metadata = + meta.and_then(|mc| mc.html.as_string()).unwrap_or_default(); let head = format!( r#" @@ -584,7 +629,9 @@ fn html_parts(options: &LeptosOptions, meta: Option<&MetaContext>) -> (String, & /// Generates a list of all routes defined in Leptos's Router in your app. We can then use this to automatically /// create routes in Axum's Router without having to use wildcard matching or fallbacks. Takes in your root app Element /// as an argument so it can walk you app tree. This version is tailored to generate Axum compatible paths. -pub async fn generate_route_list(app_fn: impl FnOnce(Scope) -> IV + 'static) -> Vec +pub async fn generate_route_list( + app_fn: impl FnOnce(Scope) -> IV + 'static, +) -> Vec where IV: IntoView + 'static, { @@ -645,7 +692,11 @@ pub trait LeptosRoutes { where IV: IntoView + 'static; - fn leptos_routes_with_handler(self, paths: Vec, handler: H) -> Self + fn leptos_routes_with_handler( + self, + paths: Vec, + handler: H, + ) -> Self where H: axum::handler::Handler, T: 'static; @@ -696,7 +747,11 @@ impl LeptosRoutes for axum::Router { router } - fn leptos_routes_with_handler(self, paths: Vec, handler: H) -> Self + fn leptos_routes_with_handler( + self, + paths: Vec, + handler: H, + ) -> Self where H: axum::handler::Handler, T: 'static, diff --git a/leptos/src/lib.rs b/leptos/src/lib.rs index ca68cfee0..3019c43a4 100644 --- a/leptos/src/lib.rs +++ b/leptos/src/lib.rs @@ -132,7 +132,7 @@ //! //! #[component] //! fn SimpleCounter(cx: Scope, initial_value: i32) -> impl IntoView { -//! todo!() +//! todo!() //! } //! //! pub fn main() { @@ -142,14 +142,14 @@ //! ``` pub use leptos_config::*; -pub use leptos_dom; -pub use leptos_dom::wasm_bindgen::{JsCast, UnwrapThrowExt}; -pub use leptos_dom::*; +pub use leptos_dom::{ + self, + wasm_bindgen::{JsCast, UnwrapThrowExt}, + *, +}; pub use leptos_macro::*; pub use leptos_reactive::*; -pub use leptos_server; -pub use leptos_server::*; - +pub use leptos_server::{self, *}; pub use tracing; pub use typed_builder; mod error_boundary; @@ -161,9 +161,8 @@ pub use show::*; mod suspense; pub use suspense::*; mod transition; -pub use transition::*; - pub use leptos_dom::debug_warn; +pub use transition::*; extern crate self as leptos; @@ -188,15 +187,14 @@ pub type ChildrenFnMut = Box Fragment>; /// /// #[component] /// pub fn MyHeading( -/// cx: Scope, -/// text: String, -/// #[prop(optional, into)] -/// class: Option +/// cx: Scope, +/// text: String, +/// #[prop(optional, into)] class: Option, /// ) -> impl IntoView { -/// view!{ -/// cx, -///

{text}

-/// } +/// view! { +/// cx, +///

{text}

+/// } /// } /// ``` pub type AttributeValue = Box; diff --git a/leptos/src/suspense.rs b/leptos/src/suspense.rs index f41ae5895..0c6e1495c 100644 --- a/leptos/src/suspense.rs +++ b/leptos/src/suspense.rs @@ -1,6 +1,5 @@ use cfg_if::cfg_if; -use leptos_dom::HydrationCtx; -use leptos_dom::{DynChild, Fragment, IntoView}; +use leptos_dom::{DynChild, Fragment, HydrationCtx, IntoView}; use leptos_macro::component; use leptos_reactive::{provide_context, Scope, SuspenseContext}; use std::rc::Rc; diff --git a/leptos/src/transition.rs b/leptos/src/transition.rs index 1b745ade8..c6761494d 100644 --- a/leptos/src/transition.rs +++ b/leptos/src/transition.rs @@ -19,7 +19,9 @@ use std::{cell::RefCell, rc::Rc}; /// # use leptos::*; /// # if false { /// # run_scope(create_runtime(), |cx| { -/// async fn fetch_cats(how_many: u32) -> Option> { Some(vec![]) } +/// async fn fetch_cats(how_many: u32) -> Option> { +/// Some(vec![]) +/// } /// /// let (cat_count, set_cat_count) = create_signal::(cx, 1); /// let (pending, set_pending) = create_signal(cx, false); diff --git a/leptos/tests/ssr.rs b/leptos/tests/ssr.rs index 58efe404d..08fb1a4a4 100644 --- a/leptos/tests/ssr.rs +++ b/leptos/tests/ssr.rs @@ -16,7 +16,11 @@ fn simple_ssr_test() { assert_eq!( rendered.into_view(cx).render_to_string(cx), - "
Value: 0!
" + "
Value: \ + 0!
" ); }); } @@ -50,7 +54,21 @@ fn ssr_test_with_components() { assert_eq!( rendered.into_view(cx).render_to_string(cx), - "
Value: 1!
Value: 2!
" + "
Value: \ + 1!
Value: \ + 2!
" ); }); } @@ -84,7 +102,22 @@ fn ssr_test_with_snake_case_components() { assert_eq!( rendered.into_view(cx).render_to_string(cx), - "
Value: 1!
Value: 2!
" + "
Value: \ + 1!
Value: \ + 2!
" ); }); } @@ -125,7 +158,8 @@ fn ssr_with_styles() { assert_eq!( rendered.into_view(cx).render_to_string(cx), - "
" + "
" ); }); } diff --git a/leptos_config/src/errors.rs b/leptos_config/src/errors.rs index 278b26d9f..4c13d2d8c 100644 --- a/leptos_config/src/errors.rs +++ b/leptos_config/src/errors.rs @@ -1,5 +1,4 @@ use std::{net::AddrParseError, num::ParseIntError}; - use thiserror::Error; #[derive(Debug, Error, Clone)] diff --git a/leptos_config/src/lib.rs b/leptos_config/src/lib.rs index fbe79c236..b9b230552 100644 --- a/leptos_config/src/lib.rs +++ b/leptos_config/src/lib.rs @@ -5,9 +5,7 @@ pub mod errors; use crate::errors::LeptosConfigError; use config::{Config, File, FileFormat}; use regex::Regex; -use std::convert::TryFrom; -use std::fs; -use std::{env::VarError, net::SocketAddr, str::FromStr}; +use std::{convert::TryFrom, env::VarError, fs, net::SocketAddr, str::FromStr}; use typed_builder::TypedBuilder; /// A Struct to allow us to parse LeptosOptions from the file. Not really needed, most interactions should @@ -53,18 +51,26 @@ pub struct LeptosOptions { impl LeptosOptions { fn try_from_env() -> Result { Ok(LeptosOptions { - output_name: std::env::var("LEPTOS_OUTPUT_NAME") - .map_err(|e| LeptosConfigError::EnvVarError(format!("LEPTOS_OUTPUT_NAME: {e}")))?, + output_name: std::env::var("LEPTOS_OUTPUT_NAME").map_err(|e| { + LeptosConfigError::EnvVarError(format!( + "LEPTOS_OUTPUT_NAME: {e}" + )) + })?, site_root: env_w_default("LEPTOS_SITE_ROOT", "target/site")?, site_pkg_dir: env_w_default("LEPTOS_SITE_PKG_DIR", "pkg")?, env: Env::default(), - site_addr: env_w_default("LEPTOS_SITE_ADDR", "127.0.0.1:3000")?.parse()?, - reload_port: env_w_default("LEPTOS_RELOAD_PORT", "3001")?.parse()?, + site_addr: env_w_default("LEPTOS_SITE_ADDR", "127.0.0.1:3000")? + .parse()?, + reload_port: env_w_default("LEPTOS_RELOAD_PORT", "3001")? + .parse()?, }) } } -fn env_w_default(key: &str, default: &str) -> Result { +fn env_w_default( + key: &str, + default: &str, +) -> Result { match std::env::var(key) { Ok(val) => Ok(val), Err(VarError::NotPresent) => Ok(default.to_string()), @@ -93,7 +99,8 @@ fn from_str(input: &str) -> Result { "dev" | "development" => Ok(Env::DEV), "prod" | "production" => Ok(Env::PROD), _ => Err(format!( - "{input} is not a supported environment. Use either `dev` or `production`.", + "{input} is not a supported environment. Use either `dev` or \ + `production`.", )), } } @@ -132,11 +139,15 @@ impl TryFrom for Env { /// you'll need to set the options as environment variables or rely on the defaults. This is the preferred /// approach for cargo-leptos. If Some("./Cargo.toml") is provided, Leptos will read in the settings itself. This /// option currently does not allow dashes in file or foldernames, as all dashes become underscores -pub async fn get_configuration(path: Option<&str>) -> Result { +pub async fn get_configuration( + path: Option<&str>, +) -> Result { if let Some(path) = path { - let text = fs::read_to_string(path).map_err(|_| LeptosConfigError::ConfigNotFound)?; + let text = fs::read_to_string(path) + .map_err(|_| LeptosConfigError::ConfigNotFound)?; - let re: Regex = Regex::new(r#"(?m)^\[package.metadata.leptos\]"#).unwrap(); + let re: Regex = + Regex::new(r#"(?m)^\[package.metadata.leptos\]"#).unwrap(); let start = match re.find(&text) { Some(found) => found.start(), None => return Err(LeptosConfigError::ConfigSectionNotFound), @@ -154,7 +165,9 @@ pub async fn get_configuration(path: Option<&str>) -> Result) -> fmt::Result { - match self { - Self::Unit(u) => u.fmt(f), - Self::DynChild(dc) => dc.fmt(f), - Self::Each(e) => e.fmt(f), + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Unit(u) => u.fmt(f), + Self::DynChild(dc) => dc.fmt(f), + Self::Each(e) => e.fmt(f), + } } - } } /// Custom leptos component. #[derive(Clone, PartialEq, Eq)] pub struct ComponentRepr { - #[cfg(all(target_arch = "wasm32", feature = "web"))] - pub(crate) document_fragment: web_sys::DocumentFragment, - #[cfg(all(target_arch = "wasm32", feature = "web"))] - mounted: Rc>, - #[cfg(debug_assertions)] - pub(crate) name: Cow<'static, str>, - #[cfg(debug_assertions)] - _opening: Comment, - /// The children of the component. - pub children: Vec, - closing: Comment, - #[cfg(not(all(target_arch = "wasm32", feature = "web")))] - pub(crate) id: HydrationKey, + #[cfg(all(target_arch = "wasm32", feature = "web"))] + pub(crate) document_fragment: web_sys::DocumentFragment, + #[cfg(all(target_arch = "wasm32", feature = "web"))] + mounted: Rc>, + #[cfg(debug_assertions)] + pub(crate) name: Cow<'static, str>, + #[cfg(debug_assertions)] + _opening: Comment, + /// The children of the component. + pub children: Vec, + closing: Comment, + #[cfg(not(all(target_arch = "wasm32", feature = "web")))] + pub(crate) id: HydrationKey, } impl fmt::Debug for ComponentRepr { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - use fmt::Write; + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + use fmt::Write; - if self.children.is_empty() { - #[cfg(debug_assertions)] - return write!(f, "<{} />", self.name); + if self.children.is_empty() { + #[cfg(debug_assertions)] + return write!(f, "<{} />", self.name); - #[cfg(not(debug_assertions))] - return f.write_str(""); - } else { - #[cfg(debug_assertions)] - writeln!(f, "<{}>", self.name)?; - #[cfg(not(debug_assertions))] - f.write_str("")?; + #[cfg(not(debug_assertions))] + return f.write_str(""); + } else { + #[cfg(debug_assertions)] + writeln!(f, "<{}>", self.name)?; + #[cfg(not(debug_assertions))] + f.write_str("")?; - let mut pad_adapter = pad_adapter::PadAdapter::new(f); + let mut pad_adapter = pad_adapter::PadAdapter::new(f); - for child in &self.children { - writeln!(pad_adapter, "{child:#?}")?; - } + for child in &self.children { + writeln!(pad_adapter, "{child:#?}")?; + } - #[cfg(debug_assertions)] - write!(f, "", self.name)?; - #[cfg(not(debug_assertions))] - f.write_str("")?; + #[cfg(debug_assertions)] + write!(f, "", self.name)?; + #[cfg(not(debug_assertions))] + f.write_str("")?; - Ok(()) + Ok(()) + } } - } } #[cfg(all(target_arch = "wasm32", feature = "web"))] impl Mountable for ComponentRepr { - fn get_mountable_node(&self) -> web_sys::Node { - if self.mounted.get().is_none() { - self.mounted.set(()).unwrap(); + fn get_mountable_node(&self) -> web_sys::Node { + if self.mounted.get().is_none() { + self.mounted.set(()).unwrap(); - self - .document_fragment - .unchecked_ref::() - .to_owned() + self.document_fragment + .unchecked_ref::() + .to_owned() + } + // We need to prepare all children to move + else { + let opening = self.get_opening_node(); + + prepare_to_move( + &self.document_fragment, + &opening, + &self.closing.node, + ); + + self.document_fragment.clone().unchecked_into() + } } - // We need to prepare all children to move - else { - let opening = self.get_opening_node(); - prepare_to_move(&self.document_fragment, &opening, &self.closing.node); + fn get_opening_node(&self) -> web_sys::Node { + #[cfg(debug_assertions)] + return self._opening.node.clone(); - self.document_fragment.clone().unchecked_into() + #[cfg(not(debug_assertions))] + return if let Some(child) = self.children.get(0) { + child.get_opening_node() + } else { + self.closing.node.clone() + }; } - } - fn get_opening_node(&self) -> web_sys::Node { - #[cfg(debug_assertions)] - return self._opening.node.clone(); - - #[cfg(not(debug_assertions))] - return if let Some(child) = self.children.get(0) { - child.get_opening_node() - } else { - self.closing.node.clone() - }; - } - - fn get_closing_node(&self) -> web_sys::Node { - self.closing.node.clone() - } + fn get_closing_node(&self) -> web_sys::Node { + self.closing.node.clone() + } } impl IntoView for ComponentRepr { - #[cfg_attr(debug_assertions, instrument(level = "trace", name = "", skip_all, fields(name = %self.name)))] - fn into_view(self, _: Scope) -> View { - #[cfg(all(target_arch = "wasm32", feature = "web"))] - if !HydrationCtx::is_hydrating() { - for child in &self.children { - mount_child(MountKind::Before(&self.closing.node), child); - } - } + #[cfg_attr(debug_assertions, instrument(level = "trace", name = "", skip_all, fields(name = %self.name)))] + fn into_view(self, _: Scope) -> View { + #[cfg(all(target_arch = "wasm32", feature = "web"))] + if !HydrationCtx::is_hydrating() { + for child in &self.children { + mount_child(MountKind::Before(&self.closing.node), child); + } + } - View::Component(self) - } + View::Component(self) + } } impl ComponentRepr { - /// Creates a new [`Component`]. - pub fn new(name: impl Into>) -> Self { - Self::new_with_id(name, HydrationCtx::id()) - } - - /// Creates a new [`Component`] with the given hydration ID. - pub fn new_with_id( - name: impl Into>, - id: HydrationKey, - ) -> Self { - let name = name.into(); - - let markers = ( - Comment::new(Cow::Owned(format!("")), &id, true), - #[cfg(debug_assertions)] - Comment::new(Cow::Owned(format!("<{name}>")), &id, false), - ); - - #[cfg(all(target_arch = "wasm32", feature = "web"))] - let document_fragment = { - let fragment = crate::document().create_document_fragment(); - - // Insert the comments into the document fragment - // so they can serve as our references when inserting - // future nodes - if !HydrationCtx::is_hydrating() { - #[cfg(debug_assertions)] - fragment - .append_with_node_2(&markers.1.node, &markers.0.node) - .expect("append to not err"); - #[cfg(not(debug_assertions))] - fragment - .append_with_node_1(&markers.0.node) - .expect("append to not err"); - } - - fragment - }; - - Self { - #[cfg(all(target_arch = "wasm32", feature = "web"))] - document_fragment, - #[cfg(all(target_arch = "wasm32", feature = "web"))] - mounted: Default::default(), - #[cfg(debug_assertions)] - _opening: markers.1, - closing: markers.0, - #[cfg(debug_assertions)] - name, - children: Vec::with_capacity(1), - #[cfg(not(all(target_arch = "wasm32", feature = "web")))] - id, + /// Creates a new [`Component`]. + pub fn new(name: impl Into>) -> Self { + Self::new_with_id(name, HydrationCtx::id()) + } + + /// Creates a new [`Component`] with the given hydration ID. + pub fn new_with_id( + name: impl Into>, + id: HydrationKey, + ) -> Self { + let name = name.into(); + + let markers = ( + Comment::new(Cow::Owned(format!("")), &id, true), + #[cfg(debug_assertions)] + Comment::new(Cow::Owned(format!("<{name}>")), &id, false), + ); + + #[cfg(all(target_arch = "wasm32", feature = "web"))] + let document_fragment = { + let fragment = crate::document().create_document_fragment(); + + // Insert the comments into the document fragment + // so they can serve as our references when inserting + // future nodes + if !HydrationCtx::is_hydrating() { + #[cfg(debug_assertions)] + fragment + .append_with_node_2(&markers.1.node, &markers.0.node) + .expect("append to not err"); + #[cfg(not(debug_assertions))] + fragment + .append_with_node_1(&markers.0.node) + .expect("append to not err"); + } + + fragment + }; + + Self { + #[cfg(all(target_arch = "wasm32", feature = "web"))] + document_fragment, + #[cfg(all(target_arch = "wasm32", feature = "web"))] + mounted: Default::default(), + #[cfg(debug_assertions)] + _opening: markers.1, + closing: markers.0, + #[cfg(debug_assertions)] + name, + children: Vec::with_capacity(1), + #[cfg(not(all(target_arch = "wasm32", feature = "web")))] + id, + } } - } } /// A user-defined `leptos` component. pub struct Component where - F: FnOnce(Scope) -> V, - V: IntoView, + F: FnOnce(Scope) -> V, + V: IntoView, { - id: HydrationKey, - name: Cow<'static, str>, - children_fn: F, + id: HydrationKey, + name: Cow<'static, str>, + children_fn: F, } impl Component where - F: FnOnce(Scope) -> V, - V: IntoView, + F: FnOnce(Scope) -> V, + V: IntoView, { - /// Creates a new component. - pub fn new(name: impl Into>, f: F) -> Self { - Self { - id: HydrationCtx::next_component(), - name: name.into(), - children_fn: f, + /// Creates a new component. + pub fn new(name: impl Into>, f: F) -> Self { + Self { + id: HydrationCtx::next_component(), + name: name.into(), + children_fn: f, + } } - } } impl IntoView for Component where - F: FnOnce(Scope) -> V, - V: IntoView, + F: FnOnce(Scope) -> V, + V: IntoView, { - #[track_caller] - fn into_view(self, cx: Scope) -> View { - let Self { - id, - name, - children_fn, - } = self; + #[track_caller] + fn into_view(self, cx: Scope) -> View { + let Self { + id, + name, + children_fn, + } = self; - let mut repr = ComponentRepr::new_with_id(name, id); + let mut repr = ComponentRepr::new_with_id(name, id); - // disposed automatically when the parent scope is disposed - let (child, _) = - cx.run_child_scope(|cx| cx.untrack(|| children_fn(cx).into_view(cx))); + // disposed automatically when the parent scope is disposed + let (child, _) = cx + .run_child_scope(|cx| cx.untrack(|| children_fn(cx).into_view(cx))); - repr.children.push(child); + repr.children.push(child); - repr.into_view(cx) - } + repr.into_view(cx) + } } diff --git a/leptos_dom/src/components/dyn_child.rs b/leptos_dom/src/components/dyn_child.rs index 69c209cac..ccc226e6b 100644 --- a/leptos_dom/src/components/dyn_child.rs +++ b/leptos_dom/src/components/dyn_child.rs @@ -1,6 +1,6 @@ use crate::{ - hydration::{HydrationCtx, HydrationKey}, - Comment, IntoView, View, + hydration::{HydrationCtx, HydrationKey}, + Comment, IntoView, View, }; use cfg_if::cfg_if; use leptos_reactive::Scope; @@ -16,320 +16,349 @@ cfg_if! { /// The internal representation of the [`DynChild`] core-component. #[derive(Clone, PartialEq, Eq)] pub struct DynChildRepr { - #[cfg(all(target_arch = "wasm32", feature = "web"))] - document_fragment: web_sys::DocumentFragment, - #[cfg(debug_assertions)] - opening: Comment, - pub(crate) child: Rc>>>, - closing: Comment, - #[cfg(not(all(target_arch = "wasm32", feature = "web")))] - pub(crate) id: HydrationKey, + #[cfg(all(target_arch = "wasm32", feature = "web"))] + document_fragment: web_sys::DocumentFragment, + #[cfg(debug_assertions)] + opening: Comment, + pub(crate) child: Rc>>>, + closing: Comment, + #[cfg(not(all(target_arch = "wasm32", feature = "web")))] + pub(crate) id: HydrationKey, } impl fmt::Debug for DynChildRepr { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - use fmt::Write; + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + use fmt::Write; - f.write_str("\n")?; + f.write_str("\n")?; - let mut pad_adapter = pad_adapter::PadAdapter::new(f); + let mut pad_adapter = pad_adapter::PadAdapter::new(f); - writeln!( - pad_adapter, - "{:#?}", - self.child.borrow().deref().deref().as_ref().unwrap() - )?; + writeln!( + pad_adapter, + "{:#?}", + self.child.borrow().deref().deref().as_ref().unwrap() + )?; - f.write_str("") - } + f.write_str("") + } } #[cfg(all(target_arch = "wasm32", feature = "web"))] impl Mountable for DynChildRepr { - fn get_mountable_node(&self) -> web_sys::Node { - if self.document_fragment.child_nodes().length() != 0 { - self.document_fragment.clone().unchecked_into() - } else { - let opening = self.get_opening_node(); + fn get_mountable_node(&self) -> web_sys::Node { + if self.document_fragment.child_nodes().length() != 0 { + self.document_fragment.clone().unchecked_into() + } else { + let opening = self.get_opening_node(); - prepare_to_move(&self.document_fragment, &opening, &self.closing.node); + prepare_to_move( + &self.document_fragment, + &opening, + &self.closing.node, + ); - self.document_fragment.clone().unchecked_into() + self.document_fragment.clone().unchecked_into() + } } - } - fn get_opening_node(&self) -> web_sys::Node { - #[cfg(debug_assertions)] - return self.opening.node.clone(); + fn get_opening_node(&self) -> web_sys::Node { + #[cfg(debug_assertions)] + return self.opening.node.clone(); - #[cfg(not(debug_assertions))] - return self - .child - .borrow() - .as_ref() - .as_ref() - .unwrap() - .get_opening_node(); - } + #[cfg(not(debug_assertions))] + return self + .child + .borrow() + .as_ref() + .as_ref() + .unwrap() + .get_opening_node(); + } - fn get_closing_node(&self) -> web_sys::Node { - self.closing.node.clone() - } + fn get_closing_node(&self) -> web_sys::Node { + self.closing.node.clone() + } } impl DynChildRepr { - fn new_with_id(id: HydrationKey) -> Self { - let markers = ( - Comment::new(Cow::Borrowed(""), &id, true), - #[cfg(debug_assertions)] - Comment::new(Cow::Borrowed(""), &id, false), - ); + fn new_with_id(id: HydrationKey) -> Self { + let markers = ( + Comment::new(Cow::Borrowed(""), &id, true), + #[cfg(debug_assertions)] + Comment::new(Cow::Borrowed(""), &id, false), + ); - #[cfg(all(target_arch = "wasm32", feature = "web"))] - let document_fragment = { - let fragment = crate::document().create_document_fragment(); + #[cfg(all(target_arch = "wasm32", feature = "web"))] + let document_fragment = { + let fragment = crate::document().create_document_fragment(); - // Insert the comments into the document fragment - // so they can serve as our references when inserting - // future nodes - if !HydrationCtx::is_hydrating() { - #[cfg(debug_assertions)] - fragment - .append_with_node_2(&markers.1.node, &markers.0.node) - .unwrap(); - #[cfg(not(debug_assertions))] - fragment.append_with_node_1(&markers.0.node).unwrap(); - } + // Insert the comments into the document fragment + // so they can serve as our references when inserting + // future nodes + if !HydrationCtx::is_hydrating() { + #[cfg(debug_assertions)] + fragment + .append_with_node_2(&markers.1.node, &markers.0.node) + .unwrap(); + #[cfg(not(debug_assertions))] + fragment.append_with_node_1(&markers.0.node).unwrap(); + } - fragment - }; + fragment + }; - Self { - #[cfg(all(target_arch = "wasm32", feature = "web"))] - document_fragment, - #[cfg(debug_assertions)] - opening: markers.1, - child: Default::default(), - closing: markers.0, - #[cfg(not(all(target_arch = "wasm32", feature = "web")))] - id, + Self { + #[cfg(all(target_arch = "wasm32", feature = "web"))] + document_fragment, + #[cfg(debug_assertions)] + opening: markers.1, + child: Default::default(), + closing: markers.0, + #[cfg(not(all(target_arch = "wasm32", feature = "web")))] + id, + } } - } } /// Represents any [`View`] that can change over time. pub struct DynChild where - CF: Fn() -> N + 'static, - N: IntoView, + CF: Fn() -> N + 'static, + N: IntoView, { - id: crate::HydrationKey, - child_fn: CF, + id: crate::HydrationKey, + child_fn: CF, } impl DynChild where - CF: Fn() -> N + 'static, - N: IntoView, + CF: Fn() -> N + 'static, + N: IntoView, { - /// Creates a new dynamic child which will re-render whenever it's - /// signal dependencies change. - pub fn new(child_fn: CF) -> Self { - Self::new_with_id(HydrationCtx::id(), child_fn) - } + /// Creates a new dynamic child which will re-render whenever it's + /// signal dependencies change. + pub fn new(child_fn: CF) -> Self { + Self::new_with_id(HydrationCtx::id(), child_fn) + } - #[doc(hidden)] - pub fn new_with_id(id: HydrationKey, child_fn: CF) -> Self { - Self { id, child_fn } - } + #[doc(hidden)] + pub fn new_with_id(id: HydrationKey, child_fn: CF) -> Self { + Self { id, child_fn } + } } impl IntoView for DynChild where - CF: Fn() -> N + 'static, - N: IntoView, + CF: Fn() -> N + 'static, + N: IntoView, { - #[cfg_attr( - debug_assertions, - instrument(level = "trace", name = "", skip_all) - )] - fn into_view(self, cx: Scope) -> View { - // concrete inner function - fn create_dyn_view( - cx: Scope, - component: DynChildRepr, - child_fn: Box View>, - ) -> DynChildRepr { - #[cfg(all(target_arch = "wasm32", feature = "web"))] - let closing = component.closing.node.clone(); + #[cfg_attr( + debug_assertions, + instrument(level = "trace", name = "", skip_all) + )] + fn into_view(self, cx: Scope) -> View { + // concrete inner function + fn create_dyn_view( + cx: Scope, + component: DynChildRepr, + child_fn: Box View>, + ) -> DynChildRepr { + #[cfg(all(target_arch = "wasm32", feature = "web"))] + let closing = component.closing.node.clone(); - let child = component.child.clone(); + let child = component.child.clone(); - #[cfg(all(debug_assertions, target_arch = "wasm32", feature = "web"))] - let span = tracing::Span::current(); + #[cfg(all( + debug_assertions, + target_arch = "wasm32", + feature = "web" + ))] + let span = tracing::Span::current(); - #[cfg(all(target_arch = "wasm32", feature = "web"))] - create_effect( - cx, - move |prev_run: Option<(Option, ScopeDisposer)>| { - #[cfg(debug_assertions)] - let _guard = span.enter(); + #[cfg(all(target_arch = "wasm32", feature = "web"))] + create_effect( + cx, + move |prev_run: Option<( + Option, + ScopeDisposer, + )>| { + #[cfg(debug_assertions)] + let _guard = span.enter(); - let (new_child, disposer) = - cx.run_child_scope(|cx| child_fn().into_view(cx)); + let (new_child, disposer) = + cx.run_child_scope(|cx| child_fn().into_view(cx)); - let mut child_borrow = child.borrow_mut(); + let mut child_borrow = child.borrow_mut(); - // Is this at least the second time we are loading a child? - if let Some((prev_t, prev_disposer)) = prev_run { - let child = child_borrow.take().unwrap(); + // Is this at least the second time we are loading a child? + if let Some((prev_t, prev_disposer)) = prev_run { + let child = child_borrow.take().unwrap(); - // Dispose of the scope - prev_disposer.dispose(); + // Dispose of the scope + prev_disposer.dispose(); - // We need to know if our child wasn't moved elsewhere. - // If it was, `DynChild` no longer "owns" that child, and - // is therefore no longer sound to unmount it from the DOM - // or to reuse it in the case of a text node + // We need to know if our child wasn't moved elsewhere. + // If it was, `DynChild` no longer "owns" that child, and + // is therefore no longer sound to unmount it from the DOM + // or to reuse it in the case of a text node - // TODO check does this still detect moves correctly? - let was_child_moved = prev_t.is_none() - && child.get_closing_node().next_sibling().as_ref() - != Some(&closing); + // TODO check does this still detect moves correctly? + let was_child_moved = prev_t.is_none() + && child.get_closing_node().next_sibling().as_ref() + != Some(&closing); - // If the previous child was a text node, we would like to - // make use of it again if our current child is also a text - // node - let ret = if let Some(prev_t) = prev_t { - // Here, our child is also a text node - if let Some(new_t) = new_child.get_text() { - if !was_child_moved && child != new_child { - prev_t - .unchecked_ref::() - .set_data(&new_t.content); + // If the previous child was a text node, we would like to + // make use of it again if our current child is also a text + // node + let ret = if let Some(prev_t) = prev_t { + // Here, our child is also a text node + if let Some(new_t) = new_child.get_text() { + if !was_child_moved && child != new_child { + prev_t + .unchecked_ref::() + .set_data(&new_t.content); - **child_borrow = Some(new_child); + **child_borrow = Some(new_child); - (Some(prev_t), disposer) - } else { - mount_child(MountKind::Before(&closing), &new_child); + (Some(prev_t), disposer) + } else { + mount_child( + MountKind::Before(&closing), + &new_child, + ); - **child_borrow = Some(new_child.clone()); + **child_borrow = Some(new_child.clone()); - (Some(new_t.node.clone()), disposer) - } - } - // Child is not a text node, so we can remove the previous - // text node - else { - if !was_child_moved && child != new_child { - // Remove the text - closing - .previous_sibling() - .unwrap() - .unchecked_into::() - .remove(); - } + (Some(new_t.node.clone()), disposer) + } + } + // Child is not a text node, so we can remove the previous + // text node + else { + if !was_child_moved && child != new_child { + // Remove the text + closing + .previous_sibling() + .unwrap() + .unchecked_into::() + .remove(); + } - // Mount the new child, and we're done - mount_child(MountKind::Before(&closing), &new_child); + // Mount the new child, and we're done + mount_child( + MountKind::Before(&closing), + &new_child, + ); - **child_borrow = Some(new_child); + **child_borrow = Some(new_child); - (None, disposer) - } - } - // Otherwise, the new child can still be a text node, - // but we know the previous child was not, so no special - // treatment here - else { - // Technically, I think this check shouldn't be necessary, but - // I can imagine some edge case that the child changes while - // hydration is ongoing - if !HydrationCtx::is_hydrating() { - if !was_child_moved && child != new_child { - // Remove the child - let start = child.get_opening_node(); - let end = &closing; + (None, disposer) + } + } + // Otherwise, the new child can still be a text node, + // but we know the previous child was not, so no special + // treatment here + else { + // Technically, I think this check shouldn't be necessary, but + // I can imagine some edge case that the child changes while + // hydration is ongoing + if !HydrationCtx::is_hydrating() { + if !was_child_moved && child != new_child { + // Remove the child + let start = child.get_opening_node(); + let end = &closing; - unmount_child(&start, end); - } + unmount_child(&start, end); + } - // Mount the new child - mount_child(MountKind::Before(&closing), &new_child); - } + // Mount the new child + mount_child( + MountKind::Before(&closing), + &new_child, + ); + } - // We want to reuse text nodes, so hold onto it if - // our child is one - let t = new_child.get_text().map(|t| t.node.clone()); + // We want to reuse text nodes, so hold onto it if + // our child is one + let t = + new_child.get_text().map(|t| t.node.clone()); - **child_borrow = Some(new_child); + **child_borrow = Some(new_child); - (t, disposer) - }; + (t, disposer) + }; - ret - } - // Otherwise, we know for sure this is our first time - else { - // We need to remove the text created from SSR - if HydrationCtx::is_hydrating() && new_child.get_text().is_some() { - let t = closing - .previous_sibling() - .unwrap() - .unchecked_into::(); + ret + } + // Otherwise, we know for sure this is our first time + else { + // We need to remove the text created from SSR + if HydrationCtx::is_hydrating() + && new_child.get_text().is_some() + { + let t = closing + .previous_sibling() + .unwrap() + .unchecked_into::(); - // See note on ssr.rs when matching on `DynChild` - // for more details on why we need to do this for - // release - if !cfg!(debug_assertions) { - t.previous_sibling() - .unwrap() - .unchecked_into::() - .remove(); - } + // See note on ssr.rs when matching on `DynChild` + // for more details on why we need to do this for + // release + if !cfg!(debug_assertions) { + t.previous_sibling() + .unwrap() + .unchecked_into::() + .remove(); + } - t.remove(); + t.remove(); - mount_child(MountKind::Before(&closing), &new_child); + mount_child( + MountKind::Before(&closing), + &new_child, + ); + } + + // If we are not hydrating, we simply mount the child + if !HydrationCtx::is_hydrating() { + mount_child( + MountKind::Before(&closing), + &new_child, + ); + } + + // We want to update text nodes, rather than replace them, so + // make sure to hold onto the text node + let t = new_child.get_text().map(|t| t.node.clone()); + + **child_borrow = Some(new_child); + + (t, disposer) + } + }, + ); + + #[cfg(not(all(target_arch = "wasm32", feature = "web")))] + { + let new_child = child_fn().into_view(cx); + + **child.borrow_mut() = Some(new_child); } - // If we are not hydrating, we simply mount the child - if !HydrationCtx::is_hydrating() { - mount_child(MountKind::Before(&closing), &new_child); - } + component + } - // We want to update text nodes, rather than replace them, so - // make sure to hold onto the text node - let t = new_child.get_text().map(|t| t.node.clone()); + // monomorphized outer function + let Self { id, child_fn } = self; - **child_borrow = Some(new_child); + let component = DynChildRepr::new_with_id(id); + let component = create_dyn_view( + cx, + component, + Box::new(move || child_fn().into_view(cx)), + ); - (t, disposer) - } - }, - ); - - #[cfg(not(all(target_arch = "wasm32", feature = "web")))] - { - let new_child = child_fn().into_view(cx); - - **child.borrow_mut() = Some(new_child); - } - - component + View::CoreComponent(crate::CoreComponent::DynChild(component)) } - - // monomorphized outer function - let Self { id, child_fn } = self; - - let component = DynChildRepr::new_with_id(id); - let component = create_dyn_view( - cx, - component, - Box::new(move || child_fn().into_view(cx)), - ); - - View::CoreComponent(crate::CoreComponent::DynChild(component)) - } } diff --git a/leptos_dom/src/components/each.rs b/leptos_dom/src/components/each.rs index 30f29dc7b..d4d15027b 100644 --- a/leptos_dom/src/components/each.rs +++ b/leptos_dom/src/components/each.rs @@ -45,362 +45,366 @@ use std::{borrow::Cow, cell::RefCell, fmt, hash::Hash, ops::Deref, rc::Rc}; /// The internal representation of the [`Each`] core-component. #[derive(Clone, PartialEq, Eq)] pub struct EachRepr { - #[cfg(all(target_arch = "wasm32", feature = "web"))] - document_fragment: web_sys::DocumentFragment, - #[cfg(all(target_arch = "wasm32", feature = "web"))] - mounted: Rc>, - #[cfg(debug_assertions)] - opening: Comment, - pub(crate) children: Rc>>>, - closing: Comment, - #[cfg(not(all(target_arch = "wasm32", feature = "web")))] - pub(crate) id: HydrationKey, + #[cfg(all(target_arch = "wasm32", feature = "web"))] + document_fragment: web_sys::DocumentFragment, + #[cfg(all(target_arch = "wasm32", feature = "web"))] + mounted: Rc>, + #[cfg(debug_assertions)] + opening: Comment, + pub(crate) children: Rc>>>, + closing: Comment, + #[cfg(not(all(target_arch = "wasm32", feature = "web")))] + pub(crate) id: HydrationKey, } impl fmt::Debug for EachRepr { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - use fmt::Write; + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + use fmt::Write; - f.write_str("\n")?; + f.write_str("\n")?; - for child in self.children.borrow().deref() { - let mut pad_adapter = pad_adapter::PadAdapter::new(f); + for child in self.children.borrow().deref() { + let mut pad_adapter = pad_adapter::PadAdapter::new(f); - writeln!(pad_adapter, "{:#?}", child.as_ref().unwrap())?; + writeln!(pad_adapter, "{:#?}", child.as_ref().unwrap())?; + } + + f.write_str("") } - - f.write_str("") - } } impl Default for EachRepr { - fn default() -> Self { - let id = HydrationCtx::id(); + fn default() -> Self { + let id = HydrationCtx::id(); - let markers = ( - Comment::new(Cow::Borrowed(""), &id, true), - #[cfg(debug_assertions)] - Comment::new(Cow::Borrowed(""), &id, false), - ); + let markers = ( + Comment::new(Cow::Borrowed(""), &id, true), + #[cfg(debug_assertions)] + Comment::new(Cow::Borrowed(""), &id, false), + ); - #[cfg(all(target_arch = "wasm32", feature = "web"))] - let document_fragment = { - let fragment = crate::document().create_document_fragment(); + #[cfg(all(target_arch = "wasm32", feature = "web"))] + let document_fragment = { + let fragment = crate::document().create_document_fragment(); - // Insert the comments into the document fragment - #[cfg(debug_assertions)] - // so they can serve as our references when inserting - // future nodes - if !HydrationCtx::is_hydrating() { - fragment - .append_with_node_2(&markers.1.node, &markers.0.node) - .expect("append to not err"); - } + // Insert the comments into the document fragment + #[cfg(debug_assertions)] + // so they can serve as our references when inserting + // future nodes + if !HydrationCtx::is_hydrating() { + fragment + .append_with_node_2(&markers.1.node, &markers.0.node) + .expect("append to not err"); + } - #[cfg(not(debug_assertions))] - if !HydrationCtx::is_hydrating() { - fragment.append_with_node_1(&markers.0.node).unwrap(); - } + #[cfg(not(debug_assertions))] + if !HydrationCtx::is_hydrating() { + fragment.append_with_node_1(&markers.0.node).unwrap(); + } - fragment - }; + fragment + }; - Self { - #[cfg(all(target_arch = "wasm32", feature = "web"))] - document_fragment, - #[cfg(all(target_arch = "wasm32", feature = "web"))] - mounted: Default::default(), - #[cfg(debug_assertions)] - opening: markers.1, - children: Default::default(), - closing: markers.0, - #[cfg(not(all(target_arch = "wasm32", feature = "web")))] - id, + Self { + #[cfg(all(target_arch = "wasm32", feature = "web"))] + document_fragment, + #[cfg(all(target_arch = "wasm32", feature = "web"))] + mounted: Default::default(), + #[cfg(debug_assertions)] + opening: markers.1, + children: Default::default(), + closing: markers.0, + #[cfg(not(all(target_arch = "wasm32", feature = "web")))] + id, + } } - } } #[cfg(all(target_arch = "wasm32", feature = "web"))] impl Mountable for EachRepr { - fn get_mountable_node(&self) -> web_sys::Node { - if self.mounted.get().is_none() { - self.mounted.set(()).unwrap(); + fn get_mountable_node(&self) -> web_sys::Node { + if self.mounted.get().is_none() { + self.mounted.set(()).unwrap(); - self.document_fragment.clone().unchecked_into() - } else { - let opening = self.get_opening_node(); + self.document_fragment.clone().unchecked_into() + } else { + let opening = self.get_opening_node(); - prepare_to_move(&self.document_fragment, &opening, &self.closing.node); + prepare_to_move( + &self.document_fragment, + &opening, + &self.closing.node, + ); - self.document_fragment.clone().unchecked_into() + self.document_fragment.clone().unchecked_into() + } } - } - fn get_opening_node(&self) -> web_sys::Node { - #[cfg(debug_assertions)] - return self.opening.node.clone(); + fn get_opening_node(&self) -> web_sys::Node { + #[cfg(debug_assertions)] + return self.opening.node.clone(); - #[cfg(not(debug_assertions))] - return { - let children_borrow = self.children.borrow(); + #[cfg(not(debug_assertions))] + return { + let children_borrow = self.children.borrow(); - if let Some(Some(child)) = children_borrow.get(0) { - child.get_opening_node() - } else { + if let Some(Some(child)) = children_borrow.get(0) { + child.get_opening_node() + } else { + self.closing.node.clone() + } + }; + } + + fn get_closing_node(&self) -> web_sys::Node { self.closing.node.clone() - } - }; - } - - fn get_closing_node(&self) -> web_sys::Node { - self.closing.node.clone() - } + } } /// The internal representation of an [`Each`] item. #[derive(PartialEq, Eq)] pub(crate) struct EachItem { - cx: Scope, - #[cfg(all(target_arch = "wasm32", feature = "web"))] - document_fragment: web_sys::DocumentFragment, - #[cfg(debug_assertions)] - opening: Comment, - pub(crate) child: View, - closing: Comment, - #[cfg(not(all(target_arch = "wasm32", feature = "web")))] - pub(crate) id: HydrationKey, + cx: Scope, + #[cfg(all(target_arch = "wasm32", feature = "web"))] + document_fragment: web_sys::DocumentFragment, + #[cfg(debug_assertions)] + opening: Comment, + pub(crate) child: View, + closing: Comment, + #[cfg(not(all(target_arch = "wasm32", feature = "web")))] + pub(crate) id: HydrationKey, } impl fmt::Debug for EachItem { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - use fmt::Write; + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + use fmt::Write; - f.write_str("\n")?; + f.write_str("\n")?; - let mut pad_adapter = pad_adapter::PadAdapter::new(f); + let mut pad_adapter = pad_adapter::PadAdapter::new(f); - writeln!(pad_adapter, "{:#?}", self.child)?; + writeln!(pad_adapter, "{:#?}", self.child)?; - f.write_str("") - } + f.write_str("") + } } impl EachItem { - fn new(cx: Scope, child: View) -> Self { - let id = HydrationCtx::id(); + fn new(cx: Scope, child: View) -> Self { + let id = HydrationCtx::id(); - let markers = ( - Comment::new(Cow::Borrowed(""), &id, true), - #[cfg(debug_assertions)] - Comment::new(Cow::Borrowed(""), &id, false), - ); + let markers = ( + Comment::new(Cow::Borrowed(""), &id, true), + #[cfg(debug_assertions)] + Comment::new(Cow::Borrowed(""), &id, false), + ); - #[cfg(all(target_arch = "wasm32", feature = "web"))] - let document_fragment = { - let fragment = crate::document().create_document_fragment(); + #[cfg(all(target_arch = "wasm32", feature = "web"))] + let document_fragment = { + let fragment = crate::document().create_document_fragment(); - // Insert the comments into the document fragment - // so they can serve as our references when inserting - // future nodes - if !HydrationCtx::is_hydrating() { - #[cfg(debug_assertions)] - fragment - .append_with_node_2(&markers.1.node, &markers.0.node) - .unwrap(); - fragment.append_with_node_1(&markers.0.node).unwrap(); - } + // Insert the comments into the document fragment + // so they can serve as our references when inserting + // future nodes + if !HydrationCtx::is_hydrating() { + #[cfg(debug_assertions)] + fragment + .append_with_node_2(&markers.1.node, &markers.0.node) + .unwrap(); + fragment.append_with_node_1(&markers.0.node).unwrap(); + } - mount_child(MountKind::Before(&markers.0.node), &child); + mount_child(MountKind::Before(&markers.0.node), &child); - fragment - }; + fragment + }; - Self { - cx, - #[cfg(all(target_arch = "wasm32", feature = "web"))] - document_fragment, - #[cfg(debug_assertions)] - opening: markers.1, - child, - closing: markers.0, - #[cfg(not(all(target_arch = "wasm32", feature = "web")))] - id, + Self { + cx, + #[cfg(all(target_arch = "wasm32", feature = "web"))] + document_fragment, + #[cfg(debug_assertions)] + opening: markers.1, + child, + closing: markers.0, + #[cfg(not(all(target_arch = "wasm32", feature = "web")))] + id, + } } - } } #[cfg(all(target_arch = "wasm32", feature = "web"))] impl Drop for EachItem { - fn drop(&mut self) { - self.cx.dispose(); - } + fn drop(&mut self) { + self.cx.dispose(); + } } #[cfg(all(target_arch = "wasm32", feature = "web"))] impl Mountable for EachItem { - fn get_mountable_node(&self) -> web_sys::Node { - self.document_fragment.clone().unchecked_into() - } + fn get_mountable_node(&self) -> web_sys::Node { + self.document_fragment.clone().unchecked_into() + } - fn get_opening_node(&self) -> web_sys::Node { - #[cfg(debug_assertions)] - return self.opening.node.clone(); + fn get_opening_node(&self) -> web_sys::Node { + #[cfg(debug_assertions)] + return self.opening.node.clone(); - #[cfg(not(debug_assertions))] - return self.child.get_opening_node(); - } + #[cfg(not(debug_assertions))] + return self.child.get_opening_node(); + } - fn get_closing_node(&self) -> web_sys::Node { - self.closing.node.clone() - } + fn get_closing_node(&self) -> web_sys::Node { + self.closing.node.clone() + } } impl EachItem { - /// Moves all child nodes into its' `DocumentFragment` in - /// order to be reinserted somewhere else. - #[cfg(all(target_arch = "wasm32", feature = "web"))] - fn prepare_for_move(&self) { - let start = self.get_opening_node(); - let end = &self.closing.node; + /// Moves all child nodes into its' `DocumentFragment` in + /// order to be reinserted somewhere else. + #[cfg(all(target_arch = "wasm32", feature = "web"))] + fn prepare_for_move(&self) { + let start = self.get_opening_node(); + let end = &self.closing.node; - let mut sibling = start; + let mut sibling = start; - while sibling != *end { - let next_sibling = sibling.next_sibling().unwrap(); + while sibling != *end { + let next_sibling = sibling.next_sibling().unwrap(); - self.document_fragment.append_child(&sibling).unwrap(); + self.document_fragment.append_child(&sibling).unwrap(); - sibling = next_sibling; + sibling = next_sibling; + } + + self.document_fragment.append_with_node_1(end).unwrap(); } - - self.document_fragment.append_with_node_1(end).unwrap(); - } } /// A component for efficiently rendering an iterable. pub struct Each where - IF: Fn() -> I + 'static, - I: IntoIterator, - EF: Fn(Scope, T) -> N + 'static, - N: IntoView, - KF: Fn(&T) -> K + 'static, - K: Eq + Hash + 'static, - T: 'static, + IF: Fn() -> I + 'static, + I: IntoIterator, + EF: Fn(Scope, T) -> N + 'static, + N: IntoView, + KF: Fn(&T) -> K + 'static, + K: Eq + Hash + 'static, + T: 'static, { - pub(crate) items_fn: IF, - pub(crate) each_fn: EF, - key_fn: KF, + pub(crate) items_fn: IF, + pub(crate) each_fn: EF, + key_fn: KF, } impl Each where - IF: Fn() -> I + 'static, - I: IntoIterator, - EF: Fn(Scope, T) -> N + 'static, - N: IntoView, - KF: Fn(&T) -> K, - K: Eq + Hash + 'static, - T: 'static, + IF: Fn() -> I + 'static, + I: IntoIterator, + EF: Fn(Scope, T) -> N + 'static, + N: IntoView, + KF: Fn(&T) -> K, + K: Eq + Hash + 'static, + T: 'static, { - /// Creates a new [`Each`] component. - pub fn new(items_fn: IF, key_fn: KF, each_fn: EF) -> Self { - Self { - items_fn, - each_fn, - key_fn, + /// Creates a new [`Each`] component. + pub fn new(items_fn: IF, key_fn: KF, each_fn: EF) -> Self { + Self { + items_fn, + each_fn, + key_fn, + } } - } } impl IntoView for Each where - IF: Fn() -> I + 'static, - I: IntoIterator, - EF: Fn(Scope, T) -> N + 'static, - N: IntoView, - KF: Fn(&T) -> K + 'static, - K: Eq + Hash + 'static, - T: 'static, + IF: Fn() -> I + 'static, + I: IntoIterator, + EF: Fn(Scope, T) -> N + 'static, + N: IntoView, + KF: Fn(&T) -> K + 'static, + K: Eq + Hash + 'static, + T: 'static, { - #[cfg_attr( - debug_assertions, - instrument(level = "trace", name = "", skip_all) - )] - fn into_view(self, cx: Scope) -> crate::View { - let Self { - items_fn, - each_fn, - key_fn, - } = self; + #[cfg_attr( + debug_assertions, + instrument(level = "trace", name = "", skip_all) + )] + fn into_view(self, cx: Scope) -> crate::View { + let Self { + items_fn, + each_fn, + key_fn, + } = self; - #[cfg(not(all(target_arch = "wasm32", feature = "web")))] - let _ = key_fn; + #[cfg(not(all(target_arch = "wasm32", feature = "web")))] + let _ = key_fn; - let component = EachRepr::default(); + let component = EachRepr::default(); - #[cfg(all(target_arch = "wasm32", feature = "web"))] - let (children, closing) = - (component.children.clone(), component.closing.node.clone()); + #[cfg(all(target_arch = "wasm32", feature = "web"))] + let (children, closing) = + (component.children.clone(), component.closing.node.clone()); - cfg_if::cfg_if! { - if #[cfg(all(target_arch = "wasm32", feature = "web"))] { - create_effect(cx, move |prev_hash_run| { - let mut children_borrow = children.borrow_mut(); + cfg_if::cfg_if! { + if #[cfg(all(target_arch = "wasm32", feature = "web"))] { + create_effect(cx, move |prev_hash_run| { + let mut children_borrow = children.borrow_mut(); - #[cfg(all(target_arch = "wasm32", feature = "web"))] - let opening = if let Some(Some(child)) = children_borrow.get(0) { - child.get_opening_node() + #[cfg(all(target_arch = "wasm32", feature = "web"))] + let opening = if let Some(Some(child)) = children_borrow.get(0) { + child.get_opening_node() + } else { + closing.clone() + }; + + let items = items_fn(); + + let items = items.into_iter().collect::>(); + + let hashed_items = + items.iter().map(&key_fn).collect::>(); + + if let Some(HashRun(prev_hash_run)) = prev_hash_run { + let cmds = diff(&prev_hash_run, &hashed_items); + + apply_cmds( + cx, + #[cfg(all(target_arch = "wasm32", feature = "web"))] + &opening, + #[cfg(all(target_arch = "wasm32", feature = "web"))] + &closing, + cmds, + &mut children_borrow, + items.into_iter().map(|t| Some(t)).collect(), + &each_fn + ); + } else { + *children_borrow = Vec::with_capacity(items.len()); + + for item in items { + let (each_item, _) = cx.run_child_scope(|cx| EachItem::new(cx, each_fn(cx, item).into_view(cx))); + + #[cfg(all(target_arch = "wasm32", feature = "web"))] + mount_child(MountKind::Before(&closing), &each_item); + + children_borrow.push(Some(each_item)); + } + } + + HashRun(hashed_items) + }); } else { - closing.clone() - }; - - let items = items_fn(); - - let items = items.into_iter().collect::>(); - - let hashed_items = - items.iter().map(&key_fn).collect::>(); - - if let Some(HashRun(prev_hash_run)) = prev_hash_run { - let cmds = diff(&prev_hash_run, &hashed_items); - - apply_cmds( - cx, - #[cfg(all(target_arch = "wasm32", feature = "web"))] - &opening, - #[cfg(all(target_arch = "wasm32", feature = "web"))] - &closing, - cmds, - &mut children_borrow, - items.into_iter().map(|t| Some(t)).collect(), - &each_fn - ); - } else { - *children_borrow = Vec::with_capacity(items.len()); - - for item in items { - let (each_item, _) = cx.run_child_scope(|cx| EachItem::new(cx, each_fn(cx, item).into_view(cx))); - - #[cfg(all(target_arch = "wasm32", feature = "web"))] - mount_child(MountKind::Before(&closing), &each_item); - - children_borrow.push(Some(each_item)); - } + *component.children.borrow_mut() = (items_fn)() + .into_iter() + .map(|child| cx.run_child_scope(|cx| Some(EachItem::new(cx, (each_fn)(cx, child).into_view(cx)))).0) + .collect(); } + } - HashRun(hashed_items) - }); - } else { - *component.children.borrow_mut() = (items_fn)() - .into_iter() - .map(|child| cx.run_child_scope(|cx| Some(EachItem::new(cx, (each_fn)(cx, child).into_view(cx)))).0) - .collect(); - } + View::CoreComponent(CoreComponent::Each(component)) } - - View::CoreComponent(CoreComponent::Each(component)) - } } #[derive(educe::Educe)] @@ -410,287 +414,292 @@ struct HashRun(#[educe(Debug(ignore))] T); /// Calculates the operations need to get from `a` to `b`. #[cfg(all(target_arch = "wasm32", feature = "web"))] fn diff(from: &FxIndexSet, to: &FxIndexSet) -> Diff { - if from.is_empty() && to.is_empty() { - return Diff::default(); - } else if to.is_empty() { - return Diff { - clear: true, - ..Default::default() + if from.is_empty() && to.is_empty() { + return Diff::default(); + } else if to.is_empty() { + return Diff { + clear: true, + ..Default::default() + }; + } + + // Get removed items + let mut removed = from.difference(to); + + let removed_cmds = removed + .clone() + .map(|k| from.get_full(k).unwrap().0) + .map(|idx| DiffOpRemove { at: idx }); + + // Get added items + let mut added = to.difference(from); + + let added_cmds = + added + .clone() + .map(|k| to.get_full(k).unwrap().0) + .map(|idx| DiffOpAdd { + at: idx, + mode: Default::default(), + }); + + // Get moved items + let mut normalized_idx = 0; + let mut move_cmds = SmallVec::<[_; 8]>::with_capacity(to.len()); + let mut added_idx = added.next().map(|k| to.get_full(k).unwrap().0); + let mut removed_idx = removed.next().map(|k| from.get_full(k).unwrap().0); + + for (idx, k) in to.iter().enumerate() { + if let Some(added_idx) = added_idx.as_mut().filter(|r_i| **r_i == idx) { + if let Some(next_added) = + added.next().map(|k| to.get_full(k).unwrap().0) + { + *added_idx = next_added; + + normalized_idx = usize::wrapping_sub(normalized_idx, 1); + } + } + + if let Some(removed_idx) = + removed_idx.as_mut().filter(|r_i| **r_i == idx) + { + normalized_idx = normalized_idx.wrapping_add(1); + + if let Some(next_removed) = + removed.next().map(|k| from.get_full(k).unwrap().0) + { + *removed_idx = next_removed; + } + } + + if let Some((from_idx, _)) = from.get_full(k) { + if from_idx != normalized_idx { + move_cmds.push(DiffOpMove { + from: from_idx, + to: idx, + move_in_dom: true, + }); + } else if from_idx != idx { + move_cmds.push(DiffOpMove { + from: from_idx, + to: idx, + move_in_dom: false, + }); + } + } + + normalized_idx = normalized_idx.wrapping_add(1); + } + + let mut diffs = Diff { + removed: removed_cmds.collect(), + moved: move_cmds, + added: added_cmds.collect(), + clear: false, }; - } - // Get removed items - let mut removed = from.difference(to); + apply_opts(from, to, &mut diffs); - let removed_cmds = removed - .clone() - .map(|k| from.get_full(k).unwrap().0) - .map(|idx| DiffOpRemove { at: idx }); - - // Get added items - let mut added = to.difference(from); - - let added_cmds = - added - .clone() - .map(|k| to.get_full(k).unwrap().0) - .map(|idx| DiffOpAdd { - at: idx, - mode: Default::default(), - }); - - // Get moved items - let mut normalized_idx = 0; - let mut move_cmds = SmallVec::<[_; 8]>::with_capacity(to.len()); - let mut added_idx = added.next().map(|k| to.get_full(k).unwrap().0); - let mut removed_idx = removed.next().map(|k| from.get_full(k).unwrap().0); - - for (idx, k) in to.iter().enumerate() { - if let Some(added_idx) = added_idx.as_mut().filter(|r_i| **r_i == idx) { - if let Some(next_added) = added.next().map(|k| to.get_full(k).unwrap().0) - { - *added_idx = next_added; - - normalized_idx = usize::wrapping_sub(normalized_idx, 1); - } - } - - if let Some(removed_idx) = removed_idx.as_mut().filter(|r_i| **r_i == idx) { - normalized_idx = normalized_idx.wrapping_add(1); - - if let Some(next_removed) = - removed.next().map(|k| from.get_full(k).unwrap().0) - { - *removed_idx = next_removed; - } - } - - if let Some((from_idx, _)) = from.get_full(k) { - if from_idx != normalized_idx { - move_cmds.push(DiffOpMove { - from: from_idx, - to: idx, - move_in_dom: true, - }); - } else if from_idx != idx { - move_cmds.push(DiffOpMove { - from: from_idx, - to: idx, - move_in_dom: false, - }); - } - } - - normalized_idx = normalized_idx.wrapping_add(1); - } - - let mut diffs = Diff { - removed: removed_cmds.collect(), - moved: move_cmds, - added: added_cmds.collect(), - clear: false, - }; - - apply_opts(from, to, &mut diffs); - - diffs + diffs } #[cfg(all(target_arch = "wasm32", feature = "web"))] fn apply_opts( - from: &FxIndexSet, - to: &FxIndexSet, - cmds: &mut Diff, + from: &FxIndexSet, + to: &FxIndexSet, + cmds: &mut Diff, ) { - // We can optimize the case of replacing all items - if !from.is_empty() - && !to.is_empty() - && cmds.removed.len() == from.len() - && cmds.moved.is_empty() - { - cmds.clear = true; + // We can optimize the case of replacing all items + if !from.is_empty() + && !to.is_empty() + && cmds.removed.len() == from.len() + && cmds.moved.is_empty() + { + cmds.clear = true; - cmds - .added - .iter_mut() - .for_each(|op| op.mode = DiffOpAddMode::Append); + cmds.added + .iter_mut() + .for_each(|op| op.mode = DiffOpAddMode::Append); - return; - } + return; + } - // We can optimize appends. - if !cmds.added.is_empty() - && cmds.moved.is_empty() - && cmds.removed.is_empty() - && cmds.added[0].at >= from.len() - { - cmds - .added - .iter_mut() - .for_each(|op| op.mode = DiffOpAddMode::Append); - } + // We can optimize appends. + if !cmds.added.is_empty() + && cmds.moved.is_empty() + && cmds.removed.is_empty() + && cmds.added[0].at >= from.len() + { + cmds.added + .iter_mut() + .for_each(|op| op.mode = DiffOpAddMode::Append); + } } #[derive(Debug, Default)] #[allow(unused)] struct Diff { - removed: SmallVec<[DiffOpRemove; 8]>, - moved: SmallVec<[DiffOpMove; 8]>, - added: SmallVec<[DiffOpAdd; 8]>, - clear: bool, + removed: SmallVec<[DiffOpRemove; 8]>, + moved: SmallVec<[DiffOpMove; 8]>, + added: SmallVec<[DiffOpAdd; 8]>, + clear: bool, } #[derive(Debug)] #[allow(unused)] struct DiffOpMove { - from: usize, - to: usize, - move_in_dom: bool, + from: usize, + to: usize, + move_in_dom: bool, } #[derive(Debug)] #[allow(unused)] struct DiffOpAdd { - at: usize, - mode: DiffOpAddMode, + at: usize, + mode: DiffOpAddMode, } #[derive(Debug)] #[allow(unused)] struct DiffOpRemove { - at: usize, + at: usize, } #[derive(Debug)] #[allow(unused)] enum DiffOpAddMode { - Normal, - Append, - // Todo - _Prepend, + Normal, + Append, + // Todo + _Prepend, } impl Default for DiffOpAddMode { - fn default() -> Self { - Self::Normal - } + fn default() -> Self { + Self::Normal + } } #[cfg(all(target_arch = "wasm32", feature = "web"))] fn apply_cmds( - cx: Scope, - opening: &web_sys::Node, - closing: &web_sys::Node, - mut cmds: Diff, - children: &mut Vec>, - mut items: SmallVec<[Option; 128]>, - each_fn: &EF, + cx: Scope, + opening: &web_sys::Node, + closing: &web_sys::Node, + mut cmds: Diff, + children: &mut Vec>, + mut items: SmallVec<[Option; 128]>, + each_fn: &EF, ) where - EF: Fn(Scope, T) -> N, - N: IntoView, + EF: Fn(Scope, T) -> N, + N: IntoView, { - let range = RANGE.with(|range| (*range).clone()); + let range = RANGE.with(|range| (*range).clone()); - // Resize children if needed - if cmds.added.len().checked_sub(cmds.removed.len()).is_some() { - let target_size = children.len() - + (cmds.added.len() as isize - cmds.removed.len() as isize) as usize; + // Resize children if needed + if cmds.added.len().checked_sub(cmds.removed.len()).is_some() { + let target_size = children.len() + + (cmds.added.len() as isize - cmds.removed.len() as isize) + as usize; - children.resize_with(target_size, || None); - } + children.resize_with(target_size, || None); + } - // We need to hold a list of items which will be moved, and - // we can only perform the move after all commands have run, otherwise, - // we risk overwriting one of the values - let mut items_to_move = Vec::with_capacity(cmds.moved.len()); + // We need to hold a list of items which will be moved, and + // we can only perform the move after all commands have run, otherwise, + // we risk overwriting one of the values + let mut items_to_move = Vec::with_capacity(cmds.moved.len()); - // The order of cmds needs to be: - // 1. Clear - // 2. Removed - // 3. Moved - // 4. Add - if cmds.clear { - cmds.removed.clear(); + // The order of cmds needs to be: + // 1. Clear + // 2. Removed + // 3. Moved + // 4. Add + if cmds.clear { + cmds.removed.clear(); - if opening.previous_sibling().is_none() && closing.next_sibling().is_none() + if opening.previous_sibling().is_none() + && closing.next_sibling().is_none() + { + let parent = closing + .parent_node() + .expect("could not get closing node") + .unchecked_into::(); + parent.set_text_content(Some("")); + + #[cfg(debug_assertions)] + parent.append_with_node_2(opening, closing).unwrap(); + + #[cfg(not(debug_assertions))] + parent.append_with_node_1(closing).unwrap(); + } else { + range.set_start_before(opening).unwrap(); + range.set_end_before(closing).unwrap(); + + range.delete_contents().unwrap(); + } + } + + for DiffOpRemove { at } in cmds.removed { + let item_to_remove = std::mem::take(&mut children[at]).unwrap(); + + item_to_remove.prepare_for_move(); + } + + for DiffOpMove { + from, + to, + move_in_dom, + } in cmds.moved { - let parent = closing - .parent_node() - .expect("could not get closing node") - .unchecked_into::(); - parent.set_text_content(Some("")); + let item = std::mem::take(&mut children[from]).unwrap(); - #[cfg(debug_assertions)] - parent.append_with_node_2(opening, closing).unwrap(); + if move_in_dom { + item.prepare_for_move() + } - #[cfg(not(debug_assertions))] - parent.append_with_node_1(closing).unwrap(); - } else { - range.set_start_before(opening).unwrap(); - range.set_end_before(closing).unwrap(); - - range.delete_contents().unwrap(); - } - } - - for DiffOpRemove { at } in cmds.removed { - let item_to_remove = std::mem::take(&mut children[at]).unwrap(); - - item_to_remove.prepare_for_move(); - } - - for DiffOpMove { - from, - to, - move_in_dom, - } in cmds.moved - { - let item = std::mem::take(&mut children[from]).unwrap(); - - if move_in_dom { - item.prepare_for_move() + items_to_move.push((move_in_dom, to, item)); } - items_to_move.push((move_in_dom, to, item)); - } + for DiffOpAdd { at, mode } in cmds.added { + let item = items[at].take().unwrap(); - for DiffOpAdd { at, mode } in cmds.added { - let item = items[at].take().unwrap(); + let (each_item, _) = cx.run_child_scope(|cx| { + let child = each_fn(cx, item).into_view(cx); + EachItem::new(cx, child) + }); - let (each_item, _) = cx.run_child_scope(|cx| { - let child = each_fn(cx, item).into_view(cx); - EachItem::new(cx, child) - }); + match mode { + DiffOpAddMode::Normal => { + let opening = children.get_next_closest_mounted_sibling( + at + 1, + closing.to_owned(), + ); - match mode { - DiffOpAddMode::Normal => { - let opening = - children.get_next_closest_mounted_sibling(at + 1, closing.to_owned()); + mount_child(MountKind::Before(&opening), &each_item); + } + DiffOpAddMode::Append => { + mount_child(MountKind::Before(closing), &each_item); + } + DiffOpAddMode::_Prepend => todo!(), + } - mount_child(MountKind::Before(&opening), &each_item); - } - DiffOpAddMode::Append => { - mount_child(MountKind::Before(closing), &each_item); - } - DiffOpAddMode::_Prepend => todo!(), + children[at] = Some(each_item); } - children[at] = Some(each_item); - } + for (move_in_dom, to, each_item) in items_to_move { + if move_in_dom { + let opening = children + .get_next_closest_mounted_sibling(to + 1, closing.to_owned()); - for (move_in_dom, to, each_item) in items_to_move { - if move_in_dom { - let opening = - children.get_next_closest_mounted_sibling(to + 1, closing.to_owned()); + mount_child(MountKind::Before(&opening), &each_item); + } - mount_child(MountKind::Before(&opening), &each_item); + children[to] = Some(each_item); } - children[to] = Some(each_item); - } - - // Now, remove the holes that might have been left from removing - // items - #[allow(unstable_name_collisions)] - children.drain_filter(|c| c.is_none()); + // Now, remove the holes that might have been left from removing + // items + #[allow(unstable_name_collisions)] + children.drain_filter(|c| c.is_none()); } diff --git a/leptos_dom/src/components/errors.rs b/leptos_dom/src/components/errors.rs index e155be0a0..bf8c84182 100644 --- a/leptos_dom/src/components/errors.rs +++ b/leptos_dom/src/components/errors.rs @@ -9,79 +9,83 @@ pub struct Errors(pub HashMap>); impl IntoView for Result where - T: IntoView + 'static, - E: Error + Send + Sync + 'static, + T: IntoView + 'static, + E: Error + Send + Sync + 'static, { - fn into_view(self, cx: leptos_reactive::Scope) -> crate::View { - let id = HydrationCtx::peek().previous; - let errors = use_context::>(cx); - match self { - Ok(stuff) => { - if let Some(errors) = errors { - errors.update(|errors| { - errors.0.remove(&id); - }); - } - stuff.into_view(cx) - } - Err(error) => { - match errors { - Some(errors) => { - errors.update({ - #[cfg(all(target_arch = "wasm32", feature = "web"))] - let id = id.clone(); - move |errors: &mut Errors| errors.insert(id, error) - }); - - // remove the error from the list if this drops, - // i.e., if it's in a DynChild that switches from Err to Ok - // Only can run on the client, will panic on the server - cfg_if! { - if #[cfg(all(target_arch = "wasm32", feature = "web"))] { - use leptos_reactive::{on_cleanup, queue_microtask}; - on_cleanup(cx, move || { - queue_microtask(move || { - errors.update(|errors: &mut Errors| { - errors.remove::(&id); + fn into_view(self, cx: leptos_reactive::Scope) -> crate::View { + let id = HydrationCtx::peek().previous; + let errors = use_context::>(cx); + match self { + Ok(stuff) => { + if let Some(errors) = errors { + errors.update(|errors| { + errors.0.remove(&id); }); - }); - }); - } + } + stuff.into_view(cx) + } + Err(error) => { + match errors { + Some(errors) => { + errors.update({ + #[cfg(all( + target_arch = "wasm32", + feature = "web" + ))] + let id = id.clone(); + move |errors: &mut Errors| errors.insert(id, error) + }); + + // remove the error from the list if this drops, + // i.e., if it's in a DynChild that switches from Err to Ok + // Only can run on the client, will panic on the server + cfg_if! { + if #[cfg(all(target_arch = "wasm32", feature = "web"))] { + use leptos_reactive::{on_cleanup, queue_microtask}; + on_cleanup(cx, move || { + queue_microtask(move || { + errors.update(|errors: &mut Errors| { + errors.remove::(&id); + }); + }); + }); + } + } + } + None => { + #[cfg(debug_assertions)] + warn!( + "No ErrorBoundary components found! Returning \ + errors will not be handled and will silently \ + disappear" + ); + } + } + ().into_view(cx) } - } - None => { - #[cfg(debug_assertions)] - warn!( - "No ErrorBoundary components found! Returning errors will not \ - be handled and will silently disappear" - ); - } } - ().into_view(cx) - } } - } } impl Errors { - /// Add an error to Errors that will be processed by `` - pub fn insert(&mut self, key: String, error: E) - where - E: Error + Send + Sync + 'static, - { - self.0.insert(key, Arc::new(error)); - } - /// Add an error with the default key for errors outside the reactive system - pub fn insert_with_default_key(&mut self, error: E) - where - E: Error + Send + Sync + 'static, - { - self.0.insert(String::new(), Arc::new(error)); - } - /// Remove an error to Errors that will be processed by `` - pub fn remove(&mut self, key: &str) - where - E: Error + Send + Sync + 'static, - { - self.0.remove(key); - } + /// Add an error to Errors that will be processed by `` + pub fn insert(&mut self, key: String, error: E) + where + E: Error + Send + Sync + 'static, + { + self.0.insert(key, Arc::new(error)); + } + /// Add an error with the default key for errors outside the reactive system + pub fn insert_with_default_key(&mut self, error: E) + where + E: Error + Send + Sync + 'static, + { + self.0.insert(String::new(), Arc::new(error)); + } + /// Remove an error to Errors that will be processed by `` + pub fn remove(&mut self, key: &str) + where + E: Error + Send + Sync + 'static, + { + self.0.remove(key); + } } diff --git a/leptos_dom/src/components/fragment.rs b/leptos_dom/src/components/fragment.rs index 543fc0994..540b8517f 100644 --- a/leptos_dom/src/components/fragment.rs +++ b/leptos_dom/src/components/fragment.rs @@ -1,79 +1,78 @@ -use leptos_reactive::Scope; - use crate::{ - hydration::HydrationKey, ComponentRepr, HydrationCtx, IntoView, View, + hydration::HydrationKey, ComponentRepr, HydrationCtx, IntoView, View, }; +use leptos_reactive::Scope; /// Trait for converting any iterable into a [`Fragment`]. pub trait IntoFragment { - /// Consumes this type, returning [`Fragment`]. - fn into_fragment(self, cx: Scope) -> Fragment; + /// Consumes this type, returning [`Fragment`]. + fn into_fragment(self, cx: Scope) -> Fragment; } impl IntoFragment for I where - I: IntoIterator, - V: IntoView, + I: IntoIterator, + V: IntoView, { - fn into_fragment(self, cx: Scope) -> Fragment { - self.into_iter().map(|v| v.into_view(cx)).collect() - } + fn into_fragment(self, cx: Scope) -> Fragment { + self.into_iter().map(|v| v.into_view(cx)).collect() + } } /// Represents a group of [`views`](View). #[derive(Debug, Clone)] pub struct Fragment { - id: HydrationKey, - /// The nodes contained in the fragment. - pub nodes: Vec, + id: HydrationKey, + /// The nodes contained in the fragment. + pub nodes: Vec, } impl FromIterator for Fragment { - fn from_iter>(iter: T) -> Self { - Fragment::new(iter.into_iter().collect()) - } + fn from_iter>(iter: T) -> Self { + Fragment::new(iter.into_iter().collect()) + } } impl From for Fragment { - fn from(view: View) -> Self { - Fragment::new(vec![view]) - } + fn from(view: View) -> Self { + Fragment::new(vec![view]) + } } impl Fragment { - /// Creates a new [`Fragment`] from a [`Vec`]. - pub fn new(nodes: Vec) -> Self { - Self::new_with_id(HydrationCtx::id(), nodes) - } + /// Creates a new [`Fragment`] from a [`Vec`]. + pub fn new(nodes: Vec) -> Self { + Self::new_with_id(HydrationCtx::id(), nodes) + } - /// Creates a new [`Fragment`] from a function that returns [`Vec`]. - pub fn lazy(nodes: impl FnOnce() -> Vec) -> Self { - Self::new_with_id(HydrationCtx::id(), nodes()) - } + /// Creates a new [`Fragment`] from a function that returns [`Vec`]. + pub fn lazy(nodes: impl FnOnce() -> Vec) -> Self { + Self::new_with_id(HydrationCtx::id(), nodes()) + } - /// Creates a new [`Fragment`] with the given hydration ID from a [`Vec`]. - pub fn new_with_id(id: HydrationKey, nodes: Vec) -> Self { - Self { id, nodes } - } + /// Creates a new [`Fragment`] with the given hydration ID from a [`Vec`]. + pub fn new_with_id(id: HydrationKey, nodes: Vec) -> Self { + Self { id, nodes } + } - /// Gives access to the [View] children contained within the fragment. - pub fn as_children(&self) -> &[View] { - &self.nodes - } + /// Gives access to the [View] children contained within the fragment. + pub fn as_children(&self) -> &[View] { + &self.nodes + } - /// Returns the fragment's hydration ID. - pub fn id(&self) -> &HydrationKey { - &self.id - } + /// Returns the fragment's hydration ID. + pub fn id(&self) -> &HydrationKey { + &self.id + } } impl IntoView for Fragment { - #[cfg_attr(debug_assertions, instrument(level = "trace", name = "", skip_all, fields(children = self.nodes.len())))] - fn into_view(self, cx: leptos_reactive::Scope) -> View { - let mut frag = ComponentRepr::new_with_id("", self.id.clone()); + #[cfg_attr(debug_assertions, instrument(level = "trace", name = "", skip_all, fields(children = self.nodes.len())))] + fn into_view(self, cx: leptos_reactive::Scope) -> View { + let mut frag = ComponentRepr::new_with_id("", self.id.clone()); - frag.children = self.nodes; + frag.children = self.nodes; - frag.into_view(cx) - } + frag.into_view(cx) + } } diff --git a/leptos_dom/src/components/unit.rs b/leptos_dom/src/components/unit.rs index 0a6f74acb..2b7188d48 100644 --- a/leptos_dom/src/components/unit.rs +++ b/leptos_dom/src/components/unit.rs @@ -15,42 +15,42 @@ use crate::{hydration::HydrationCtx, Comment, CoreComponent, IntoView, View}; /// The internal representation of the [`Unit`] core-component. #[derive(Clone, PartialEq, Eq)] pub struct UnitRepr { - comment: Comment, - #[cfg(not(all(target_arch = "wasm32", feature = "web")))] - pub(crate) id: HydrationKey, + comment: Comment, + #[cfg(not(all(target_arch = "wasm32", feature = "web")))] + pub(crate) id: HydrationKey, } impl fmt::Debug for UnitRepr { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("<() />") - } + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("<() />") + } } impl Default for UnitRepr { - fn default() -> Self { - let id = HydrationCtx::id(); + fn default() -> Self { + let id = HydrationCtx::id(); - Self { - comment: Comment::new("<() />", &id, true), - #[cfg(not(all(target_arch = "wasm32", feature = "web")))] - id, + Self { + comment: Comment::new("<() />", &id, true), + #[cfg(not(all(target_arch = "wasm32", feature = "web")))] + id, + } } - } } #[cfg(all(target_arch = "wasm32", feature = "web"))] impl Mountable for UnitRepr { - fn get_mountable_node(&self) -> web_sys::Node { - self.comment.node.clone().unchecked_into() - } + fn get_mountable_node(&self) -> web_sys::Node { + self.comment.node.clone().unchecked_into() + } - fn get_opening_node(&self) -> web_sys::Node { - self.comment.node.clone().unchecked_into() - } + fn get_opening_node(&self) -> web_sys::Node { + self.comment.node.clone().unchecked_into() + } - fn get_closing_node(&self) -> web_sys::Node { - self.comment.node.clone().unchecked_into() - } + fn get_closing_node(&self) -> web_sys::Node { + self.comment.node.clone().unchecked_into() + } } /// The unit `()` leptos counterpart. @@ -58,13 +58,13 @@ impl Mountable for UnitRepr { pub struct Unit; impl IntoView for Unit { - #[cfg_attr( - debug_assertions, - instrument(level = "trace", name = "<() />", skip_all) - )] - fn into_view(self, _: leptos_reactive::Scope) -> crate::View { - let component = UnitRepr::default(); + #[cfg_attr( + debug_assertions, + instrument(level = "trace", name = "<() />", skip_all) + )] + fn into_view(self, _: leptos_reactive::Scope) -> crate::View { + let component = UnitRepr::default(); - View::CoreComponent(CoreComponent::Unit(component)) - } + View::CoreComponent(CoreComponent::Unit(component)) + } } diff --git a/leptos_dom/src/events.rs b/leptos_dom/src/events.rs index 53a0eb027..71acb4968 100644 --- a/leptos_dom/src/events.rs +++ b/leptos_dom/src/events.rs @@ -3,8 +3,8 @@ pub mod typed; use std::{borrow::Cow, cell::RefCell, collections::HashSet}; #[cfg(all(target_arch = "wasm32", feature = "web"))] use wasm_bindgen::{ - convert::FromWasmAbi, intern, prelude::Closure, JsCast, JsValue, - UnwrapThrowExt, + convert::FromWasmAbi, intern, prelude::Closure, JsCast, JsValue, + UnwrapThrowExt, }; thread_local! { @@ -14,135 +14,141 @@ thread_local! { /// Adds an event listener to the target DOM element using implicit event delegation. #[cfg(all(target_arch = "wasm32", feature = "web"))] pub fn add_event_listener( - target: &web_sys::Element, - event_name: Cow<'static, str>, - #[cfg(debug_assertions)] mut cb: impl FnMut(E) + 'static, - #[cfg(not(debug_assertions))] cb: impl FnMut(E) + 'static, + target: &web_sys::Element, + event_name: Cow<'static, str>, + #[cfg(debug_assertions)] mut cb: impl FnMut(E) + 'static, + #[cfg(not(debug_assertions))] cb: impl FnMut(E) + 'static, ) where - E: FromWasmAbi + 'static, + E: FromWasmAbi + 'static, { - cfg_if::cfg_if! { - if #[cfg(debug_assertions)] { - let span = ::tracing::Span::current(); - let cb = move |e| { - let _guard = span.enter(); - cb(e); - }; + cfg_if::cfg_if! { + if #[cfg(debug_assertions)] { + let span = ::tracing::Span::current(); + let cb = move |e| { + let _guard = span.enter(); + cb(e); + }; + } } - } - let cb = Closure::wrap(Box::new(cb) as Box).into_js_value(); - let key = event_delegation_key(&event_name); - _ = js_sys::Reflect::set(target, &JsValue::from_str(&key), &cb); - add_delegated_event_listener(event_name); + let cb = Closure::wrap(Box::new(cb) as Box).into_js_value(); + let key = event_delegation_key(&event_name); + _ = js_sys::Reflect::set(target, &JsValue::from_str(&key), &cb); + add_delegated_event_listener(event_name); } #[doc(hidden)] #[cfg(all(target_arch = "wasm32", feature = "web"))] pub fn add_event_listener_undelegated( - target: &web_sys::Element, - event_name: &str, - mut cb: impl FnMut(E) + 'static, + target: &web_sys::Element, + event_name: &str, + mut cb: impl FnMut(E) + 'static, ) where - E: FromWasmAbi + 'static, + E: FromWasmAbi + 'static, { - cfg_if::cfg_if! { - if #[cfg(debug_assertions)] { - let span = ::tracing::Span::current(); - let cb = move |e| { - let _guard = span.enter(); - cb(e); - }; + cfg_if::cfg_if! { + if #[cfg(debug_assertions)] { + let span = ::tracing::Span::current(); + let cb = move |e| { + let _guard = span.enter(); + cb(e); + }; + } } - } - let event_name = intern(event_name); - let cb = Closure::wrap(Box::new(cb) as Box).into_js_value(); - _ = target.add_event_listener_with_callback(event_name, cb.unchecked_ref()); + let event_name = intern(event_name); + let cb = Closure::wrap(Box::new(cb) as Box).into_js_value(); + _ = target.add_event_listener_with_callback(event_name, cb.unchecked_ref()); } // cf eventHandler in ryansolid/dom-expressions #[cfg(all(target_arch = "wasm32", feature = "web"))] pub(crate) fn add_delegated_event_listener(event_name: Cow<'static, str>) { - GLOBAL_EVENTS.with(|global_events| { - let mut events = global_events.borrow_mut(); - if !events.contains(&event_name) { - // create global handler - let key = JsValue::from_str(&event_delegation_key(&event_name)); - let handler = move |ev: web_sys::Event| { - let target = ev.target(); - let node = ev.composed_path().get(0); - let mut node = if node.is_undefined() || node.is_null() { - JsValue::from(target) - } else { - node - }; + GLOBAL_EVENTS.with(|global_events| { + let mut events = global_events.borrow_mut(); + if !events.contains(&event_name) { + // create global handler + let key = JsValue::from_str(&event_delegation_key(&event_name)); + let handler = move |ev: web_sys::Event| { + let target = ev.target(); + let node = ev.composed_path().get(0); + let mut node = if node.is_undefined() || node.is_null() { + JsValue::from(target) + } else { + node + }; - // TODO reverse Shadow DOM retargetting + // TODO reverse Shadow DOM retargetting - // TODO simulate currentTarget + // TODO simulate currentTarget - while !node.is_null() { - let node_is_disabled = - js_sys::Reflect::get(&node, &JsValue::from_str("disabled")) - .unwrap_throw() - .is_truthy(); - if !node_is_disabled { - let maybe_handler = - js_sys::Reflect::get(&node, &key).unwrap_throw(); - if !maybe_handler.is_undefined() { - let f = maybe_handler.unchecked_ref::(); - let _ = f.call1(&node, &ev); + while !node.is_null() { + let node_is_disabled = js_sys::Reflect::get( + &node, + &JsValue::from_str("disabled"), + ) + .unwrap_throw() + .is_truthy(); + if !node_is_disabled { + let maybe_handler = + js_sys::Reflect::get(&node, &key).unwrap_throw(); + if !maybe_handler.is_undefined() { + let f = maybe_handler + .unchecked_ref::(); + let _ = f.call1(&node, &ev); - if ev.cancel_bubble() { - return; + if ev.cancel_bubble() { + return; + } + } + } + + // navigate up tree + let host = + js_sys::Reflect::get(&node, &JsValue::from_str("host")) + .unwrap_throw(); + if host.is_truthy() + && host != node + && host.dyn_ref::().is_some() + { + node = host; + } else if let Some(parent) = + node.unchecked_into::().parent_node() + { + node = parent.into() + } else { + node = JsValue::null() + } + } + }; + + cfg_if::cfg_if! { + if #[cfg(debug_assertions)] { + let span = ::tracing::Span::current(); + let handler = move |e| { + let _guard = span.enter(); + handler(e); + }; } } - } - // navigate up tree - let host = js_sys::Reflect::get(&node, &JsValue::from_str("host")) - .unwrap_throw(); - if host.is_truthy() - && host != node - && host.dyn_ref::().is_some() - { - node = host; - } else if let Some(parent) = - node.unchecked_into::().parent_node() - { - node = parent.into() - } else { - node = JsValue::null() - } + let handler = Box::new(handler) as Box; + let handler = Closure::wrap(handler).into_js_value(); + _ = crate::window().add_event_listener_with_callback( + &event_name, + handler.unchecked_ref(), + ); + + // register that we've created handler + events.insert(event_name); } - }; - - cfg_if::cfg_if! { - if #[cfg(debug_assertions)] { - let span = ::tracing::Span::current(); - let handler = move |e| { - let _guard = span.enter(); - handler(e); - }; - } - } - - let handler = Box::new(handler) as Box; - let handler = Closure::wrap(handler).into_js_value(); - _ = crate::window() - .add_event_listener_with_callback(&event_name, handler.unchecked_ref()); - - // register that we've created handler - events.insert(event_name); - } - }) + }) } #[cfg(all(target_arch = "wasm32", feature = "web"))] pub(crate) fn event_delegation_key(event_name: &str) -> String { - let event_name = intern(event_name); - let mut n = String::from("$$$"); - n.push_str(event_name); - n + let event_name = intern(event_name); + let mut n = String::from("$$$"); + n.push_str(event_name); + n } diff --git a/leptos_dom/src/events/typed.rs b/leptos_dom/src/events/typed.rs index c89564c17..d8618a1b0 100644 --- a/leptos_dom/src/events/typed.rs +++ b/leptos_dom/src/events/typed.rs @@ -5,20 +5,20 @@ use wasm_bindgen::convert::FromWasmAbi; /// A trait for converting types into [web_sys events](web_sys). pub trait EventDescriptor: Clone { - /// The [`web_sys`] event type, such as [`web_sys::MouseEvent`]. - type EventType: FromWasmAbi; + /// The [`web_sys`] event type, such as [`web_sys::MouseEvent`]. + type EventType: FromWasmAbi; - /// The name of the event, such as `click` or `mouseover`. - fn name(&self) -> Cow<'static, str>; + /// The name of the event, such as `click` or `mouseover`. + fn name(&self) -> Cow<'static, str>; - /// Indicates if this event bubbles. For example, `click` bubbles, - /// but `focus` does not. - /// - /// If this method returns true, then the event will be delegated globally, - /// otherwise, event listeners will be directly attached to the element. - fn bubbles(&self) -> bool { - true - } + /// Indicates if this event bubbles. For example, `click` bubbles, + /// but `focus` does not. + /// + /// If this method returns true, then the event will be delegated globally, + /// otherwise, event listeners will be directly attached to the element. + fn bubbles(&self) -> bool { + true + } } /// Overrides the [`EventDescriptor::bubbles`] method to always return @@ -28,54 +28,54 @@ pub trait EventDescriptor: Clone { pub struct undelegated(pub Ev); impl EventDescriptor for undelegated { - type EventType = Ev::EventType; + type EventType = Ev::EventType; - fn name(&self) -> Cow<'static, str> { - self.0.name() - } + fn name(&self) -> Cow<'static, str> { + self.0.name() + } - fn bubbles(&self) -> bool { - false - } + fn bubbles(&self) -> bool { + false + } } /// A custom event. pub struct Custom { - name: Cow<'static, str>, - _event_type: PhantomData, + name: Cow<'static, str>, + _event_type: PhantomData, } impl Clone for Custom { - fn clone(&self) -> Self { - Self { - name: self.name.clone(), - _event_type: PhantomData, + fn clone(&self) -> Self { + Self { + name: self.name.clone(), + _event_type: PhantomData, + } } - } } impl EventDescriptor for Custom { - type EventType = E; + type EventType = E; - fn name(&self) -> Cow<'static, str> { - self.name.clone() - } + fn name(&self) -> Cow<'static, str> { + self.name.clone() + } - fn bubbles(&self) -> bool { - false - } + fn bubbles(&self) -> bool { + false + } } impl Custom { - /// Creates a custom event type that can be used within - /// [`HtmlElement::on`](crate::HtmlElement::on), for events - /// which are not covered in the [`ev`](crate::ev) module. - pub fn new(name: impl Into>) -> Self { - Self { - name: name.into(), - _event_type: PhantomData, + /// Creates a custom event type that can be used within + /// [`HtmlElement::on`](crate::HtmlElement::on), for events + /// which are not covered in the [`ev`](crate::ev) module. + pub fn new(name: impl Into>) -> Self { + Self { + name: name.into(), + _event_type: PhantomData, + } } - } } macro_rules! generate_event_types { diff --git a/leptos_dom/src/helpers.rs b/leptos_dom/src/helpers.rs index 407dfb367..2afd2a571 100644 --- a/leptos_dom/src/helpers.rs +++ b/leptos_dom/src/helpers.rs @@ -4,53 +4,53 @@ use wasm_bindgen::{prelude::Closure, JsCast, JsValue, UnwrapThrowExt}; /// Sets a property on a DOM element. pub fn set_property( - el: &web_sys::Element, - prop_name: &str, - value: &Option, + el: &web_sys::Element, + prop_name: &str, + value: &Option, ) { - let key = JsValue::from_str(prop_name); - match value { - Some(value) => _ = js_sys::Reflect::set(el, &key, value), - None => _ = js_sys::Reflect::delete_property(el, &key), - }; + let key = JsValue::from_str(prop_name); + match value { + Some(value) => _ = js_sys::Reflect::set(el, &key, value), + None => _ = js_sys::Reflect::delete_property(el, &key), + }; } /// Gets the value of a property set on a DOM element. pub fn get_property( - el: &web_sys::Element, - prop_name: &str, + el: &web_sys::Element, + prop_name: &str, ) -> Result { - let key = JsValue::from_str(prop_name); - js_sys::Reflect::get(el, &key) + let key = JsValue::from_str(prop_name); + js_sys::Reflect::get(el, &key) } /// Returns the current [`window.location`](https://developer.mozilla.org/en-US/docs/Web/API/Window/location). pub fn location() -> web_sys::Location { - window().location() + window().location() } /// Current [`window.location.hash`](https://developer.mozilla.org/en-US/docs/Web/API/Window/location) /// without the beginning #. pub fn location_hash() -> Option { - if is_server() { - None - } else { - location().hash().ok().map(|hash| hash.replace('#', "")) - } + if is_server() { + None + } else { + location().hash().ok().map(|hash| hash.replace('#', "")) + } } /// Current [`window.location.pathname`](https://developer.mozilla.org/en-US/docs/Web/API/Window/location). pub fn location_pathname() -> Option { - location().pathname().ok() + location().pathname().ok() } /// Helper function to extract [`Event.target`](https://developer.mozilla.org/en-US/docs/Web/API/Event/target) /// from any event. pub fn event_target(event: &web_sys::Event) -> T where - T: JsCast, + T: JsCast, { - event.target().unwrap_throw().unchecked_into::() + event.target().unwrap_throw().unchecked_into::() } /// Helper function to extract `event.target.value` from an event. @@ -58,60 +58,60 @@ where /// This is useful in the `on:input` or `on:change` listeners for an `` element. pub fn event_target_value(event: &T) -> String where - T: JsCast, + T: JsCast, { - event - .unchecked_ref::() - .target() - .unwrap_throw() - .unchecked_into::() - .value() + event + .unchecked_ref::() + .target() + .unwrap_throw() + .unchecked_into::() + .value() } /// Helper function to extract `event.target.checked` from an event. /// /// This is useful in the `on:change` listeners for an `` element. pub fn event_target_checked(ev: &web_sys::Event) -> bool { - ev.target() - .unwrap() - .unchecked_into::() - .checked() + ev.target() + .unwrap() + .unchecked_into::() + .checked() } /// Runs the given function between the next repaint /// using [`Window.requestAnimationFrame`](https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame). #[cfg_attr(debug_assertions, instrument(level = "trace", skip_all))] pub fn request_animation_frame(cb: impl FnOnce() + 'static) { - cfg_if::cfg_if! { - if #[cfg(debug_assertions)] { - let span = ::tracing::Span::current(); - let cb = move || { - let _guard = span.enter(); - cb(); - }; + cfg_if::cfg_if! { + if #[cfg(debug_assertions)] { + let span = ::tracing::Span::current(); + let cb = move || { + let _guard = span.enter(); + cb(); + }; + } } - } - let cb = Closure::once_into_js(cb); - _ = window().request_animation_frame(cb.as_ref().unchecked_ref()); + let cb = Closure::once_into_js(cb); + _ = window().request_animation_frame(cb.as_ref().unchecked_ref()); } /// Queues the given function during an idle period /// using [`Window.requestIdleCallback`](https://developer.mozilla.org/en-US/docs/Web/API/window/requestIdleCallback). #[cfg_attr(debug_assertions, instrument(level = "trace", skip_all))] pub fn request_idle_callback(cb: impl Fn() + 'static) { - cfg_if::cfg_if! { - if #[cfg(debug_assertions)] { - let span = ::tracing::Span::current(); - let cb = move || { - let _guard = span.enter(); - cb(); - }; + cfg_if::cfg_if! { + if #[cfg(debug_assertions)] { + let span = ::tracing::Span::current(); + let cb = move || { + let _guard = span.enter(); + cb(); + }; + } } - } - let cb = Closure::wrap(Box::new(cb) as Box).into_js_value(); - _ = window().request_idle_callback(cb.as_ref().unchecked_ref()); + let cb = Closure::wrap(Box::new(cb) as Box).into_js_value(); + _ = window().request_idle_callback(cb.as_ref().unchecked_ref()); } /// Executes the given function after the given duration of time has passed. @@ -121,21 +121,21 @@ pub fn request_idle_callback(cb: impl Fn() + 'static) { instrument(level = "trace", skip_all, fields(duration = ?duration)) )] pub fn set_timeout(cb: impl FnOnce() + 'static, duration: Duration) { - cfg_if::cfg_if! { - if #[cfg(debug_assertions)] { - let span = ::tracing::Span::current(); - let cb = move || { - let _guard = span.enter(); - cb(); - }; + cfg_if::cfg_if! { + if #[cfg(debug_assertions)] { + let span = ::tracing::Span::current(); + let cb = move || { + let _guard = span.enter(); + cb(); + }; + } } - } - let cb = Closure::once_into_js(Box::new(cb) as Box); - _ = window().set_timeout_with_callback_and_timeout_and_arguments_0( - cb.as_ref().unchecked_ref(), - duration.as_millis().try_into().unwrap_throw(), - ); + let cb = Closure::once_into_js(Box::new(cb) as Box); + _ = window().set_timeout_with_callback_and_timeout_and_arguments_0( + cb.as_ref().unchecked_ref(), + duration.as_millis().try_into().unwrap_throw(), + ); } /// Handle that is generated by [set_interval] and can be used to clear the interval. @@ -143,11 +143,11 @@ pub fn set_timeout(cb: impl FnOnce() + 'static, duration: Duration) { pub struct IntervalHandle(i32); impl IntervalHandle { - /// Cancels the repeating event to which this refers. - /// See [`clearInterval()`](https://developer.mozilla.org/en-US/docs/Web/API/clearInterval) - pub fn clear(&self) { - window().clear_interval_with_handle(self.0); - } + /// Cancels the repeating event to which this refers. + /// See [`clearInterval()`](https://developer.mozilla.org/en-US/docs/Web/API/clearInterval) + pub fn clear(&self) { + window().clear_interval_with_handle(self.0); + } } /// Repeatedly calls the given function, with a delay of the given duration between calls. @@ -157,26 +157,26 @@ impl IntervalHandle { instrument(level = "trace", skip_all, fields(duration = ?duration)) )] pub fn set_interval( - cb: impl Fn() + 'static, - duration: Duration, + cb: impl Fn() + 'static, + duration: Duration, ) -> Result { - cfg_if::cfg_if! { - if #[cfg(debug_assertions)] { - let span = ::tracing::Span::current(); - let cb = move || { - let _guard = span.enter(); - cb(); - }; + cfg_if::cfg_if! { + if #[cfg(debug_assertions)] { + let span = ::tracing::Span::current(); + let cb = move || { + let _guard = span.enter(); + cb(); + }; + } } - } - let cb = Closure::wrap(Box::new(cb) as Box).into_js_value(); - let handle = window() - .set_interval_with_callback_and_timeout_and_arguments_0( - cb.as_ref().unchecked_ref(), - duration.as_millis().try_into().unwrap_throw(), - )?; - Ok(IntervalHandle(handle)) + let cb = Closure::wrap(Box::new(cb) as Box).into_js_value(); + let handle = window() + .set_interval_with_callback_and_timeout_and_arguments_0( + cb.as_ref().unchecked_ref(), + duration.as_millis().try_into().unwrap_throw(), + )?; + Ok(IntervalHandle(handle)) } /// Adds an event listener to the `Window`. @@ -185,34 +185,34 @@ pub fn set_interval( instrument(level = "trace", skip_all, fields(event_name = %event_name)) )] pub fn window_event_listener( - event_name: &str, - cb: impl Fn(web_sys::Event) + 'static, + event_name: &str, + cb: impl Fn(web_sys::Event) + 'static, ) { - cfg_if::cfg_if! { - if #[cfg(debug_assertions)] { - let span = ::tracing::Span::current(); - let cb = move |e| { - let _guard = span.enter(); - cb(e); - }; + cfg_if::cfg_if! { + if #[cfg(debug_assertions)] { + let span = ::tracing::Span::current(); + let cb = move |e| { + let _guard = span.enter(); + cb(e); + }; + } } - } - if !is_server() { - let handler = Box::new(cb) as Box; + if !is_server() { + let handler = Box::new(cb) as Box; - let cb = Closure::wrap(handler).into_js_value(); - _ = - window().add_event_listener_with_callback(event_name, cb.unchecked_ref()); - } + let cb = Closure::wrap(handler).into_js_value(); + _ = window() + .add_event_listener_with_callback(event_name, cb.unchecked_ref()); + } } #[doc(hidden)] /// This exists only to enable type inference on event listeners when in SSR mode. pub fn ssr_event_listener( - event: E, - event_handler: impl FnMut(E::EventType) + 'static, + event: E, + event_handler: impl FnMut(E::EventType) + 'static, ) { - _ = event; - _ = event_handler; + _ = event; + _ = event_handler; } diff --git a/leptos_dom/src/html.rs b/leptos_dom/src/html.rs index 9929e9b8b..6b37182f5 100644 --- a/leptos_dom/src/html.rs +++ b/leptos_dom/src/html.rs @@ -40,210 +40,214 @@ cfg_if! { } use crate::{ - ev::EventDescriptor, - hydration::HydrationCtx, - macro_helpers::{IntoAttribute, IntoClass, IntoProperty}, - Element, Fragment, IntoView, NodeRef, Text, View, + ev::EventDescriptor, + hydration::HydrationCtx, + macro_helpers::{IntoAttribute, IntoClass, IntoProperty}, + Element, Fragment, IntoView, NodeRef, Text, View, }; use leptos_reactive::Scope; use std::{borrow::Cow, fmt}; /// Trait which allows creating an element tag. pub trait ElementDescriptor: ElementDescriptorBounds { - /// The name of the element, i.e., `div`, `p`, `custom-element`. - fn name(&self) -> Cow<'static, str>; + /// The name of the element, i.e., `div`, `p`, `custom-element`. + fn name(&self) -> Cow<'static, str>; - /// Determains if the tag is void, i.e., `` and `
`. - fn is_void(&self) -> bool { - false - } + /// Determains if the tag is void, i.e., `` and `
`. + fn is_void(&self) -> bool { + false + } - /// A unique `id` that should be generated for each new instance of - /// this element, and be consistant for both SSR and CSR. - #[cfg(not(all(target_arch = "wasm32", feature = "web")))] - fn hydration_id(&self) -> &HydrationKey; + /// A unique `id` that should be generated for each new instance of + /// this element, and be consistant for both SSR and CSR. + #[cfg(not(all(target_arch = "wasm32", feature = "web")))] + fn hydration_id(&self) -> &HydrationKey; } /// Trait for converting any type which impl [`AsRef`] /// to [`HtmlElement`]. pub trait ToHtmlElement { - /// Converts the type to [`HtmlElement`]. - fn to_leptos_element(self, cx: Scope) -> HtmlElement; + /// Converts the type to [`HtmlElement`]. + fn to_leptos_element(self, cx: Scope) -> HtmlElement; } impl ToHtmlElement for T where - T: AsRef, + T: AsRef, { - fn to_leptos_element(self, cx: Scope) -> HtmlElement { - #[cfg(all(target_arch = "wasm32", feature = "web"))] - { - let el = self.as_ref().clone().unchecked_into(); + fn to_leptos_element(self, cx: Scope) -> HtmlElement { + #[cfg(all(target_arch = "wasm32", feature = "web"))] + { + let el = self.as_ref().clone().unchecked_into(); - let element = AnyElement { - name: "".into(), - is_void: false, - element: el, - }; + let element = AnyElement { + name: "".into(), + is_void: false, + element: el, + }; - HtmlElement { - cx, - element, - #[cfg(debug_assertions)] - span: ::tracing::Span::current(), - } + HtmlElement { + cx, + element, + #[cfg(debug_assertions)] + span: ::tracing::Span::current(), + } + } + + #[cfg(not(all(target_arch = "wasm32", feature = "web")))] + { + let _ = cx; + + unreachable!(); + } } - - #[cfg(not(all(target_arch = "wasm32", feature = "web")))] - { - let _ = cx; - - unreachable!(); - } - } } /// Represents potentially any element. #[derive(Clone, Debug)] pub struct AnyElement { - pub(crate) name: Cow<'static, str>, - #[cfg(all(target_arch = "wasm32", feature = "web"))] - pub(crate) element: web_sys::HtmlElement, - pub(crate) is_void: bool, - #[cfg(not(all(target_arch = "wasm32", feature = "web")))] - pub(crate) id: HydrationKey, + pub(crate) name: Cow<'static, str>, + #[cfg(all(target_arch = "wasm32", feature = "web"))] + pub(crate) element: web_sys::HtmlElement, + pub(crate) is_void: bool, + #[cfg(not(all(target_arch = "wasm32", feature = "web")))] + pub(crate) id: HydrationKey, } impl std::ops::Deref for AnyElement { - type Target = web_sys::HtmlElement; + type Target = web_sys::HtmlElement; - fn deref(&self) -> &Self::Target { - #[cfg(all(target_arch = "wasm32", feature = "web"))] - return &self.element; + fn deref(&self) -> &Self::Target { + #[cfg(all(target_arch = "wasm32", feature = "web"))] + return &self.element; - #[cfg(not(all(target_arch = "wasm32", feature = "web")))] - unimplemented!("{HTML_ELEMENT_DEREF_UNIMPLEMENTED_MSG}"); - } + #[cfg(not(all(target_arch = "wasm32", feature = "web")))] + unimplemented!("{HTML_ELEMENT_DEREF_UNIMPLEMENTED_MSG}"); + } } impl std::convert::AsRef for AnyElement { - fn as_ref(&self) -> &web_sys::HtmlElement { - #[cfg(all(target_arch = "wasm32", feature = "web"))] - return &self.element; + fn as_ref(&self) -> &web_sys::HtmlElement { + #[cfg(all(target_arch = "wasm32", feature = "web"))] + return &self.element; - #[cfg(not(all(target_arch = "wasm32", feature = "web")))] - unimplemented!("{HTML_ELEMENT_DEREF_UNIMPLEMENTED_MSG}"); - } + #[cfg(not(all(target_arch = "wasm32", feature = "web")))] + unimplemented!("{HTML_ELEMENT_DEREF_UNIMPLEMENTED_MSG}"); + } } impl ElementDescriptor for AnyElement { - fn name(&self) -> Cow<'static, str> { - self.name.clone() - } + fn name(&self) -> Cow<'static, str> { + self.name.clone() + } - fn is_void(&self) -> bool { - self.is_void - } + fn is_void(&self) -> bool { + self.is_void + } - #[cfg(not(all(target_arch = "wasm32", feature = "web")))] - fn hydration_id(&self) -> &HydrationKey { - &self.id - } + #[cfg(not(all(target_arch = "wasm32", feature = "web")))] + fn hydration_id(&self) -> &HydrationKey { + &self.id + } } /// Represents a custom HTML element, such as ``. #[derive(Clone, Debug)] pub struct Custom { - name: Cow<'static, str>, - #[cfg(all(target_arch = "wasm32", feature = "web"))] - element: web_sys::HtmlElement, - #[cfg(not(all(target_arch = "wasm32", feature = "web")))] - id: HydrationKey, + name: Cow<'static, str>, + #[cfg(all(target_arch = "wasm32", feature = "web"))] + element: web_sys::HtmlElement, + #[cfg(not(all(target_arch = "wasm32", feature = "web")))] + id: HydrationKey, } impl Custom { - pub fn new(name: impl Into>) -> Self { - let name = name.into(); - let id = HydrationCtx::id(); + pub fn new(name: impl Into>) -> Self { + let name = name.into(); + let id = HydrationCtx::id(); - #[cfg(all(target_arch = "wasm32", feature = "web"))] - let element = if HydrationCtx::is_hydrating() { - if let Some(el) = crate::document().get_element_by_id(&format!("_{id}")) { - #[cfg(debug_assertions)] - assert_eq!( - el.node_name().to_ascii_uppercase(), - name.to_ascii_uppercase(), - "SSR and CSR elements have the same `TopoId` but different node \ - kinds. This is either a discrepancy between SSR and CSR rendering + #[cfg(all(target_arch = "wasm32", feature = "web"))] + let element = if HydrationCtx::is_hydrating() { + if let Some(el) = + crate::document().get_element_by_id(&format!("_{id}")) + { + #[cfg(debug_assertions)] + assert_eq!( + el.node_name().to_ascii_uppercase(), + name.to_ascii_uppercase(), + "SSR and CSR elements have the same `TopoId` but \ + different node kinds. This is either a discrepancy \ + between SSR and CSR rendering logic, which is considered a bug, or it can also be a \ - leptos hydration issue." - ); + leptos hydration issue." + ); - el.remove_attribute("id").unwrap(); + el.remove_attribute("id").unwrap(); - el.unchecked_into() - } else if let Ok(Some(el)) = - crate::document().query_selector(&format!("[leptos-hk=_{id}]")) - { - #[cfg(debug_assertions)] - assert_eq!( - el.node_name().to_ascii_uppercase(), - name.to_ascii_uppercase(), - "SSR and CSR elements have the same `TopoId` but different node \ - kinds. This is either a discrepancy between SSR and CSR rendering + el.unchecked_into() + } else if let Ok(Some(el)) = + crate::document().query_selector(&format!("[leptos-hk=_{id}]")) + { + #[cfg(debug_assertions)] + assert_eq!( + el.node_name().to_ascii_uppercase(), + name.to_ascii_uppercase(), + "SSR and CSR elements have the same `TopoId` but \ + different node kinds. This is either a discrepancy \ + between SSR and CSR rendering logic, which is considered a bug, or it can also be a \ - leptos hydration issue." - ); + leptos hydration issue." + ); - el.remove_attribute("leptos-hk").unwrap(); + el.remove_attribute("leptos-hk").unwrap(); - el.unchecked_into() - } else { - crate::warn!( - "element with id {id} not found, ignoring it for hydration" - ); + el.unchecked_into() + } else { + crate::warn!( + "element with id {id} not found, ignoring it for hydration" + ); - crate::document().create_element(&name).unwrap() - } - } else { - crate::document().create_element(&name).unwrap() - }; + crate::document().create_element(&name).unwrap() + } + } else { + crate::document().create_element(&name).unwrap() + }; - Self { - name, - #[cfg(all(target_arch = "wasm32", feature = "web"))] - element: element.unchecked_into(), - #[cfg(not(all(target_arch = "wasm32", feature = "web")))] - id, + Self { + name, + #[cfg(all(target_arch = "wasm32", feature = "web"))] + element: element.unchecked_into(), + #[cfg(not(all(target_arch = "wasm32", feature = "web")))] + id, + } } - } } #[cfg(all(target_arch = "wasm32", feature = "web"))] impl std::ops::Deref for Custom { - type Target = web_sys::HtmlElement; + type Target = web_sys::HtmlElement; - fn deref(&self) -> &Self::Target { - &self.element - } + fn deref(&self) -> &Self::Target { + &self.element + } } #[cfg(all(target_arch = "wasm32", feature = "web"))] impl std::convert::AsRef for Custom { - fn as_ref(&self) -> &web_sys::HtmlElement { - &self.element - } + fn as_ref(&self) -> &web_sys::HtmlElement { + &self.element + } } impl ElementDescriptor for Custom { - fn name(&self) -> Cow<'static, str> { - self.name.clone() - } + fn name(&self) -> Cow<'static, str> { + self.name.clone() + } - #[cfg(not(all(target_arch = "wasm32", feature = "web")))] - fn hydration_id(&self) -> &HydrationKey { - &self.id - } + #[cfg(not(all(target_arch = "wasm32", feature = "web")))] + fn hydration_id(&self) -> &HydrationKey { + &self.id + } } cfg_if! { @@ -277,501 +281,504 @@ cfg_if! { impl std::ops::Deref for HtmlElement where - El: ElementDescriptor + std::ops::Deref, + El: ElementDescriptor + std::ops::Deref, { - type Target = ::Target; + type Target = ::Target; - fn deref(&self) -> &Self::Target { - #[cfg(all(target_arch = "wasm32", feature = "web"))] - return self.element.deref(); + fn deref(&self) -> &Self::Target { + #[cfg(all(target_arch = "wasm32", feature = "web"))] + return self.element.deref(); - #[cfg(not(all(target_arch = "wasm32", feature = "web")))] - unimplemented!("{HTML_ELEMENT_DEREF_UNIMPLEMENTED_MSG}"); - } + #[cfg(not(all(target_arch = "wasm32", feature = "web")))] + unimplemented!("{HTML_ELEMENT_DEREF_UNIMPLEMENTED_MSG}"); + } } impl HtmlElement { - fn new(cx: Scope, element: El) -> Self { - cfg_if! { - if #[cfg(all(target_arch = "wasm32", feature = "web"))] { - Self { - cx, - element, - #[cfg(debug_assertions)] - span: ::tracing::Span::current() - } - } else { - Self { - cx, - attrs: smallvec![], - children: smallvec![], - element, - prerendered: None - } - } - } - } - - #[doc(hidden)] - #[cfg(not(all(target_arch = "wasm32", feature = "web")))] - pub fn from_html( - cx: Scope, - element: El, - html: impl Into>, - ) -> Self { - Self { - cx, - attrs: smallvec![], - children: smallvec![], - element, - prerendered: Some(html.into()), - } - } - - /// Converts this element into [`HtmlElement`]. - pub fn into_any(self) -> HtmlElement { - cfg_if! { - if #[cfg(all(target_arch = "wasm32", feature = "web"))] { - let Self { - cx, - element, - #[cfg(debug_assertions)] - span - } = self; - - HtmlElement { - cx, - element: AnyElement { - name: element.name(), - element: element.as_ref().clone(), - is_void: element.is_void(), - }, - #[cfg(debug_assertions)] - span - } - } else { - let Self { - cx, - attrs, - children, - element, - prerendered - } = self; - - HtmlElement { - cx, - attrs, - children, - prerendered, - element: AnyElement { - name: element.name(), - is_void: element.is_void(), - id: element.hydration_id().clone(), - }, - } - } - } - } - - /// Adds an `id` to the element. - #[track_caller] - pub fn id(self, id: impl Into>) -> Self { - let id = id.into(); - - #[cfg(all(target_arch = "wasm32", feature = "web"))] - { - self - .element - .as_ref() - .set_attribute(wasm_bindgen::intern("id"), &id) - .unwrap(); - - self - } - - #[cfg(not(all(target_arch = "wasm32", feature = "web")))] - { - let mut this = self; - - this.attrs.push(("id".into(), id)); - - this - } - } - - /// Binds the element reference to [`NodeRef`]. - pub fn node_ref(self, node_ref: NodeRef) -> Self - where - Self: Clone, - { - #[cfg(all(target_arch = "wasm32", feature = "web"))] - node_ref.load(&self); - - #[cfg(not(all(target_arch = "wasm32", feature = "web")))] - let _ = node_ref; - - self - } - - /// Runs the callback when this element has been mounted to the DOM. - /// - /// ### Important Note - /// This method will only ever run at most once. If this element - /// is unmounted and remounted, or moved somewhere else, it will not - /// re-run unless you call this method again. - pub fn on_mount(self, f: impl FnOnce(Self) + 'static) -> Self { - #[cfg(all(target_arch = "wasm32", feature = "web"))] - { - use futures::future::poll_fn; - use once_cell::unsync::OnceCell; - use std::{ - cell::RefCell, - rc::Rc, - task::{Poll, Waker}, - }; - - let this = self.clone(); - let el = self.element.as_ref().clone(); - - wasm_bindgen_futures::spawn_local(async move { - while !crate::document().body().unwrap().contains(Some(&el)) { - // We need to cook ourselves a small future that resolves - // when the next animation frame is available - let waker = Rc::new(RefCell::new(None::)); - let ready = Rc::new(OnceCell::new()); - - crate::request_animation_frame({ - let waker = waker.clone(); - let ready = ready.clone(); - - move || { - let _ = ready.set(()); - if let Some(waker) = &*waker.borrow() { - waker.wake_by_ref(); - } + fn new(cx: Scope, element: El) -> Self { + cfg_if! { + if #[cfg(all(target_arch = "wasm32", feature = "web"))] { + Self { + cx, + element, + #[cfg(debug_assertions)] + span: ::tracing::Span::current() } - }); - - // Wait for the animation frame to become available - poll_fn(move |cx| { - let mut waker_borrow = waker.borrow_mut(); - - *waker_borrow = Some(cx.waker().clone()); - - if ready.get().is_some() { - Poll::Ready(()) - } else { - Poll::<()>::Pending + } else { + Self { + cx, + attrs: smallvec![], + children: smallvec![], + element, + prerendered: None } - }) - .await; - } - - f(this); - }); - } - - #[cfg(not(all(target_arch = "wasm32", feature = "web")))] - { - let _ = f; - } - self - } - - /// Adds an attribute to this element. - #[track_caller] - pub fn attr( - self, - name: impl Into>, - attr: impl IntoAttribute, - ) -> Self { - let name = name.into(); - - #[cfg(all(target_arch = "wasm32", feature = "web"))] - { - attribute_helper( - self.element.as_ref(), - name, - attr.into_attribute(self.cx), - ); - self - } - - #[cfg(not(all(target_arch = "wasm32", feature = "web")))] - { - use crate::macro_helpers::Attribute; - - let mut this = self; - - let mut attr = attr.into_attribute(this.cx); - while let Attribute::Fn(_, f) = attr { - attr = f(); - } - match attr { - Attribute::String(value) => { - this.attrs.push((name, value.into())); - } - Attribute::Bool(include) => { - if include { - this.attrs.push((name, "".into())); } } - Attribute::Option(_, maybe) => { - if let Some(value) = maybe { - this.attrs.push((name, value.into())); + } + + #[doc(hidden)] + #[cfg(not(all(target_arch = "wasm32", feature = "web")))] + pub fn from_html( + cx: Scope, + element: El, + html: impl Into>, + ) -> Self { + Self { + cx, + attrs: smallvec![], + children: smallvec![], + element, + prerendered: Some(html.into()), + } + } + + /// Converts this element into [`HtmlElement`]. + pub fn into_any(self) -> HtmlElement { + cfg_if! { + if #[cfg(all(target_arch = "wasm32", feature = "web"))] { + let Self { + cx, + element, + #[cfg(debug_assertions)] + span + } = self; + + HtmlElement { + cx, + element: AnyElement { + name: element.name(), + element: element.as_ref().clone(), + is_void: element.is_void(), + }, + #[cfg(debug_assertions)] + span + } + } else { + let Self { + cx, + attrs, + children, + element, + prerendered + } = self; + + HtmlElement { + cx, + attrs, + children, + prerendered, + element: AnyElement { + name: element.name(), + is_void: element.is_void(), + id: element.hydration_id().clone(), + }, + } } } - _ => unreachable!(), - } - - this - } - } - - /// Adds a class to an element. - #[track_caller] - pub fn class( - self, - name: impl Into>, - class: impl IntoClass, - ) -> Self { - let name = name.into(); - - #[cfg(all(target_arch = "wasm32", feature = "web"))] - { - let el = self.element.as_ref(); - let value = class.into_class(self.cx); - class_helper(el, name, value); - - self } - #[cfg(not(all(target_arch = "wasm32", feature = "web")))] - { - use crate::macro_helpers::Class; + /// Adds an `id` to the element. + #[track_caller] + pub fn id(self, id: impl Into>) -> Self { + let id = id.into(); - let mut this = self; - - let class = class.into_class(this.cx); - - let include = match class { - Class::Value(include) => include, - Class::Fn(_, f) => f(), - }; - - if include { - if let Some((_, ref mut value)) = - this.attrs.iter_mut().find(|(name, _)| name == "class") + #[cfg(all(target_arch = "wasm32", feature = "web"))] { - *value = format!("{value} {name}").into(); - } else { - this.attrs.push(("class".into(), name)); + self.element + .as_ref() + .set_attribute(wasm_bindgen::intern("id"), &id) + .unwrap(); + + self } - } - this + #[cfg(not(all(target_arch = "wasm32", feature = "web")))] + { + let mut this = self; + + this.attrs.push(("id".into(), id)); + + this + } } - } - /// Sets a property on an element. - #[track_caller] - pub fn prop( - self, - name: impl Into>, - value: impl IntoProperty, - ) -> Self { - #[cfg(all(target_arch = "wasm32", feature = "web"))] + /// Binds the element reference to [`NodeRef`]. + pub fn node_ref(self, node_ref: NodeRef) -> Self + where + Self: Clone, { - let name = name.into(); - let value = value.into_property(self.cx); - let el = self.element.as_ref(); - property_helper(el, name, value); + #[cfg(all(target_arch = "wasm32", feature = "web"))] + node_ref.load(&self); + + #[cfg(not(all(target_arch = "wasm32", feature = "web")))] + let _ = node_ref; + + self } - #[cfg(not(all(target_arch = "wasm32", feature = "web")))] - { - let _ = name; - let _ = value; + /// Runs the callback when this element has been mounted to the DOM. + /// + /// ### Important Note + /// This method will only ever run at most once. If this element + /// is unmounted and remounted, or moved somewhere else, it will not + /// re-run unless you call this method again. + pub fn on_mount(self, f: impl FnOnce(Self) + 'static) -> Self { + #[cfg(all(target_arch = "wasm32", feature = "web"))] + { + use futures::future::poll_fn; + use once_cell::unsync::OnceCell; + use std::{ + cell::RefCell, + rc::Rc, + task::{Poll, Waker}, + }; + + let this = self.clone(); + let el = self.element.as_ref().clone(); + + wasm_bindgen_futures::spawn_local(async move { + while !crate::document().body().unwrap().contains(Some(&el)) { + // We need to cook ourselves a small future that resolves + // when the next animation frame is available + let waker = Rc::new(RefCell::new(None::)); + let ready = Rc::new(OnceCell::new()); + + crate::request_animation_frame({ + let waker = waker.clone(); + let ready = ready.clone(); + + move || { + let _ = ready.set(()); + if let Some(waker) = &*waker.borrow() { + waker.wake_by_ref(); + } + } + }); + + // Wait for the animation frame to become available + poll_fn(move |cx| { + let mut waker_borrow = waker.borrow_mut(); + + *waker_borrow = Some(cx.waker().clone()); + + if ready.get().is_some() { + Poll::Ready(()) + } else { + Poll::<()>::Pending + } + }) + .await; + } + + f(this); + }); + } + + #[cfg(not(all(target_arch = "wasm32", feature = "web")))] + { + let _ = f; + } + self } - self - } + /// Adds an attribute to this element. + #[track_caller] + pub fn attr( + self, + name: impl Into>, + attr: impl IntoAttribute, + ) -> Self { + let name = name.into(); - /// Adds an event listener to this element. - #[track_caller] - pub fn on( - self, - event: E, - #[allow(unused_mut)] // used for tracing in debug - mut event_handler: impl FnMut(E::EventType) + 'static, - ) -> Self { - #[cfg(all(target_arch = "wasm32", feature = "web"))] - { - cfg_if! { - if #[cfg(debug_assertions)] { - let onspan = ::tracing::span!( - parent: &self.span, - ::tracing::Level::TRACE, - "on", - event = %event.name() - ); - let _onguard = onspan.enter(); - } - } - let event_name = event.name(); + #[cfg(all(target_arch = "wasm32", feature = "web"))] + { + attribute_helper( + self.element.as_ref(), + name, + attr.into_attribute(self.cx), + ); + self + } - if event.bubbles() { - add_event_listener(self.element.as_ref(), event_name, event_handler); - } else { - add_event_listener_undelegated( - self.element.as_ref(), - &event_name, - event_handler, - ); - } + #[cfg(not(all(target_arch = "wasm32", feature = "web")))] + { + use crate::macro_helpers::Attribute; - self + let mut this = self; + + let mut attr = attr.into_attribute(this.cx); + while let Attribute::Fn(_, f) = attr { + attr = f(); + } + match attr { + Attribute::String(value) => { + this.attrs.push((name, value.into())); + } + Attribute::Bool(include) => { + if include { + this.attrs.push((name, "".into())); + } + } + Attribute::Option(_, maybe) => { + if let Some(value) = maybe { + this.attrs.push((name, value.into())); + } + } + _ => unreachable!(), + } + + this + } } - #[cfg(not(all(target_arch = "wasm32", feature = "web")))] - { - _ = event; - _ = event_handler; + /// Adds a class to an element. + #[track_caller] + pub fn class( + self, + name: impl Into>, + class: impl IntoClass, + ) -> Self { + let name = name.into(); - self - } - } + #[cfg(all(target_arch = "wasm32", feature = "web"))] + { + let el = self.element.as_ref(); + let value = class.into_class(self.cx); + class_helper(el, name, value); - /// Adds a child to this element. - #[track_caller] - pub fn child(self, child: impl IntoView) -> Self { - let child = child.into_view(self.cx); + self + } - #[cfg(all(target_arch = "wasm32", feature = "web"))] - { - if !HydrationCtx::is_hydrating() { - // add a debug-only, run-time warning for the SVG element - #[cfg(debug_assertions)] - warn_on_ambiguous_a(self.element.as_ref(), &child); + #[cfg(not(all(target_arch = "wasm32", feature = "web")))] + { + use crate::macro_helpers::Class; - mount_child(MountKind::Append(self.element.as_ref()), &child); - } + let mut this = self; - self + let class = class.into_class(this.cx); + + let include = match class { + Class::Value(include) => include, + Class::Fn(_, f) => f(), + }; + + if include { + if let Some((_, ref mut value)) = + this.attrs.iter_mut().find(|(name, _)| name == "class") + { + *value = format!("{value} {name}").into(); + } else { + this.attrs.push(("class".into(), name)); + } + } + + this + } } - #[cfg(not(all(target_arch = "wasm32", feature = "web")))] - { - let mut this = self; + /// Sets a property on an element. + #[track_caller] + pub fn prop( + self, + name: impl Into>, + value: impl IntoProperty, + ) -> Self { + #[cfg(all(target_arch = "wasm32", feature = "web"))] + { + let name = name.into(); + let value = value.into_property(self.cx); + let el = self.element.as_ref(); + property_helper(el, name, value); + } - this.children.push(child); + #[cfg(not(all(target_arch = "wasm32", feature = "web")))] + { + let _ = name; + let _ = value; + } - this - } - } - - /// Sets the inner HTML of this element from the provided - /// string slice. - /// - /// # Security - /// Be very careful when using this method. Always remember to - /// sanitize the input to avoid a cross-site scripting (XSS) - /// vulnerability. - pub fn inner_html(self, html: impl Into>) -> Self { - let html = html.into(); - - #[cfg(all(target_arch = "wasm32", feature = "web"))] - { - self.element.as_ref().set_inner_html(&html); - - self + self } - #[cfg(not(all(target_arch = "wasm32", feature = "web")))] - { - let mut this = self; + /// Adds an event listener to this element. + #[track_caller] + pub fn on( + self, + event: E, + #[allow(unused_mut)] // used for tracing in debug + mut event_handler: impl FnMut(E::EventType) + 'static, + ) -> Self { + #[cfg(all(target_arch = "wasm32", feature = "web"))] + { + cfg_if! { + if #[cfg(debug_assertions)] { + let onspan = ::tracing::span!( + parent: &self.span, + ::tracing::Level::TRACE, + "on", + event = %event.name() + ); + let _onguard = onspan.enter(); + } + } + let event_name = event.name(); - let child = HtmlElement::from_html( - this.cx, - Custom { - name: "inner-html".into(), - id: Default::default(), - }, - html, - ); + if event.bubbles() { + add_event_listener( + self.element.as_ref(), + event_name, + event_handler, + ); + } else { + add_event_listener_undelegated( + self.element.as_ref(), + &event_name, + event_handler, + ); + } - this.children = smallvec![child.into_view(this.cx)]; + self + } - this + #[cfg(not(all(target_arch = "wasm32", feature = "web")))] + { + _ = event; + _ = event_handler; + + self + } + } + + /// Adds a child to this element. + #[track_caller] + pub fn child(self, child: impl IntoView) -> Self { + let child = child.into_view(self.cx); + + #[cfg(all(target_arch = "wasm32", feature = "web"))] + { + if !HydrationCtx::is_hydrating() { + // add a debug-only, run-time warning for the SVG element + #[cfg(debug_assertions)] + warn_on_ambiguous_a(self.element.as_ref(), &child); + + mount_child(MountKind::Append(self.element.as_ref()), &child); + } + + self + } + + #[cfg(not(all(target_arch = "wasm32", feature = "web")))] + { + let mut this = self; + + this.children.push(child); + + this + } + } + + /// Sets the inner HTML of this element from the provided + /// string slice. + /// + /// # Security + /// Be very careful when using this method. Always remember to + /// sanitize the input to avoid a cross-site scripting (XSS) + /// vulnerability. + pub fn inner_html(self, html: impl Into>) -> Self { + let html = html.into(); + + #[cfg(all(target_arch = "wasm32", feature = "web"))] + { + self.element.as_ref().set_inner_html(&html); + + self + } + + #[cfg(not(all(target_arch = "wasm32", feature = "web")))] + { + let mut this = self; + + let child = HtmlElement::from_html( + this.cx, + Custom { + name: "inner-html".into(), + id: Default::default(), + }, + html, + ); + + this.children = smallvec![child.into_view(this.cx)]; + + this + } } - } } impl IntoView for HtmlElement { - #[cfg_attr(debug_assertions, instrument(level = "trace", name = "", skip_all, fields(tag = %self.element.name())))] - fn into_view(self, _: Scope) -> View { - #[cfg(all(target_arch = "wasm32", feature = "web"))] - { - View::Element(Element::new(self.element)) + #[cfg_attr(debug_assertions, instrument(level = "trace", name = "", skip_all, fields(tag = %self.element.name())))] + fn into_view(self, _: Scope) -> View { + #[cfg(all(target_arch = "wasm32", feature = "web"))] + { + View::Element(Element::new(self.element)) + } + #[cfg(not(all(target_arch = "wasm32", feature = "web")))] + { + let Self { + element, + mut attrs, + children, + prerendered, + .. + } = self; + + let id = element.hydration_id().clone(); + + let mut element = Element::new(element); + let children = children; + + if attrs.iter_mut().any(|(name, _)| name == "id") { + attrs.push(("leptos-hk".into(), format!("_{id}").into())); + } else { + attrs.push(("id".into(), format!("_{id}").into())); + } + + element.attrs = attrs; + element.children.extend(children); + element.prerendered = prerendered; + + View::Element(element) + } } - #[cfg(not(all(target_arch = "wasm32", feature = "web")))] - { - let Self { - element, - mut attrs, - children, - prerendered, - .. - } = self; - - let id = element.hydration_id().clone(); - - let mut element = Element::new(element); - let children = children; - - if attrs.iter_mut().any(|(name, _)| name == "id") { - attrs.push(("leptos-hk".into(), format!("_{id}").into())); - } else { - attrs.push(("id".into(), format!("_{id}").into())); - } - - element.attrs = attrs; - element.children.extend(children); - element.prerendered = prerendered; - - View::Element(element) - } - } } impl IntoView for [HtmlElement; N] { - #[cfg_attr( - debug_assertions, - instrument(level = "trace", name = "[HtmlElement; N]", skip_all) - )] - fn into_view(self, cx: Scope) -> View { - Fragment::new(self.into_iter().map(|el| el.into_view(cx)).collect()) - .into_view(cx) - } + #[cfg_attr( + debug_assertions, + instrument(level = "trace", name = "[HtmlElement; N]", skip_all) + )] + fn into_view(self, cx: Scope) -> View { + Fragment::new(self.into_iter().map(|el| el.into_view(cx)).collect()) + .into_view(cx) + } } /// Creates any custom element, such as ``. pub fn custom(cx: Scope, el: El) -> HtmlElement { - HtmlElement::new( - cx, - Custom { - name: el.name(), - #[cfg(all(target_arch = "wasm32", feature = "web"))] - element: el.as_ref().clone(), - #[cfg(not(all(target_arch = "wasm32", feature = "web")))] - id: el.hydration_id().clone(), - }, - ) + HtmlElement::new( + cx, + Custom { + name: el.name(), + #[cfg(all(target_arch = "wasm32", feature = "web"))] + element: el.as_ref().clone(), + #[cfg(not(all(target_arch = "wasm32", feature = "web")))] + id: el.hydration_id().clone(), + }, + ) } /// Creates a text node. pub fn text(text: impl Into>) -> Text { - Text::new(text.into()) + Text::new(text.into()) } macro_rules! generate_html_tags { @@ -894,70 +901,75 @@ macro_rules! generate_html_tags { #[cfg(all(target_arch = "wasm32", feature = "web"))] fn create_leptos_element( - tag: &str, - id: crate::HydrationKey, - clone_element: fn() -> web_sys::HtmlElement, + tag: &str, + id: crate::HydrationKey, + clone_element: fn() -> web_sys::HtmlElement, ) -> web_sys::HtmlElement { - if HydrationCtx::is_hydrating() { - if let Some(el) = crate::document().get_element_by_id(&format!("_{id}")) { - #[cfg(debug_assertions)] - assert_eq!( - &el.node_name().to_ascii_uppercase(), - tag, - "SSR and CSR elements have the same `TopoId` but different node \ - kinds. This is either a discrepancy between SSR and CSR rendering + if HydrationCtx::is_hydrating() { + if let Some(el) = crate::document().get_element_by_id(&format!("_{id}")) + { + #[cfg(debug_assertions)] + assert_eq!( + &el.node_name().to_ascii_uppercase(), + tag, + "SSR and CSR elements have the same `TopoId` but different \ + node kinds. This is either a discrepancy between SSR and CSR \ + rendering logic, which is considered a bug, or it can also be a leptos \ - hydration issue." - ); + hydration issue." + ); - el.remove_attribute("id").unwrap(); + el.remove_attribute("id").unwrap(); - el.unchecked_into() - } else if let Ok(Some(el)) = - crate::document().query_selector(&format!("[leptos-hk=_{id}]")) - { - #[cfg(debug_assertions)] - assert_eq!( - el.node_name().to_ascii_uppercase(), - tag, - "SSR and CSR elements have the same `TopoId` but different node \ - kinds. This is either a discrepancy between SSR and CSR rendering + el.unchecked_into() + } else if let Ok(Some(el)) = + crate::document().query_selector(&format!("[leptos-hk=_{id}]")) + { + #[cfg(debug_assertions)] + assert_eq!( + el.node_name().to_ascii_uppercase(), + tag, + "SSR and CSR elements have the same `TopoId` but different \ + node kinds. This is either a discrepancy between SSR and CSR \ + rendering logic, which is considered a bug, or it can also be a leptos \ - hydration issue." - ); + hydration issue." + ); - el.remove_attribute("leptos-hk").unwrap(); + el.remove_attribute("leptos-hk").unwrap(); - el.unchecked_into() + el.unchecked_into() + } else { + crate::warn!( + "element with id {id} not found, ignoring it for hydration" + ); + + clone_element() + } } else { - crate::warn!("element with id {id} not found, ignoring it for hydration"); - - clone_element() + clone_element() } - } else { - clone_element() - } } #[cfg(all(debug_assertions, target_arch = "wasm32", feature = "web"))] fn warn_on_ambiguous_a(parent: &web_sys::Element, child: &View) { - if let View::Element(el) = &child { - if (el.name == "a" - || el.name == "script" - || el.name == "style" - || el.name == "title") - && parent.namespace_uri() != el.element.namespace_uri() - { - crate::warn!( - "Warning: you are appending an SVG element to an HTML element, or an \ - HTML element to an SVG. Typically, this occurs when you create an \ - or "#, - ) - }); + ) + }); - // HTML for the view function and script to store resources - let stream = futures::stream::once(async move { - format!( - r#" + // HTML for the view function and script to store resources + let stream = futures::stream::once(async move { + format!( + r#" {prefix} {shell} "# - ) - }) - // TODO these should be combined again in a way that chains them appropriately - // such that individual resources can resolve before all fragments are done - .chain(fragments) - .chain(resources); + ) + }) + // TODO these should be combined again in a way that chains them appropriately + // such that individual resources can resolve before all fragments are done + .chain(fragments) + .chain(resources); - (stream, runtime, scope) + (stream, runtime, scope) } impl View { - /// Consumes the node and renders it into an HTML string. - pub fn render_to_string(self, _cx: Scope) -> Cow<'static, str> { - self.render_to_string_helper() - } + /// Consumes the node and renders it into an HTML string. + pub fn render_to_string(self, _cx: Scope) -> Cow<'static, str> { + self.render_to_string_helper() + } - pub(crate) fn render_to_string_helper(self) -> Cow<'static, str> { - match self { - View::Text(node) => node.content, - View::Component(node) => { - let content = || { - node - .children - .into_iter() - .map(|node| node.render_to_string_helper()) - .join("") - }; - cfg_if! { - if #[cfg(debug_assertions)] { - format!(r#"{}"#, - HydrationCtx::to_string(&node.id, false), - content(), - HydrationCtx::to_string(&node.id, true), - name = to_kebab_case(&node.name) - ).into() - } else { - format!( - r#"{}"#, - content(), - HydrationCtx::to_string(&node.id, true) - ).into() - } - } - } - View::Suspense(id, node) => format!( - "{}", - View::CoreComponent(node).render_to_string_helper() - ) - .into(), - View::CoreComponent(node) => { - let (id, name, wrap, content) = match node { - CoreComponent::Unit(u) => ( - u.id.clone(), - "", - false, - Box::new(move || { - #[cfg(debug_assertions)] - { - format!( - "", - HydrationCtx::to_string(&u.id, true) - ) - .into() - } - - #[cfg(not(debug_assertions))] - format!("", HydrationCtx::to_string(&u.id, true)) - .into() - }) as Box Cow<'static, str>>, - ), - CoreComponent::DynChild(node) => { - let child = node.child.take(); - ( - node.id, - "dyn-child", - true, - Box::new(move || { - if let Some(child) = *child { - // On debug builds, `DynChild` has two marker nodes, - // so there is no way for the text to be merged with - // surrounding text when the browser parses the HTML, - // but in release, `DynChild` only has a trailing marker, - // and the browser automatically merges the dynamic text - // into one single node, so we need to artificially make the - // browser create the dynamic text as it's own text node - if let View::Text(t) = child { - if !cfg!(debug_assertions) { - format!("{}", t.content).into() - } else { - t.content - } + pub(crate) fn render_to_string_helper(self) -> Cow<'static, str> { + match self { + View::Text(node) => node.content, + View::Component(node) => { + let content = || { + node.children + .into_iter() + .map(|node| node.render_to_string_helper()) + .join("") + }; + cfg_if! { + if #[cfg(debug_assertions)] { + format!(r#"{}"#, + HydrationCtx::to_string(&node.id, false), + content(), + HydrationCtx::to_string(&node.id, true), + name = to_kebab_case(&node.name) + ).into() } else { - child.render_to_string_helper() + format!( + r#"{}"#, + content(), + HydrationCtx::to_string(&node.id, true) + ).into() } - } else { - "".into() } - }) as Box Cow<'static, str>>, + } + View::Suspense(id, node) => format!( + "{}", + View::CoreComponent(node).render_to_string_helper() ) - } - CoreComponent::Each(node) => { - let children = node.children.take(); - ( - node.id, - "each", - true, - Box::new(move || { - children - .into_iter() - .flatten() - .map(|node| { - let id = node.id; + .into(), + View::CoreComponent(node) => { + let (id, name, wrap, content) = match node { + CoreComponent::Unit(u) => ( + u.id.clone(), + "", + false, + Box::new(move || { + #[cfg(debug_assertions)] + { + format!( + "", + HydrationCtx::to_string(&u.id, true) + ) + .into() + } - let content = || node.child.render_to_string_helper(); + #[cfg(not(debug_assertions))] + format!( + "", + HydrationCtx::to_string(&u.id, true) + ) + .into() + }) + as Box Cow<'static, str>>, + ), + CoreComponent::DynChild(node) => { + let child = node.child.take(); + ( + node.id, + "dyn-child", + true, + Box::new(move || { + if let Some(child) = *child { + // On debug builds, `DynChild` has two marker nodes, + // so there is no way for the text to be merged with + // surrounding text when the browser parses the HTML, + // but in release, `DynChild` only has a trailing marker, + // and the browser automatically merges the dynamic text + // into one single node, so we need to artificially make the + // browser create the dynamic text as it's own text node + if let View::Text(t) = child { + if !cfg!(debug_assertions) { + format!("{}", t.content).into() + } else { + t.content + } + } else { + child.render_to_string_helper() + } + } else { + "".into() + } + }) + as Box Cow<'static, str>>, + ) + } + CoreComponent::Each(node) => { + let children = node.children.take(); + ( + node.id, + "each", + true, + Box::new(move || { + children + .into_iter() + .flatten() + .map(|node| { + let id = node.id; - #[cfg(debug_assertions)] - { - format!( + let content = || { + node.child.render_to_string_helper() + }; + + #[cfg(debug_assertions)] + { + format!( "{}", HydrationCtx::to_string(&id, false), content(), HydrationCtx::to_string(&id, true), ) + } + + #[cfg(not(debug_assertions))] + format!( + "{}", + content(), + HydrationCtx::to_string(&id, true) + ) + }) + .join("") + .into() + }) + as Box Cow<'static, str>>, + ) } + }; - #[cfg(not(debug_assertions))] - format!( - "{}", - content(), - HydrationCtx::to_string(&id, true) - ) - }) - .join("") - .into() - }) as Box Cow<'static, str>>, - ) - } - }; + if wrap { + cfg_if! { + if #[cfg(debug_assertions)] { + format!( + r#"{}"#, + HydrationCtx::to_string(&id, false), + content(), + HydrationCtx::to_string(&id, true), + ).into() + } else { + let _ = name; - if wrap { - cfg_if! { - if #[cfg(debug_assertions)] { - format!( - r#"{}"#, - HydrationCtx::to_string(&id, false), - content(), - HydrationCtx::to_string(&id, true), - ).into() - } else { - let _ = name; - - format!( - r#"{}"#, - content(), - HydrationCtx::to_string(&id, true) - ).into() + format!( + r#"{}"#, + content(), + HydrationCtx::to_string(&id, true) + ).into() + } + } + } else { + content() + } } - } - } else { - content() - } - } - View::Element(el) => { - if let Some(prerendered) = el.prerendered { - prerendered - } else { - let tag_name = el.name; + View::Element(el) => { + if let Some(prerendered) = el.prerendered { + prerendered + } else { + let tag_name = el.name; - let mut inner_html = None; + let mut inner_html = None; - let attrs = el - .attrs - .into_iter() - .filter_map(|(name, value)| -> Option> { - if value.is_empty() { - Some(format!(" {name}").into()) - } else if name == "inner_html" { - inner_html = Some(value); - None - } else { - Some( - format!( + let attrs = el + .attrs + .into_iter() + .filter_map( + |(name, value)| -> Option> { + if value.is_empty() { + Some(format!(" {name}").into()) + } else if name == "inner_html" { + inner_html = Some(value); + None + } else { + Some( + format!( " {name}=\"{}\"", html_escape::encode_double_quoted_attribute(&value) ) - .into(), - ) - } - }) - .join(""); + .into(), + ) + } + }, + ) + .join(""); - if el.is_void { - format!("<{tag_name}{attrs}/>").into() - } else if let Some(inner_html) = inner_html { - format!("<{tag_name}{attrs}>{inner_html}").into() - } else { - let children = el - .children - .into_iter() - .map(|node| node.render_to_string_helper()) - .join(""); + if el.is_void { + format!("<{tag_name}{attrs}/>").into() + } else if let Some(inner_html) = inner_html { + format!("<{tag_name}{attrs}>{inner_html}") + .into() + } else { + let children = el + .children + .into_iter() + .map(|node| node.render_to_string_helper()) + .join(""); - format!("<{tag_name}{attrs}>{children}").into() - } + format!("<{tag_name}{attrs}>{children}") + .into() + } + } + } + View::Transparent(_) => Default::default(), } - } - View::Transparent(_) => Default::default(), } - } } #[cfg(debug_assertions)] fn to_kebab_case(name: &str) -> String { - if name.is_empty() { - return String::new(); - } - - let mut new_name = String::with_capacity(name.len() + 8); - - let mut chars = name.chars(); - - new_name.push( - chars - .next() - .map(|mut c| { - if c.is_ascii() { - c.make_ascii_lowercase(); - } - - c - }) - .unwrap(), - ); - - for mut char in chars { - if char.is_ascii_uppercase() { - char.make_ascii_lowercase(); - - new_name.push('-'); + if name.is_empty() { + return String::new(); } - new_name.push(char); - } + let mut new_name = String::with_capacity(name.len() + 8); - new_name + let mut chars = name.chars(); + + new_name.push( + chars + .next() + .map(|mut c| { + if c.is_ascii() { + c.make_ascii_lowercase(); + } + + c + }) + .unwrap(), + ); + + for mut char in chars { + if char.is_ascii_uppercase() { + char.make_ascii_lowercase(); + + new_name.push('-'); + } + + new_name.push(char); + } + + new_name } #[doc(hidden)] pub fn escape_attr(value: &T) -> Cow<'_, str> where - T: AsRef, + T: AsRef, { - html_escape::encode_double_quoted_attribute(value) + html_escape::encode_double_quoted_attribute(value) } diff --git a/leptos_dom/src/transparent.rs b/leptos_dom/src/transparent.rs index 3a69612c1..9b1795f74 100644 --- a/leptos_dom/src/transparent.rs +++ b/leptos_dom/src/transparent.rs @@ -7,39 +7,39 @@ use std::{any::Any, fmt, rc::Rc}; pub struct Transparent(Rc); impl Transparent { - /// Creates a new wrapper for this data. - pub fn new(value: T) -> Self - where - T: 'static, - { - Self(Rc::new(value)) - } + /// Creates a new wrapper for this data. + pub fn new(value: T) -> Self + where + T: 'static, + { + Self(Rc::new(value)) + } - /// Returns some reference to the inner value if it is of type `T`, or `None` if it isn't. - pub fn downcast_ref(&self) -> Option<&T> - where - T: 'static, - { - self.0.downcast_ref() - } + /// Returns some reference to the inner value if it is of type `T`, or `None` if it isn't. + pub fn downcast_ref(&self) -> Option<&T> + where + T: 'static, + { + self.0.downcast_ref() + } } impl fmt::Debug for Transparent { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_tuple("Transparent").finish() - } + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_tuple("Transparent").finish() + } } impl PartialEq for Transparent { - fn eq(&self, other: &Self) -> bool { - std::ptr::eq(&self.0, &other.0) - } + fn eq(&self, other: &Self) -> bool { + std::ptr::eq(&self.0, &other.0) + } } impl Eq for Transparent {} impl IntoView for Transparent { - fn into_view(self, _: Scope) -> View { - View::Transparent(self) - } + fn into_view(self, _: Scope) -> View { + View::Transparent(self) + } } diff --git a/leptos_macro/src/component.rs b/leptos_macro/src/component.rs index 8205d6163..fb0f9e424 100644 --- a/leptos_macro/src/component.rs +++ b/leptos_macro/src/component.rs @@ -8,9 +8,10 @@ use proc_macro_error::ResultExt; use quote::{format_ident, ToTokens, TokenStreamExt}; use std::collections::HashSet; use syn::{ - parse::Parse, parse_quote, AngleBracketedGenericArguments, Attribute, FnArg, GenericArgument, - ItemFn, LitStr, Meta, MetaList, MetaNameValue, NestedMeta, Pat, PatIdent, Path, PathArguments, - ReturnType, Type, TypePath, Visibility, + parse::Parse, parse_quote, AngleBracketedGenericArguments, Attribute, + FnArg, GenericArgument, ItemFn, LitStr, Meta, MetaList, MetaNameValue, + NestedMeta, Pat, PatIdent, Path, PathArguments, ReturnType, Type, TypePath, + Visibility, }; pub struct Model { @@ -62,7 +63,8 @@ impl Parse for Model { item.sig.inputs.iter_mut().for_each(|arg| { if let FnArg::Typed(ty) = arg { drain_filter(&mut ty.attrs, |attr| { - attr.path == parse_quote!(doc) || attr.path == parse_quote!(prop) + attr.path == parse_quote!(doc) + || attr.path == parse_quote!(prop) }); } }); @@ -91,7 +93,10 @@ impl Parse for Model { // implemented manually because Vec::drain_filter is nightly only // follows std recommended parallel -fn drain_filter(vec: &mut Vec, mut some_predicate: impl FnMut(&mut T) -> bool) { +fn drain_filter( + vec: &mut Vec, + mut some_predicate: impl FnMut(&mut T) -> bool, +) { let mut i = 0; while i < vec.len() { if some_predicate(&mut vec[i]) { @@ -139,32 +144,33 @@ impl ToTokens for Model { let prop_names = prop_names(props); - let builder_name_doc = - LitStr::new(&format!("Props for the [`{name}`] component."), name.span()); + let builder_name_doc = LitStr::new( + &format!("Props for the [`{name}`] component."), + name.span(), + ); let component_fn_prop_docs = generate_component_fn_prop_docs(props); - let (tracing_instrument_attr, tracing_span_expr, tracing_guard_expr) = if cfg!( - feature = "tracing" - ) { - ( - quote! { - #[cfg_attr( - debug_assertions, - ::leptos::leptos_dom::tracing::instrument(level = "trace", name = #trace_name, skip_all) - )] - }, - quote! { - let span = ::leptos::leptos_dom::tracing::Span::current(); - }, - quote! { - #[cfg(debug_assertions)] - let _guard = span.entered(); - }, - ) - } else { - (quote! {}, quote! {}, quote! {}) - }; + let (tracing_instrument_attr, tracing_span_expr, tracing_guard_expr) = + if cfg!(feature = "tracing") { + ( + quote! { + #[cfg_attr( + debug_assertions, + ::leptos::leptos_dom::tracing::instrument(level = "trace", name = #trace_name, skip_all) + )] + }, + quote! { + let span = ::leptos::leptos_dom::tracing::Span::current(); + }, + quote! { + #[cfg(debug_assertions)] + let _guard = span.entered(); + }, + ) + } else { + (quote! {}, quote! {}, quote! {}) + }; let component = if *is_transparent { quote! { @@ -249,11 +255,16 @@ impl Prop { .attrs .iter() .enumerate() - .filter_map(|(i, attr)| PropOpt::from_attribute(attr).map(|opt| (i, opt))) + .filter_map(|(i, attr)| { + PropOpt::from_attribute(attr).map(|opt| (i, opt)) + }) .fold(HashSet::new(), |mut acc, cur| { // Make sure opts aren't repeated if acc.intersection(&cur.1).next().is_some() { - abort!(typed.attrs[cur.0], "`#[prop]` options are repeated"); + abort!( + typed.attrs[cur.0], + "`#[prop]` options are repeated" + ); } acc.extend(cur.1); @@ -262,10 +273,13 @@ impl Prop { }); // Make sure conflicting options are not present - if prop_opts.contains(&PropOpt::Optional) && prop_opts.contains(&PropOpt::OptionalNoStrip) { + if prop_opts.contains(&PropOpt::Optional) + && prop_opts.contains(&PropOpt::OptionalNoStrip) + { abort!( typed, - "`optional` and `optional_no_strip` options are mutually exclusive" + "`optional` and `optional_no_strip` options are mutually \ + exclusive" ); } else if prop_opts.contains(&PropOpt::Optional) && prop_opts.contains(&PropOpt::StripOption) @@ -279,7 +293,8 @@ impl Prop { { abort!( typed, - "`optional_no_strip` and `strip_option` options are mutually exclusive" + "`optional_no_strip` and `strip_option` options are mutually \ + exclusive" ); } @@ -288,8 +303,8 @@ impl Prop { } else { abort!( typed.pat, - "only `prop: bool` style types are allowed within the `#[component]` \ - macro" + "only `prop: bool` style types are allowed within the \ + `#[component]` macro" ); }; @@ -333,7 +348,8 @@ impl Docs { .iter() .enumerate() .map(|(idx, attr)| { - if let Meta::NameValue(MetaNameValue { lit: doc, .. }) = attr.parse_meta().unwrap() + if let Meta::NameValue(MetaNameValue { lit: doc, .. }) = + attr.parse_meta().unwrap() { let doc_str = quote!(#doc); @@ -368,7 +384,8 @@ impl Docs { .0 .iter() .map(|attr| { - if let Meta::NameValue(MetaNameValue { lit: doc, .. }) = attr.parse_meta().unwrap() + if let Meta::NameValue(MetaNameValue { lit: doc, .. }) = + attr.parse_meta().unwrap() { let mut doc_str = quote!(#doc).to_string(); @@ -403,15 +420,17 @@ enum PropOpt { impl PropOpt { fn from_attribute(attr: &Attribute) -> Option> { - const ABORT_OPT_MESSAGE: &str = "only `optional`, `optional_no_strip`, \ - `strip_option`, `default` and `into` are \ - allowed as arguments to `#[prop()]`"; + const ABORT_OPT_MESSAGE: &str = + "only `optional`, `optional_no_strip`, `strip_option`, `default` \ + and `into` are allowed as arguments to `#[prop()]`"; if attr.path != parse_quote!(prop) { return None; } - if let Meta::List(MetaList { nested, .. }) = attr.parse_meta().unwrap_or_abort() { + if let Meta::List(MetaList { nested, .. }) = + attr.parse_meta().unwrap_or_abort() + { Some( nested .iter() @@ -473,7 +492,8 @@ struct TypedBuilderOpts { impl TypedBuilderOpts { fn from_opts(opts: &HashSet, is_ty_option: bool) -> Self { Self { - default: opts.contains(&PropOpt::Optional) || opts.contains(&PropOpt::OptionalNoStrip), + default: opts.contains(&PropOpt::Optional) + || opts.contains(&PropOpt::OptionalNoStrip), default_with_value: opts.iter().find_map(|p| match p { PropOpt::OptionalWithDefault(v) => Some(v.to_owned()), _ => None, @@ -531,7 +551,8 @@ fn prop_builder_fields(vis: &Visibility, props: &[Prop]) -> TokenStream { ty, } = prop; - let builder_attrs = TypedBuilderOpts::from_opts(prop_opts, is_option(ty)); + let builder_attrs = + TypedBuilderOpts::from_opts(prop_opts, is_option(ty)); let builder_docs = prop_to_doc(prop, PropDocStyle::Inline); @@ -566,7 +587,8 @@ fn generate_component_fn_prop_docs(props: &[Prop]) -> TokenStream { let optional_prop_docs = props .iter() .filter(|Prop { prop_opts, .. }| { - prop_opts.contains(&PropOpt::Optional) || prop_opts.contains(&PropOpt::OptionalNoStrip) + prop_opts.contains(&PropOpt::Optional) + || prop_opts.contains(&PropOpt::OptionalNoStrip) }) .map(|p| prop_to_doc(p, PropDocStyle::List)) .collect::(); @@ -613,8 +635,8 @@ fn is_option(ty: &Type) -> bool { fn unwrap_option(ty: &Type) -> Option { const STD_OPTION_MSG: &str = - "make sure you're not shadowing the `std::option::Option` type that is \ - automatically imported from the standard prelude"; + "make sure you're not shadowing the `std::option::Option` type that \ + is automatically imported from the standard prelude"; if let Type::Path(TypePath { path: Path { segments, .. }, @@ -623,9 +645,9 @@ fn unwrap_option(ty: &Type) -> Option { { if let [first] = &segments.iter().collect::>()[..] { if first.ident == "Option" { - if let PathArguments::AngleBracketed(AngleBracketedGenericArguments { - args, .. - }) = &first.arguments + if let PathArguments::AngleBracketed( + AngleBracketedGenericArguments { args, .. }, + ) = &first.arguments { if let [first] = &args.iter().collect::>()[..] { if let GenericArgument::Type(ty) = first { @@ -706,7 +728,11 @@ fn prop_to_doc( &if !prop_opts.contains(&PropOpt::Into) { format!("- **{}**: [`{}`]", quote!(#name), pretty_ty) } else { - format!("- **{}**: `impl`[`Into<{}>`]", quote!(#name), pretty_ty) + format!( + "- **{}**: `impl`[`Into<{}>`]", + quote!(#name), + pretty_ty + ) }, name.ident.span(), ); diff --git a/leptos_macro/src/lib.rs b/leptos_macro/src/lib.rs index 46f37a8df..4e1daff8c 100644 --- a/leptos_macro/src/lib.rs +++ b/leptos_macro/src/lib.rs @@ -19,7 +19,10 @@ pub(crate) enum Mode { impl Default for Mode { fn default() -> Self { - if cfg!(feature = "hydrate") || cfg!(feature = "csr") || cfg!(feature = "web") { + if cfg!(feature = "hydrate") + || cfg!(feature = "csr") + || cfg!(feature = "web") + { Mode::Client } else { Mode::Ssr @@ -282,7 +285,9 @@ pub fn view(tokens: TokenStream) -> TokenStream { let mut tokens = tokens.into_iter(); let (cx, comma) = (tokens.next(), tokens.next()); match (cx, comma) { - (Some(TokenTree::Ident(cx)), Some(TokenTree::Punct(punct))) if punct.as_char() == ',' => { + (Some(TokenTree::Ident(cx)), Some(TokenTree::Punct(punct))) + if punct.as_char() == ',' => + { let first = tokens.next(); let second = tokens.next(); let third = tokens.next(); @@ -292,11 +297,17 @@ pub fn view(tokens: TokenStream) -> TokenStream { if *first == "class" && eq.as_char() == '=' => { match &fourth { - Some(TokenTree::Punct(comma)) if comma.as_char() == ',' => third.clone(), + Some(TokenTree::Punct(comma)) + if comma.as_char() == ',' => + { + third.clone() + } _ => { let error_msg = concat!( - "To create a scope class with the view! macro you must put a comma `,` after the value.\n", - "e.g., view!{cx, class=\"my-class\",
...
}" + "To create a scope class with the view! macro \ + you must put a comma `,` after the value.\n", + "e.g., view!{cx, class=\"my-class\", \ +
...
}" ); panic!("{error_msg}") } @@ -326,7 +337,10 @@ pub fn view(tokens: TokenStream) -> TokenStream { .into() } _ => { - panic!("view! macro needs a context and RSX: e.g., view! {{ cx,
...
}}") + panic!( + "view! macro needs a context and RSX: e.g., view! {{ cx, \ +
...
}}" + ) } } } @@ -351,33 +365,34 @@ pub fn view(tokens: TokenStream) -> TokenStream { /// /// #[component] /// fn HelloComponent( -/// cx: Scope, -/// /// The user's name. -/// name: String, -/// /// The user's age. -/// age: u8 +/// cx: Scope, +/// /// The user's name. +/// name: String, +/// /// The user's age. +/// age: u8, /// ) -> impl IntoView { -/// // create the signals (reactive values) that will update the UI -/// let (age, set_age) = create_signal(cx, age); -/// // increase `age` by 1 every second -/// set_interval(move || { -/// set_age.update(|age| *age += 1) -/// }, Duration::from_secs(1)); -/// -/// // return the user interface, which will be automatically updated -/// // when signal values change -/// view! { cx, -///

"Your name is " {name} " and you are " {age} " years old."

-/// } +/// // create the signals (reactive values) that will update the UI +/// let (age, set_age) = create_signal(cx, age); +/// // increase `age` by 1 every second +/// set_interval( +/// move || set_age.update(|age| *age += 1), +/// Duration::from_secs(1), +/// ); +/// +/// // return the user interface, which will be automatically updated +/// // when signal values change +/// view! { cx, +///

"Your name is " {name} " and you are " {age} " years old."

+/// } /// } /// /// #[component] /// fn App(cx: Scope) -> impl IntoView { -/// view! { cx, -///
-/// -///
-/// } +/// view! { cx, +///
+/// +///
+/// } /// } /// ``` /// @@ -402,11 +417,15 @@ pub fn view(tokens: TokenStream) -> TokenStream { /// /// // PascalCase: Generated component will be called MyComponent /// #[component] -/// fn MyComponent(cx: Scope) -> impl IntoView { todo!() } +/// fn MyComponent(cx: Scope) -> impl IntoView { +/// todo!() +/// } /// /// // snake_case: Generated component will be called MySnakeCaseComponent /// #[component] -/// fn my_snake_case_component(cx: Scope) -> impl IntoView { todo!() } +/// fn my_snake_case_component(cx: Scope) -> impl IntoView { +/// todo!() +/// } /// ``` /// /// 3. The macro generates a type `ComponentProps` for every `Component` (so, `HomePage` generates `HomePageProps`, @@ -419,22 +438,28 @@ pub fn view(tokens: TokenStream) -> TokenStream { /// use component::{MyComponent, MyComponentProps}; /// /// mod component { -/// use leptos::*; +/// use leptos::*; /// -/// #[component] -/// pub fn MyComponent(cx: Scope) -> impl IntoView { todo!() } +/// #[component] +/// pub fn MyComponent(cx: Scope) -> impl IntoView { +/// todo!() +/// } /// } /// ``` /// ``` /// # use leptos::*; /// -/// use snake_case_component::{MySnakeCaseComponent, MySnakeCaseComponentProps}; +/// use snake_case_component::{ +/// MySnakeCaseComponent, MySnakeCaseComponentProps, +/// }; /// /// mod snake_case_component { -/// use leptos::*; +/// use leptos::*; /// -/// #[component] -/// pub fn my_snake_case_component(cx: Scope) -> impl IntoView { todo!() } +/// #[component] +/// pub fn my_snake_case_component(cx: Scope) -> impl IntoView { +/// todo!() +/// } /// } /// ``` /// @@ -454,8 +479,10 @@ pub fn view(tokens: TokenStream) -> TokenStream { /// # use leptos::*; /// #[component] /// fn MyComponent(cx: Scope, render_prop: T) -> impl IntoView -/// where T: Fn() -> HtmlElement
{ -/// todo!() +/// where +/// T: Fn() -> HtmlElement
, +/// { +/// todo!() /// } /// ``` /// @@ -468,26 +495,26 @@ pub fn view(tokens: TokenStream) -> TokenStream { /// # use leptos::*; /// #[component] /// fn ComponentWithChildren(cx: Scope, children: Children) -> impl IntoView { -/// view! { -/// cx, -///
    -/// {children(cx) -/// .nodes -/// .into_iter() -/// .map(|child| view! { cx,
  • {child}
  • }) -/// .collect::>()} -///
-/// } +/// view! { +/// cx, +///
    +/// {children(cx) +/// .nodes +/// .into_iter() +/// .map(|child| view! { cx,
  • {child}
  • }) +/// .collect::>()} +///
+/// } /// } /// /// #[component] /// fn WrapSomeChildren(cx: Scope) -> impl IntoView { -/// view! { cx, -/// -/// "Ooh, look at us!" -/// "We're being projected!" -/// -/// } +/// view! { cx, +/// +/// "Ooh, look at us!" +/// "We're being projected!" +/// +/// } /// } /// ``` /// @@ -509,30 +536,27 @@ pub fn view(tokens: TokenStream) -> TokenStream { /// /// #[component] /// pub fn MyComponent( -/// cx: Scope, -/// #[prop(into)] -/// name: String, -/// #[prop(optional)] -/// optional_value: Option, -/// #[prop(optional_no_strip)] -/// optional_no_strip: Option +/// cx: Scope, +/// #[prop(into)] name: String, +/// #[prop(optional)] optional_value: Option, +/// #[prop(optional_no_strip)] optional_no_strip: Option, /// ) -> impl IntoView { -/// // whatever UI you need +/// // whatever UI you need /// } /// -/// #[component] +/// #[component] /// pub fn App(cx: Scope) -> impl IntoView { -/// view! { cx, -/// -/// -/// } +/// view! { cx, +/// +/// +/// } /// } /// ``` #[proc_macro_error::proc_macro_error] @@ -630,7 +654,9 @@ pub fn derive_prop(input: TokenStream) -> TokenStream { // Derive Params trait for routing #[proc_macro_derive(Params, attributes(params))] -pub fn params_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream { +pub fn params_derive( + input: proc_macro::TokenStream, +) -> proc_macro::TokenStream { let ast = syn::parse(input).unwrap(); params::impl_params(&ast) } diff --git a/leptos_macro/src/props.rs b/leptos_macro/src/props.rs index 6db514941..f2146586e 100644 --- a/leptos_macro/src/props.rs +++ b/leptos_macro/src/props.rs @@ -4,14 +4,14 @@ use proc_macro2::TokenStream; use quote::quote; -use syn::spanned::Spanned; -use syn::{DeriveInput, Error, Result}; +use syn::{spanned::Spanned, DeriveInput, Error, Result}; pub fn impl_derive_prop(ast: &DeriveInput) -> Result { let data = match &ast.data { syn::Data::Struct(data) => match &data.fields { syn::Fields::Named(fields) => { - let struct_info = struct_info::StructInfo::new(ast, fields.named.iter())?; + let struct_info = + struct_info::StructInfo::new(ast, fields.named.iter())?; let builder_creation = struct_info.builder_creation_impl()?; let conversion_helper = struct_info.conversion_helper_impl()?; let fields = struct_info @@ -48,26 +48,34 @@ pub fn impl_derive_prop(ast: &DeriveInput) -> Result { } }, syn::Data::Enum(_) => { - return Err(Error::new(ast.span(), "Prop is not supported for enums")) + return Err(Error::new( + ast.span(), + "Prop is not supported for enums", + )) } syn::Data::Union(_) => { - return Err(Error::new(ast.span(), "Prop is not supported for unions")) + return Err(Error::new( + ast.span(), + "Prop is not supported for unions", + )) } }; Ok(data) } mod struct_info { + use super::{ + field_info::{FieldBuilderAttr, FieldInfo}, + util::{ + empty_type, empty_type_tuple, expr_to_single_string, + make_punctuated_single, modify_types_generics_hack, + path_to_single_string, strip_raw_ident_prefix, type_tuple, + }, + }; use proc_macro2::TokenStream; use quote::quote; use syn::parse::Error; - use super::field_info::{FieldBuilderAttr, FieldInfo}; - use super::util::{ - empty_type, empty_type_tuple, expr_to_single_string, make_punctuated_single, - modify_types_generics_hack, path_to_single_string, strip_raw_ident_prefix, type_tuple, - }; - #[derive(Debug)] pub struct StructInfo<'a> { pub vis: &'a syn::Visibility, @@ -93,17 +101,27 @@ mod struct_info { fields: impl Iterator, ) -> Result, Error> { let builder_attr = TypeBuilderAttr::new(&ast.attrs)?; - let builder_name = strip_raw_ident_prefix(format!("{}Builder", ast.ident)); + let builder_name = + strip_raw_ident_prefix(format!("{}Builder", ast.ident)); Ok(StructInfo { vis: &ast.vis, name: &ast.ident, generics: &ast.generics, fields: fields .enumerate() - .map(|(i, f)| FieldInfo::new(i, f, builder_attr.field_defaults.clone())) + .map(|(i, f)| { + FieldInfo::new( + i, + f, + builder_attr.field_defaults.clone(), + ) + }) .collect::>()?, builder_attr, - builder_name: syn::Ident::new(&builder_name, proc_macro2::Span::call_site()), + builder_name: syn::Ident::new( + &builder_name, + proc_macro2::Span::call_site(), + ), conversion_helper_trait_name: syn::Ident::new( &format!("{builder_name}_Optional"), proc_macro2::Span::call_site(), @@ -115,7 +133,10 @@ mod struct_info { }) } - fn modify_generics(&self, mut mutator: F) -> syn::Generics { + fn modify_generics( + &self, + mut mutator: F, + ) -> syn::Generics { let mut generics = self.generics.clone(); mutator(&mut generics); generics @@ -128,38 +149,51 @@ mod struct_info { ref builder_name, .. } = *self; - let (impl_generics, ty_generics, where_clause) = self.generics.split_for_impl(); + let (impl_generics, ty_generics, where_clause) = + self.generics.split_for_impl(); let all_fields_param = syn::GenericParam::Type( - syn::Ident::new("PropFields", proc_macro2::Span::call_site()).into(), + syn::Ident::new("PropFields", proc_macro2::Span::call_site()) + .into(), ); let b_generics = self.modify_generics(|g| { g.params.insert(0, all_fields_param.clone()); }); - let empties_tuple = type_tuple(self.included_fields().map(|_| empty_type())); - let generics_with_empty = modify_types_generics_hack(&ty_generics, |args| { - args.insert(0, syn::GenericArgument::Type(empties_tuple.clone().into())); - }); - let phantom_generics = self.generics.params.iter().map(|param| match param { - syn::GenericParam::Lifetime(lifetime) => { - let lifetime = &lifetime.lifetime; - quote!(::core::marker::PhantomData<&#lifetime ()>) - } - syn::GenericParam::Type(ty) => { - let ty = &ty.ident; - quote!(::core::marker::PhantomData<#ty>) - } - syn::GenericParam::Const(_cnst) => { - quote!() - } - }); - let builder_method_doc = match self.builder_attr.builder_method_doc { + let empties_tuple = + type_tuple(self.included_fields().map(|_| empty_type())); + let generics_with_empty = + modify_types_generics_hack(&ty_generics, |args| { + args.insert( + 0, + syn::GenericArgument::Type( + empties_tuple.clone().into(), + ), + ); + }); + let phantom_generics = + self.generics.params.iter().map(|param| match param { + syn::GenericParam::Lifetime(lifetime) => { + let lifetime = &lifetime.lifetime; + quote!(::core::marker::PhantomData<&#lifetime ()>) + } + syn::GenericParam::Type(ty) => { + let ty = &ty.ident; + quote!(::core::marker::PhantomData<#ty>) + } + syn::GenericParam::Const(_cnst) => { + quote!() + } + }); + let builder_method_doc = match self.builder_attr.builder_method_doc + { Some(ref doc) => quote!(#doc), None => { let doc = format!( " Create a builder for building `{name}`. - On the builder, call {setters} to set the values of the fields. - Finally, call `.build()` to create the instance of `{name}`. + On the builder, call {setters} to set the values of the \ + fields. + Finally, call `.build()` to create the instance of \ + `{name}`. ", name = self.name, setters = { @@ -172,7 +206,8 @@ mod struct_info { } else { write!(&mut result, ", ").unwrap(); } - write!(&mut result, "`.{}(...)`", field.name).unwrap(); + write!(&mut result, "`.{}(...)`", field.name) + .unwrap(); if field.builder_attr.default.is_some() { write!(&mut result, "(optional)").unwrap(); } @@ -188,8 +223,9 @@ mod struct_info { Some(ref doc) => quote!(#[doc = #doc]), None => { let doc = format!( - "Builder for [`{name}`] instances.\n\nSee [`{name}::builder()`] for more info." - ); + "Builder for [`{name}`] instances.\n\nSee \ + [`{name}::builder()`] for more info." + ); quote!(#[doc = #doc]) } } @@ -197,8 +233,11 @@ mod struct_info { quote!(#[doc(hidden)]) }; - let (b_generics_impl, b_generics_ty, b_generics_where_extras_predicates) = - b_generics.split_for_impl(); + let ( + b_generics_impl, + b_generics_ty, + b_generics_where_extras_predicates, + ) = b_generics.split_for_impl(); let mut b_generics_where: syn::WhereClause = syn::parse2(quote! { where PropFields: Clone })?; @@ -266,7 +305,10 @@ mod struct_info { }) } - pub fn field_impl(&self, field: &FieldInfo) -> Result { + pub fn field_impl( + &self, + field: &FieldInfo, + ) -> Result { let StructInfo { ref builder_name, .. } = *self; @@ -296,7 +338,9 @@ mod struct_info { syn::parse(quote!(#ident).into()).unwrap() } syn::GenericParam::Lifetime(lifetime_def) => { - syn::GenericArgument::Lifetime(lifetime_def.lifetime.clone()) + syn::GenericArgument::Lifetime( + lifetime_def.lifetime.clone(), + ) } syn::GenericParam::Const(const_param) => { let ident = const_param.ident.clone(); @@ -319,11 +363,17 @@ mod struct_info { .elems .push_value(f.tuplized_type_ty_param()); } else { - g.params - .insert(index_after_lifetime_in_generics, f.generic_ty_param()); + g.params.insert( + index_after_lifetime_in_generics, + f.generic_ty_param(), + ); let generic_argument: syn::Type = f.type_ident(); - ty_generics_tuple.elems.push_value(generic_argument.clone()); - target_generics_tuple.elems.push_value(generic_argument); + ty_generics_tuple + .elems + .push_value(generic_argument.clone()); + target_generics_tuple + .elems + .push_value(generic_argument); } ty_generics_tuple.elems.push_punct(Default::default()); target_generics_tuple.elems.push_punct(Default::default()); @@ -362,26 +412,29 @@ mod struct_info { } else { field_type }; - let (arg_type, arg_expr) = if field.builder_attr.setter.auto_into.is_some() { - ( - quote!(impl ::core::convert::Into<#arg_type>), - quote!(#field_name.into()), - ) - } else { - (quote!(#arg_type), quote!(#field_name)) - }; - - let (param_list, arg_expr) = - if let Some(transform) = &field.builder_attr.setter.transform { - let params = transform.params.iter().map(|(pat, ty)| quote!(#pat: #ty)); - let body = &transform.body; - (quote!(#(#params),*), quote!({ #body })) - } else if field.builder_attr.setter.strip_option.is_some() { - (quote!(#field_name: #arg_type), quote!(Some(#arg_expr))) + let (arg_type, arg_expr) = + if field.builder_attr.setter.auto_into.is_some() { + ( + quote!(impl ::core::convert::Into<#arg_type>), + quote!(#field_name.into()), + ) } else { - (quote!(#field_name: #arg_type), arg_expr) + (quote!(#arg_type), quote!(#field_name)) }; + let (param_list, arg_expr) = if let Some(transform) = + &field.builder_attr.setter.transform + { + let params = + transform.params.iter().map(|(pat, ty)| quote!(#pat: #ty)); + let body = &transform.body; + (quote!(#(#params),*), quote!({ #body })) + } else if field.builder_attr.setter.strip_option.is_some() { + (quote!(#field_name: #arg_type), quote!(Some(#arg_expr))) + } else { + (quote!(#field_name: #arg_type), arg_expr) + }; + let repeated_fields_error_type_name = syn::Ident::new( &format!( "{}_Error_Repeated_field_{}", @@ -390,7 +443,8 @@ mod struct_info { ), proc_macro2::Span::call_site(), ); - let repeated_fields_error_message = format!("Repeated field {field_name}"); + let repeated_fields_error_message = + format!("Repeated field {field_name}"); Ok(quote! { #[allow(dead_code, non_camel_case_types, missing_docs)] @@ -421,7 +475,10 @@ mod struct_info { }) } - pub fn required_field_impl(&self, field: &FieldInfo) -> Result { + pub fn required_field_impl( + &self, + field: &FieldInfo, + ) -> Result { let StructInfo { ref name, ref builder_name, @@ -442,7 +499,9 @@ mod struct_info { syn::parse(quote!(#ident).into()).unwrap() } syn::GenericParam::Lifetime(lifetime_def) => { - syn::GenericArgument::Lifetime(lifetime_def.lifetime.clone()) + syn::GenericArgument::Lifetime( + lifetime_def.lifetime.clone(), + ) } syn::GenericParam::Const(const_param) => { let ident = &const_param.ident; @@ -464,11 +523,14 @@ mod struct_info { // whether or not `f` is set. assert!( f.ordinal != field.ordinal, - "`required_field_impl` called for optional field {}", + "`required_field_impl` called for optional field \ + {}", field.name ); - g.params - .insert(index_after_lifetime_in_generics, f.generic_ty_param()); + g.params.insert( + index_after_lifetime_in_generics, + f.generic_ty_param(), + ); builder_generics_tuple.elems.push_value(f.type_ident()); } else if f.ordinal < field.ordinal { // Only add a `build` method that warns about missing `field` if `f` is set. @@ -484,8 +546,10 @@ mod struct_info { // missing we will show a warning for `field` and // not for `f` - which means this warning should appear whether // or not `f` is set. - g.params - .insert(index_after_lifetime_in_generics, f.generic_ty_param()); + g.params.insert( + index_after_lifetime_in_generics, + f.generic_ty_param(), + ); builder_generics_tuple.elems.push_value(f.type_ident()); } @@ -512,7 +576,8 @@ mod struct_info { ), proc_macro2::Span::call_site(), ); - let early_build_error_message = format!("Missing required field {field_name}"); + let early_build_error_message = + format!("Missing required field {field_name}"); Ok(quote! { #[doc(hidden)] @@ -551,24 +616,31 @@ mod struct_info { lifetimes: None, modifier: syn::TraitBoundModifier::None, path: syn::PathSegment { - ident: self.conversion_helper_trait_name.clone(), + ident: self + .conversion_helper_trait_name + .clone(), arguments: syn::PathArguments::AngleBracketed( syn::AngleBracketedGenericArguments { colon2_token: None, lt_token: Default::default(), - args: make_punctuated_single(syn::GenericArgument::Type( - field.ty.clone(), - )), + args: make_punctuated_single( + syn::GenericArgument::Type( + field.ty.clone(), + ), + ), gt_token: Default::default(), }, ), } .into(), }; - let mut generic_param: syn::TypeParam = field.generic_ident.clone().into(); + let mut generic_param: syn::TypeParam = + field.generic_ident.clone().into(); generic_param.bounds.push(trait_ref.into()); - g.params - .insert(index_after_lifetime_in_generics, generic_param.into()); + g.params.insert( + index_after_lifetime_in_generics, + generic_param.into(), + ); } } }); @@ -576,21 +648,22 @@ mod struct_info { let (_, ty_generics, where_clause) = self.generics.split_for_impl(); - let modified_ty_generics = modify_types_generics_hack(&ty_generics, |args| { - args.insert( - 0, - syn::GenericArgument::Type( - type_tuple(self.included_fields().map(|field| { - if field.builder_attr.default.is_some() { - field.type_ident() - } else { - field.tuplized_type_ty_param() - } - })) - .into(), - ), - ); - }); + let modified_ty_generics = + modify_types_generics_hack(&ty_generics, |args| { + args.insert( + 0, + syn::GenericArgument::Type( + type_tuple(self.included_fields().map(|field| { + if field.builder_attr.default.is_some() { + field.type_ident() + } else { + field.tuplized_type_ty_param() + } + })) + .into(), + ), + ); + }); let descructuring = self.included_fields().map(|f| f.name); @@ -620,8 +693,10 @@ mod struct_info { None => { // I'd prefer “a” or “an” to “its”, but determining which is grammatically // correct is roughly impossible. - let doc = - format!("Finalise the builder and create its [`{name}`] instance"); + let doc = format!( + "Finalise the builder and create its [`{name}`] \ + instance" + ); quote!(#[doc = #doc]) } } @@ -668,7 +743,9 @@ mod struct_info { pub fn new(attrs: &[syn::Attribute]) -> Result { let mut result = TypeBuilderAttr::default(); for attr in attrs { - if path_to_single_string(&attr.path).as_deref() != Some("builder") { + if path_to_single_string(&attr.path).as_deref() + != Some("builder") + { continue; } @@ -687,7 +764,10 @@ mod struct_info { } } _ => { - return Err(Error::new_spanned(attr.tokens.clone(), "Expected (<...>)")); + return Err(Error::new_spanned( + attr.tokens.clone(), + "Expected (<...>)", + )); } } } @@ -698,8 +778,14 @@ mod struct_info { fn apply_meta(&mut self, expr: syn::Expr) -> Result<(), Error> { match expr { syn::Expr::Assign(assign) => { - let name = expr_to_single_string(&assign.left) - .ok_or_else(|| Error::new_spanned(&assign.left, "Expected identifier"))?; + let name = expr_to_single_string(&assign.left).ok_or_else( + || { + Error::new_spanned( + &assign.left, + "Expected identifier", + ) + }, + )?; match name.as_str() { "builder_method_doc" => { self.builder_method_doc = Some(*assign.right); @@ -722,8 +808,10 @@ mod struct_info { } } syn::Expr::Path(path) => { - let name = path_to_single_string(&path.path) - .ok_or_else(|| Error::new_spanned(&path, "Expected identifier"))?; + let name = + path_to_single_string(&path.path).ok_or_else(|| { + Error::new_spanned(&path, "Expected identifier") + })?; match name.as_str() { "doc" => { self.doc = true; @@ -736,19 +824,22 @@ mod struct_info { } } syn::Expr::Call(call) => { - let subsetting_name = if let syn::Expr::Path(path) = &*call.func { - path_to_single_string(&path.path) - } else { - None - } - .ok_or_else(|| { - let call_func = &call.func; - let call_func = quote!(#call_func); - Error::new_spanned( - &call.func, - format!("Illegal builder setting group {call_func}"), - ) - })?; + let subsetting_name = + if let syn::Expr::Path(path) = &*call.func { + path_to_single_string(&path.path) + } else { + None + } + .ok_or_else(|| { + let call_func = &call.func; + let call_func = quote!(#call_func); + Error::new_spanned( + &call.func, + format!( + "Illegal builder setting group {call_func}" + ), + ) + })?; match subsetting_name.as_str() { "field_defaults" => { for arg in call.args { @@ -758,7 +849,10 @@ mod struct_info { } _ => Err(Error::new_spanned( &call.func, - format!("Illegal builder setting group name {subsetting_name}"), + format!( + "Illegal builder setting group name \ + {subsetting_name}" + ), )), } } @@ -769,14 +863,13 @@ mod struct_info { } mod field_info { + use super::util::{ + expr_to_single_string, ident_to_type, path_to_single_string, + strip_raw_ident_prefix, + }; use proc_macro2::{Span, TokenStream}; use quote::quote; - use syn::parse::Error; - use syn::spanned::Spanned; - - use super::util::{ - expr_to_single_string, ident_to_type, path_to_single_string, strip_raw_ident_prefix, - }; + use syn::{parse::Error, spanned::Spanned}; #[derive(Debug)] pub struct FieldInfo<'a> { @@ -798,7 +891,10 @@ mod field_info { ordinal, name, generic_ident: syn::Ident::new( - &format!("__{}", strip_raw_ident_prefix(name.to_string())), + &format!( + "__{}", + strip_raw_ident_prefix(name.to_string()) + ), Span::call_site(), ), ty: &field.ty, @@ -843,12 +939,16 @@ mod field_info { return None; } let generic_params = - if let syn::PathArguments::AngleBracketed(generic_params) = &segment.arguments { + if let syn::PathArguments::AngleBracketed(generic_params) = + &segment.arguments + { generic_params } else { return None; }; - if let syn::GenericArgument::Type(ty) = generic_params.args.first()? { + if let syn::GenericArgument::Type(ty) = + generic_params.args.first()? + { Some(ty) } else { None @@ -874,7 +974,9 @@ mod field_info { impl FieldBuilderAttr { pub fn with(mut self, attrs: &[syn::Attribute]) -> Result { for attr in attrs { - if path_to_single_string(&attr.path).as_deref() != Some("builder") { + if path_to_single_string(&attr.path).as_deref() + != Some("builder") + { continue; } @@ -893,7 +995,10 @@ mod field_info { } } _ => { - return Err(Error::new_spanned(attr.tokens.clone(), "Expected (<...>)")); + return Err(Error::new_spanned( + attr.tokens.clone(), + "Expected (<...>)", + )); } } } @@ -906,8 +1011,14 @@ mod field_info { pub fn apply_meta(&mut self, expr: syn::Expr) -> Result<(), Error> { match expr { syn::Expr::Assign(assign) => { - let name = expr_to_single_string(&assign.left) - .ok_or_else(|| Error::new_spanned(&assign.left, "Expected identifier"))?; + let name = expr_to_single_string(&assign.left).ok_or_else( + || { + Error::new_spanned( + &assign.left, + "Expected identifier", + ) + }, + )?; match name.as_str() { "default" => { self.default = Some(*assign.right); @@ -920,13 +1031,23 @@ mod field_info { }) = *assign.right { use std::str::FromStr; - let tokenized_code = TokenStream::from_str(&code.value())?; + let tokenized_code = + TokenStream::from_str(&code.value())?; self.default = Some( - syn::parse(tokenized_code.into()) - .map_err(|e| Error::new_spanned(code, format!("{e}")))?, + syn::parse(tokenized_code.into()).map_err( + |e| { + Error::new_spanned( + code, + format!("{e}"), + ) + }, + )?, ); } else { - return Err(Error::new_spanned(assign.right, "Expected string")); + return Err(Error::new_spanned( + assign.right, + "Expected string", + )); } Ok(()) } @@ -937,13 +1058,18 @@ mod field_info { } } syn::Expr::Path(path) => { - let name = path_to_single_string(&path.path) - .ok_or_else(|| Error::new_spanned(&path, "Expected identifier"))?; + let name = + path_to_single_string(&path.path).ok_or_else(|| { + Error::new_spanned(&path, "Expected identifier") + })?; match name.as_str() { "default" => { self.default = Some( - syn::parse(quote!(::core::default::Default::default()).into()) - .unwrap(), + syn::parse( + quote!(::core::default::Default::default()) + .into(), + ) + .unwrap(), ); Ok(()) } @@ -954,19 +1080,22 @@ mod field_info { } } syn::Expr::Call(call) => { - let subsetting_name = if let syn::Expr::Path(path) = &*call.func { - path_to_single_string(&path.path) - } else { - None - } - .ok_or_else(|| { - let call_func = &call.func; - let call_func = quote!(#call_func); - Error::new_spanned( - &call.func, - format!("Illegal builder setting group {call_func}"), - ) - })?; + let subsetting_name = + if let syn::Expr::Path(path) = &*call.func { + path_to_single_string(&path.path) + } else { + None + } + .ok_or_else(|| { + let call_func = &call.func; + let call_func = quote!(#call_func); + Error::new_spanned( + &call.func, + format!( + "Illegal builder setting group {call_func}" + ), + ) + })?; match subsetting_name.as_ref() { "setter" => { for arg in call.args { @@ -976,7 +1105,10 @@ mod field_info { } _ => Err(Error::new_spanned( &call.func, - format!("Illegal builder setting group name {subsetting_name}"), + format!( + "Illegal builder setting group name \ + {subsetting_name}" + ), )), } } @@ -987,13 +1119,18 @@ mod field_info { }) => { if let syn::Expr::Path(path) = *expr { let name = path_to_single_string(&path.path) - .ok_or_else(|| Error::new_spanned(&path, "Expected identifier"))?; + .ok_or_else(|| { + Error::new_spanned(&path, "Expected identifier") + })?; match name.as_str() { "default" => { self.default = None; Ok(()) } - _ => Err(Error::new_spanned(path, "Unknown setting".to_owned())), + _ => Err(Error::new_spanned( + path, + "Unknown setting".to_owned(), + )), } } else { Err(Error::new_spanned( @@ -1010,15 +1147,22 @@ mod field_info { if let (Some(skip), None) = (&self.setter.skip, &self.default) { return Err(Error::new( *skip, - "#[builder(skip)] must be accompanied by default or default_code", + "#[builder(skip)] must be accompanied by default or \ + default_code", )); } if let (Some(strip_option), Some(transform)) = (&self.setter.strip_option, &self.setter.transform) { - let mut error = Error::new(transform.span, "transform conflicts with strip_option"); - error.combine(Error::new(*strip_option, "strip_option set here")); + let mut error = Error::new( + transform.span, + "transform conflicts with strip_option", + ); + error.combine(Error::new( + *strip_option, + "strip_option set here", + )); return Err(error); } Ok(()) @@ -1029,8 +1173,14 @@ mod field_info { fn apply_meta(&mut self, expr: syn::Expr) -> Result<(), Error> { match expr { syn::Expr::Assign(assign) => { - let name = expr_to_single_string(&assign.left) - .ok_or_else(|| Error::new_spanned(&assign.left, "Expected identifier"))?; + let name = expr_to_single_string(&assign.left).ok_or_else( + || { + Error::new_spanned( + &assign.left, + "Expected identifier", + ) + }, + )?; match name.as_str() { "doc" => { self.doc = Some(*assign.right); @@ -1040,8 +1190,10 @@ mod field_info { // if self.strip_option.is_some() { // return Err(Error::new(assign.span(), "Illegal setting - transform // conflicts with strip_option")); } - self.transform = - Some(parse_transform_closure(assign.left.span(), &assign.right)?); + self.transform = Some(parse_transform_closure( + assign.left.span(), + &assign.right, + )?); Ok(()) } _ => Err(Error::new_spanned( @@ -1051,8 +1203,10 @@ mod field_info { } } syn::Expr::Path(path) => { - let name = path_to_single_string(&path.path) - .ok_or_else(|| Error::new_spanned(&path, "Expected identifier"))?; + let name = + path_to_single_string(&path.path).ok_or_else(|| { + Error::new_spanned(&path, "Expected identifier") + })?; macro_rules! handle_fields { ( $( $flag:expr, $field:ident, $already:expr, $checks:expr; )* ) => { match name.as_str() { @@ -1093,7 +1247,9 @@ mod field_info { }) => { if let syn::Expr::Path(path) = *expr { let name = path_to_single_string(&path.path) - .ok_or_else(|| Error::new_spanned(&path, "Expected identifier"))?; + .ok_or_else(|| { + Error::new_spanned(&path, "Expected identifier") + })?; match name.as_str() { "doc" => { self.doc = None; @@ -1111,7 +1267,10 @@ mod field_info { self.strip_option = None; Ok(()) } - _ => Err(Error::new_spanned(path, "Unknown setting".to_owned())), + _ => Err(Error::new_spanned( + path, + "Unknown setting".to_owned(), + )), } } else { Err(Error::new_spanned( @@ -1132,16 +1291,25 @@ mod field_info { span: Span, } - fn parse_transform_closure(span: Span, expr: &syn::Expr) -> Result { + fn parse_transform_closure( + span: Span, + expr: &syn::Expr, + ) -> Result { let closure = match expr { syn::Expr::Closure(closure) => closure, _ => return Err(Error::new_spanned(expr, "Expected closure")), }; if let Some(kw) = &closure.asyncness { - return Err(Error::new(kw.span, "Transform closure cannot be async")); + return Err(Error::new( + kw.span, + "Transform closure cannot be async", + )); } if let Some(kw) = &closure.capture { - return Err(Error::new(kw.span, "Transform closure cannot be move")); + return Err(Error::new( + kw.span, + "Transform closure cannot be move", + )); } let params = closure @@ -1216,7 +1384,9 @@ mod util { .into() } - pub fn type_tuple(elems: impl Iterator) -> syn::TypeTuple { + pub fn type_tuple( + elems: impl Iterator, + ) -> syn::TypeTuple { let mut result = syn::TypeTuple { paren_token: Default::default(), elems: elems.collect(), @@ -1234,7 +1404,9 @@ mod util { } } - pub fn make_punctuated_single(value: T) -> syn::punctuated::Punctuated { + pub fn make_punctuated_single( + value: T, + ) -> syn::punctuated::Punctuated { let mut punctuated = syn::punctuated::Punctuated::new(); punctuated.push(value); punctuated @@ -1245,17 +1417,21 @@ mod util { mut mutator: F, ) -> syn::AngleBracketedGenericArguments where - F: FnMut(&mut syn::punctuated::Punctuated), + F: FnMut( + &mut syn::punctuated::Punctuated< + syn::GenericArgument, + syn::token::Comma, + >, + ), { let mut abga: syn::AngleBracketedGenericArguments = - syn::parse(ty_generics.clone().into_token_stream().into()).unwrap_or_else(|_| { - syn::AngleBracketedGenericArguments { + syn::parse(ty_generics.clone().into_token_stream().into()) + .unwrap_or_else(|_| syn::AngleBracketedGenericArguments { colon2_token: None, lt_token: Default::default(), args: Default::default(), gt_token: Default::default(), - } - }); + }); mutator(&mut abga.args); abga } diff --git a/leptos_macro/src/server.rs b/leptos_macro/src/server.rs index 9829a1ac4..2dba44735 100644 --- a/leptos_macro/src/server.rs +++ b/leptos_macro/src/server.rs @@ -23,7 +23,10 @@ fn fn_arg_is_cx(f: &syn::FnArg) -> bool { } } -pub fn server_macro_impl(args: proc_macro::TokenStream, s: TokenStream2) -> Result { +pub fn server_macro_impl( + args: proc_macro::TokenStream, + s: TokenStream2, +) -> Result { let ServerFnName { struct_name, prefix, @@ -57,17 +60,21 @@ pub fn server_macro_impl(args: proc_macro::TokenStream, s: TokenStream2) -> Resu let fields = body.inputs.iter().filter(|f| !fn_arg_is_cx(f)).map(|f| { let typed_arg = match f { - FnArg::Receiver(_) => panic!("cannot use receiver types in server function macro"), + FnArg::Receiver(_) => { + panic!("cannot use receiver types in server function macro") + } FnArg::Typed(t) => t, }; quote! { pub #typed_arg } }); - let cx_arg = body - .inputs - .iter() - .next() - .and_then(|f| if fn_arg_is_cx(f) { Some(f) } else { None }); + let cx_arg = body.inputs.iter().next().and_then(|f| { + if fn_arg_is_cx(f) { + Some(f) + } else { + None + } + }); let cx_assign_statement = if let Some(FnArg::Typed(arg)) = cx_arg { if let Pat::Ident(id) = &*arg.pat { quote! { @@ -88,7 +95,9 @@ pub fn server_macro_impl(args: proc_macro::TokenStream, s: TokenStream2) -> Resu let fn_args = body.inputs.iter().map(|f| { let typed_arg = match f { - FnArg::Receiver(_) => panic!("cannot use receiver types in server function macro"), + FnArg::Receiver(_) => { + panic!("cannot use receiver types in server function macro") + } FnArg::Typed(t) => t, }; let is_cx = fn_arg_is_cx(f); @@ -124,10 +133,14 @@ pub fn server_macro_impl(args: proc_macro::TokenStream, s: TokenStream2) -> Resu let output_ty = if let syn::Type::Path(pat) = &return_ty { if pat.path.segments[0].ident == "Result" { - if let PathArguments::AngleBracketed(args) = &pat.path.segments[0].arguments { + if let PathArguments::AngleBracketed(args) = + &pat.path.segments[0].arguments + { &args.args[0] } else { - panic!("server functions should return Result"); + panic!( + "server functions should return Result" + ); } } else { panic!("server functions should return Result"); diff --git a/leptos_macro/src/view.rs b/leptos_macro/src/view.rs index cd1a73d9d..4c1d204da 100644 --- a/leptos_macro/src/view.rs +++ b/leptos_macro/src/view.rs @@ -156,7 +156,12 @@ pub(crate) fn render_view( } } 1 => root_node_to_tokens_ssr(cx, &nodes[0], global_class), - _ => fragment_to_tokens_ssr(cx, Span::call_site(), nodes, global_class), + _ => fragment_to_tokens_ssr( + cx, + Span::call_site(), + nodes, + global_class, + ), } } else { match nodes.len() { @@ -185,9 +190,12 @@ fn root_node_to_tokens_ssr( global_class: Option<&TokenTree>, ) -> TokenStream { match node { - Node::Fragment(fragment) => { - fragment_to_tokens_ssr(cx, Span::call_site(), &fragment.children, global_class) - } + Node::Fragment(fragment) => fragment_to_tokens_ssr( + cx, + Span::call_site(), + &fragment.children, + global_class, + ), Node::Comment(_) | Node::Doctype(_) | Node::Attribute(_) => quote! {}, Node::Text(node) => { let value = node.value.as_ref(); @@ -202,7 +210,9 @@ fn root_node_to_tokens_ssr( #value } } - Node::Element(node) => root_element_to_tokens_ssr(cx, node, global_class), + Node::Element(node) => { + root_element_to_tokens_ssr(cx, node, global_class) + } } } @@ -267,8 +277,9 @@ fn root_element_to_tokens_ssr( let typed_element_name = if is_custom_element { Ident::new("Custom", node.name.span()) } else { - let camel_cased = - camel_case_tag_name(&tag_name.replace("svg::", "").replace("math::", "")); + let camel_cased = camel_case_tag_name( + &tag_name.replace("svg::", "").replace("math::", ""), + ); Ident::new(&camel_cased, node.name.span()) }; let typed_element_name = if is_svg_element(&tag_name) { @@ -324,7 +335,13 @@ fn element_to_tokens_ssr( for attr in &node.attributes { if let Node::Attribute(attr) = attr { - inner_html = attribute_to_tokens_ssr(cx, attr, template, holes, exprs_for_compiler); + inner_html = attribute_to_tokens_ssr( + cx, + attr, + template, + holes, + exprs_for_compiler, + ); } } @@ -376,7 +393,9 @@ fn element_to_tokens_ssr( ), Node::Text(text) => { if let Some(value) = value_to_string(&text.value) { - template.push_str(&html_escape::encode_safe(&value)); + template.push_str(&html_escape::encode_safe( + &value, + )); } else { template.push_str("{}"); let value = text.value.as_ref(); @@ -439,7 +458,9 @@ fn attribute_to_tokens_ssr<'a>( exprs_for_compiler.push(quote! { leptos::ssr_event_listener(#event_type, #handler); }) - } else if name.strip_prefix("prop:").is_some() || name.strip_prefix("class:").is_some() { + } else if name.strip_prefix("prop:").is_some() + || name.strip_prefix("class:").is_some() + { // ignore props for SSR // ignore classes: we'll handle these separately } else if name == "inner_html" { @@ -538,7 +559,9 @@ fn set_class_attribute_ssr( if let Node::Attribute(node) = node { let name = node.key.to_string(); if name == "class" { - return if let Some((_, name, value)) = fancy_class_name(&name, cx, node) { + return if let Some((_, name, value)) = + fancy_class_name(&name, cx, node) + { let span = node.key.span(); Some((span, name, value)) } else { @@ -667,7 +690,9 @@ fn node_to_tokens( quote! { #value } } Node::Attribute(node) => attribute_to_tokens(cx, node), - Node::Element(node) => element_to_tokens(cx, node, parent_type, global_class), + Node::Element(node) => { + element_to_tokens(cx, node, parent_type, global_class) + } } } @@ -705,7 +730,9 @@ fn element_to_tokens( } TagType::Html => quote! { leptos::leptos_dom::#name(#cx) }, TagType::Svg => quote! { leptos::leptos_dom::svg::#name(#cx) }, - TagType::Math => quote! { leptos::leptos_dom::math::#name(#cx) }, + TagType::Math => { + quote! { leptos::leptos_dom::math::#name(#cx) } + } } } else { let name = &node.name; @@ -749,8 +776,12 @@ fn element_to_tokens( #[allow(unused_braces)] #value } } - Node::Element(node) => element_to_tokens(cx, node, parent_type, global_class), - Node::Comment(_) | Node::Doctype(_) | Node::Attribute(_) => quote! {}, + Node::Element(node) => { + element_to_tokens(cx, node, parent_type, global_class) + } + Node::Comment(_) | Node::Doctype(_) | Node::Attribute(_) => { + quote! {} + } }; quote! { .child((#cx, #child)) @@ -957,7 +988,8 @@ fn component_to_tokens( let props = attrs .clone() .filter(|attr| { - !attr.key.to_string().starts_with("clone:") && !attr.key.to_string().starts_with("on:") + !attr.key.to_string().starts_with("clone:") + && !attr.key.to_string().starts_with("on:") }) .map(|attr| { let name = &attr.key; @@ -1051,7 +1083,8 @@ fn event_from_attribute_node( attr: &NodeAttribute, force_undelegated: bool, ) -> (TokenStream, &Expr) { - let event_name = attr.key.to_string().strip_prefix("on:").unwrap().to_owned(); + let event_name = + attr.key.to_string().strip_prefix("on:").unwrap().to_owned(); let handler = attr .value @@ -1090,7 +1123,10 @@ fn ident_from_tag_name(tag_name: &NodeName) -> Ident { .expect("element needs to have a name"), NodeName::Block(_) => { let span = tag_name.span(); - proc_macro_error::emit_error!(span, "blocks not allowed in tag-name position"); + proc_macro_error::emit_error!( + span, + "blocks not allowed in tag-name position" + ); Ident::new("", span) } _ => Ident::new( @@ -1291,7 +1327,8 @@ fn fancy_class_name<'a>( }; let class_name = &tuple.elems[0]; let class_name = if let Expr::Lit(ExprLit { - lit: Lit::Str(s), .. + lit: Lit::Str(s), + .. }) = class_name { s.value() diff --git a/leptos_reactive/src/context.rs b/leptos_reactive/src/context.rs index df0201fb8..b4a868acb 100644 --- a/leptos_reactive/src/context.rs +++ b/leptos_reactive/src/context.rs @@ -1,11 +1,10 @@ #![forbid(unsafe_code)] +use crate::{runtime::with_runtime, Scope}; use std::{ any::{Any, TypeId}, collections::HashMap, }; -use crate::{runtime::with_runtime, Scope}; - /// Provides a context value of type `T` to the current reactive [Scope](crate::Scope) /// and all of its descendants. This can be consumed using [use_context](crate::use_context). /// @@ -58,7 +57,8 @@ where _ = 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); + let context = + contexts.entry(cx.id).unwrap().or_insert_with(HashMap::new); context.insert(id, Box::new(value) as Box); }); } @@ -119,21 +119,25 @@ where let contexts = runtime.scope_contexts.borrow(); let context = contexts.get(cx.id); context - .and_then(|context| context.get(&id).and_then(|val| val.downcast_ref::())) + .and_then(|context| { + context.get(&id).and_then(|val| val.downcast_ref::()) + }) .cloned() }; match local_value { Some(val) => Some(val), - None => runtime - .scope_parents - .borrow() - .get(cx.id) - .and_then(|parent| { - use_context::(Scope { - runtime: cx.runtime, - id: *parent, + None => { + runtime + .scope_parents + .borrow() + .get(cx.id) + .and_then(|parent| { + use_context::(Scope { + runtime: cx.runtime, + id: *parent, + }) }) - }), + } } }) .ok() diff --git a/leptos_reactive/src/effect.rs b/leptos_reactive/src/effect.rs index b97e326cd..4293d524d 100644 --- a/leptos_reactive/src/effect.rs +++ b/leptos_reactive/src/effect.rs @@ -1,10 +1,11 @@ #![forbid(unsafe_code)] -use crate::macros::debug_warn; -use crate::runtime::{with_runtime, RuntimeId}; -use crate::{Runtime, Scope, ScopeProperty}; +use crate::{ + macros::debug_warn, + runtime::{with_runtime, RuntimeId}, + Runtime, Scope, ScopeProperty, +}; use cfg_if::cfg_if; -use std::cell::RefCell; -use std::fmt::Debug; +use std::{cell::RefCell, fmt::Debug}; /// Effects run a certain chunk of code whenever the signals they depend on change. /// `create_effect` immediately runs the given function once, tracks its dependence @@ -115,8 +116,10 @@ where ) )] #[track_caller] -pub fn create_isomorphic_effect(cx: Scope, f: impl Fn(Option) -> T + 'static) -where +pub fn create_isomorphic_effect( + cx: Scope, + f: impl Fn(Option) -> T + 'static, +) where T: 'static, { let e = cx.runtime.create_effect(f); @@ -210,7 +213,13 @@ impl EffectId { if let Some(effect) = effect { effect.run(*self, runtime_id); } else { - debug_warn!("[Effect] Trying to run an Effect that has been disposed. This is probably either a logic error in a component that creates and disposes of scopes, or a Resource resolving after its scope has been dropped without having been cleaned up."); + debug_warn!( + "[Effect] Trying to run an Effect that has been disposed. \ + This is probably either a logic error in a component \ + that creates and disposes of scopes, or a Resource \ + resolving after its scope has been dropped without \ + having been cleaned up." + ); } }) } diff --git a/leptos_reactive/src/lib.rs b/leptos_reactive/src/lib.rs index 7e732cb74..d44438160 100644 --- a/leptos_reactive/src/lib.rs +++ b/leptos_reactive/src/lib.rs @@ -39,30 +39,30 @@ //! // this is omitted from most of the examples in the docs //! // you usually won't need to call it yourself //! create_scope(create_runtime(), |cx| { -//! // a signal: returns a (getter, setter) pair -//! let (count, set_count) = create_signal(cx, 0); +//! // a signal: returns a (getter, setter) pair +//! let (count, set_count) = create_signal(cx, 0); //! -//! // calling the getter gets the value -//! assert_eq!(count(), 0); -//! // calling the setter sets the value -//! set_count(1); -//! // or we can mutate it in place with update() -//! set_count.update(|n| *n += 1); +//! // calling the getter gets the value +//! assert_eq!(count(), 0); +//! // calling the setter sets the value +//! set_count(1); +//! // or we can mutate it in place with update() +//! set_count.update(|n| *n += 1); //! -//! // a derived signal: a plain closure that relies on the signal -//! // the closure will run whenever we *access* double_count() -//! let double_count = move || count() * 2; -//! assert_eq!(double_count(), 4); -//! -//! // a memo: subscribes to the signal -//! // the closure will run only when count changes -//! let memoized_triple_count = create_memo(cx, move |_| count() * 3); -//! assert_eq!(memoized_triple_count(), 6); +//! // a derived signal: a plain closure that relies on the signal +//! // the closure will run whenever we *access* double_count() +//! let double_count = move || count() * 2; +//! assert_eq!(double_count(), 4); //! -//! // this effect will run whenever count() changes -//! create_effect(cx, move |_| { -//! println!("Count = {}", count()); -//! }); +//! // a memo: subscribes to the signal +//! // the closure will run only when count changes +//! let memoized_triple_count = create_memo(cx, move |_| count() * 3); +//! assert_eq!(memoized_triple_count(), 6); +//! +//! // this effect will run whenever count() changes +//! create_effect(cx, move |_| { +//! println!("Count = {}", count()); +//! }); //! }); //! ``` @@ -136,7 +136,10 @@ pub trait UntrackedSettableSignal { /// Runs the provided closure with a mutable reference to the current /// value without notifying dependents and returns /// the value the closure returned. - fn update_returning_untracked(&self, f: impl FnOnce(&mut T) -> U) -> Option; + fn update_returning_untracked( + &self, + f: impl FnOnce(&mut T) -> U, + ) -> Option; } mod macros { diff --git a/leptos_reactive/src/memo.rs b/leptos_reactive/src/memo.rs index 480d07921..42577320b 100644 --- a/leptos_reactive/src/memo.rs +++ b/leptos_reactive/src/memo.rs @@ -65,7 +65,10 @@ use std::fmt::Debug; ) ) )] -pub fn create_memo(cx: Scope, f: impl Fn(Option<&T>) -> T + 'static) -> Memo +pub fn create_memo( + cx: Scope, + f: impl Fn(Option<&T>) -> T + 'static, +) -> Memo where T: PartialEq + 'static, { @@ -270,9 +273,13 @@ where .with(|n| f(n.as_ref().expect("Memo is missing its initial value"))) } - pub(crate) fn try_with(&self, f: impl Fn(&T) -> U) -> Result { - self.0 - .try_with(|n| f(n.as_ref().expect("Memo is missing its initial value"))) + pub(crate) fn try_with( + &self, + f: impl Fn(&T) -> U, + ) -> Result { + self.0.try_with(|n| { + f(n.as_ref().expect("Memo is missing its initial value")) + }) } #[cfg(feature = "hydrate")] diff --git a/leptos_reactive/src/resource.rs b/leptos_reactive/src/resource.rs index 38d56a78c..e501273c2 100644 --- a/leptos_reactive/src/resource.rs +++ b/leptos_reactive/src/resource.rs @@ -1,10 +1,12 @@ #![forbid(unsafe_code)] use crate::{ - create_effect, create_isomorphic_effect, create_memo, create_signal, queue_microtask, + create_effect, create_isomorphic_effect, create_memo, create_signal, + queue_microtask, runtime::{with_runtime, RuntimeId}, serialization::Serializable, spawn::spawn_local, - use_context, Memo, ReadSignal, Scope, ScopeProperty, SuspenseContext, WriteSignal, + use_context, Memo, ReadSignal, Scope, ScopeProperty, SuspenseContext, + WriteSignal, }; use std::{ any::Any, @@ -113,7 +115,9 @@ where let (loading, set_loading) = create_signal(cx, false); - let fetcher = Rc::new(move |s| Box::pin(fetcher(s)) as Pin>>); + let fetcher = Rc::new(move |s| { + Box::pin(fetcher(s)) as Pin>> + }); let source = create_memo(cx, move |_| source()); let r = Rc::new(ResourceState { @@ -170,17 +174,18 @@ where /// # create_scope(create_runtime(), |cx| { /// #[derive(Debug, Clone)] // doesn't implement Serialize, Deserialize /// struct ComplicatedUnserializableStruct { -/// // something here that can't be serialized +/// // something here that can't be serialized /// } /// // any old async function; maybe this is calling a REST API or something /// async fn setup_complicated_struct() -> ComplicatedUnserializableStruct { -/// // do some work -/// ComplicatedUnserializableStruct { } +/// // do some work +/// ComplicatedUnserializableStruct {} /// } /// /// // create the resource; it will run but not be serialized /// # if cfg!(not(any(feature = "csr", feature = "hydrate"))) { -/// let result = create_local_resource(cx, move || (), |_| setup_complicated_struct()); +/// let result = +/// create_local_resource(cx, move || (), |_| setup_complicated_struct()); /// # } /// # }).dispose(); /// ``` @@ -234,7 +239,9 @@ where let (loading, set_loading) = create_signal(cx, false); - let fetcher = Rc::new(move |s| Box::pin(fetcher(s)) as Pin>>); + let fetcher = Rc::new(move |s| { + Box::pin(fetcher(s)) as Pin>> + }); let source = create_memo(cx, move |_| source()); let r = Rc::new(ResourceState { @@ -300,7 +307,8 @@ where context.pending_resources.remove(&id); // no longer pending r.resolved.set(true); - let res = T::from_json(&data).expect_throw("could not deserialize Resource JSON"); + let res = T::from_json(&data) + .expect_throw("could not deserialize Resource JSON"); r.set_value.update(|n| *n = Some(res)); r.set_loading.update(|n| *n = false); @@ -318,21 +326,25 @@ where let set_value = r.set_value; let set_loading = r.set_loading; move |res: String| { - let res = - T::from_json(&res).expect_throw("could not deserialize Resource JSON"); + let res = T::from_json(&res) + .expect_throw("could not deserialize Resource JSON"); resolved.set(true); set_value.update(|n| *n = Some(res)); set_loading.update(|n| *n = false); } }; - let resolve = - wasm_bindgen::closure::Closure::wrap(Box::new(resolve) as Box); + let resolve = wasm_bindgen::closure::Closure::wrap( + Box::new(resolve) as Box, + ); let resource_resolvers = js_sys::Reflect::get( &web_sys::window().unwrap(), &wasm_bindgen::JsValue::from_str("__LEPTOS_RESOURCE_RESOLVERS"), ) - .expect_throw("no __LEPTOS_RESOURCE_RESOLVERS found in the JS global scope"); - let id = serde_json::to_string(&id).expect_throw("could not serialize Resource ID"); + .expect_throw( + "no __LEPTOS_RESOURCE_RESOLVERS found in the JS global scope", + ); + let id = serde_json::to_string(&id) + .expect_throw("could not serialize Resource ID"); _ = js_sys::Reflect::set( &resource_resolvers, &wasm_bindgen::JsValue::from_str(&id), @@ -365,7 +377,9 @@ where T: Clone, { with_runtime(self.runtime, |runtime| { - runtime.resource(self.id, |resource: &ResourceState| resource.read()) + runtime.resource(self.id, |resource: &ResourceState| { + resource.read() + }) }) .ok() .flatten() @@ -380,7 +394,9 @@ where /// [Resource::read]. pub fn with(&self, f: impl FnOnce(&T) -> U) -> Option { with_runtime(self.runtime, |runtime| { - runtime.resource(self.id, |resource: &ResourceState| resource.with(f)) + runtime.resource(self.id, |resource: &ResourceState| { + resource.with(f) + }) }) .ok() .flatten() @@ -389,15 +405,22 @@ where /// Returns a signal that indicates whether the resource is currently loading. pub fn loading(&self) -> ReadSignal { with_runtime(self.runtime, |runtime| { - runtime.resource(self.id, |resource: &ResourceState| resource.loading) + runtime.resource(self.id, |resource: &ResourceState| { + resource.loading + }) }) - .expect("tried to call Resource::loading() in a runtime that has already been disposed.") + .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| { - runtime.resource(self.id, |resource: &ResourceState| resource.refetch()) + runtime.resource(self.id, |resource: &ResourceState| { + resource.refetch() + }) }); } @@ -413,7 +436,10 @@ where resource.to_serialization_resolver(self.id) }) }) - .expect("tried to serialize a Resource in a runtime that has already been disposed") + .expect( + "tried to serialize a Resource in a runtime that has already been \ + disposed", + ) .await } } @@ -673,8 +699,16 @@ where let mut tx = tx.clone(); move |value| { if let Some(value) = value.as_ref() { - tx.try_send((id, value.to_json().expect("could not serialize Resource"))) - .expect("failed while trying to write to Resource serializer"); + tx.try_send(( + id, + value + .to_json() + .expect("could not serialize Resource"), + )) + .expect( + "failed while trying to write to Resource \ + serializer", + ); } } }) diff --git a/leptos_reactive/src/runtime.rs b/leptos_reactive/src/runtime.rs index 5e74d0577..3094db19c 100644 --- a/leptos_reactive/src/runtime.rs +++ b/leptos_reactive/src/runtime.rs @@ -1,8 +1,9 @@ #![forbid(unsafe_code)] use crate::{ - hydration::SharedContext, AnyEffect, AnyResource, Effect, EffectId, Memo, ReadSignal, - ResourceId, ResourceState, RwSignal, Scope, ScopeDisposer, ScopeId, ScopeProperty, - SerializableResource, SignalId, UnserializableResource, WriteSignal, + hydration::SharedContext, AnyEffect, AnyResource, Effect, EffectId, Memo, + ReadSignal, ResourceId, ResourceState, RwSignal, Scope, ScopeDisposer, + ScopeId, ScopeProperty, SerializableResource, SignalId, + UnserializableResource, WriteSignal, }; use cfg_if::cfg_if; use futures::stream::FuturesUnordered; @@ -34,7 +35,10 @@ 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(id: RuntimeId, f: impl FnOnce(&Runtime) -> T) -> Result { +pub(crate) fn with_runtime( + id: RuntimeId, + f: impl FnOnce(&Runtime) -> T, +) -> Result { // in the browser, everything should exist under one runtime cfg_if! { if #[cfg(any(feature = "csr", feature = "hydrate"))] { @@ -88,7 +92,10 @@ 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") + .expect( + "tried to create raw scope in a runtime that has already been \ + disposed", + ) } pub(crate) fn run_scope_undisposed( @@ -109,24 +116,38 @@ impl RuntimeId { .expect("tried to run scope in a runtime that has been disposed") } - pub(crate) fn run_scope(self, f: impl FnOnce(Scope) -> T, parent: Option) -> T { + pub(crate) fn run_scope( + self, + f: impl FnOnce(Scope) -> T, + parent: Option, + ) -> T { let (ret, _, disposer) = self.run_scope_undisposed(f, parent); disposer.dispose(); ret } #[track_caller] - pub(crate) fn create_concrete_signal(self, value: Rc>) -> SignalId { + pub(crate) fn create_concrete_signal( + self, + value: Rc>, + ) -> SignalId { with_runtime(self, |runtime| runtime.signals.borrow_mut().insert(value)) - .expect("tried to create a signal in a runtime that has been disposed") + .expect( + "tried to create a signal in a runtime that has been disposed", + ) } #[track_caller] - pub(crate) fn create_signal(self, value: T) -> (ReadSignal, WriteSignal) + pub(crate) fn create_signal( + self, + value: T, + ) -> (ReadSignal, WriteSignal) where T: Any + 'static, { - let id = self.create_concrete_signal(Rc::new(RefCell::new(value)) as Rc>); + let id = self.create_concrete_signal( + Rc::new(RefCell::new(value)) as Rc> + ); ( ReadSignal { @@ -150,7 +171,9 @@ impl RuntimeId { where T: Any + 'static, { - let id = self.create_concrete_signal(Rc::new(RefCell::new(value)) as Rc>); + let id = self.create_concrete_signal( + Rc::new(RefCell::new(value)) as Rc> + ); RwSignal { runtime: self, id, @@ -161,13 +184,21 @@ impl RuntimeId { } #[track_caller] - pub(crate) fn create_concrete_effect(self, effect: Rc) -> EffectId { - with_runtime(self, |runtime| runtime.effects.borrow_mut().insert(effect)) - .expect("tried to create an effect in a runtime that has been disposed") + pub(crate) fn create_concrete_effect( + self, + effect: Rc, + ) -> EffectId { + with_runtime(self, |runtime| { + runtime.effects.borrow_mut().insert(effect) + }) + .expect("tried to create an effect in a runtime that has been disposed") } #[track_caller] - pub(crate) fn create_effect(self, f: impl Fn(Option) -> T + 'static) -> EffectId + pub(crate) fn create_effect( + self, + f: impl Fn(Option) -> T + 'static, + ) -> EffectId where T: Any + 'static, { @@ -187,7 +218,10 @@ impl RuntimeId { } #[track_caller] - pub(crate) fn create_memo(self, f: impl Fn(Option<&T>) -> T + 'static) -> Memo + pub(crate) fn create_memo( + self, + f: impl Fn(Option<&T>) -> T + 'static, + ) -> Memo where T: PartialEq + Any + 'static, { @@ -224,13 +258,17 @@ pub(crate) struct Runtime { pub scope_parents: RefCell>, pub scope_children: RefCell>>, #[allow(clippy::type_complexity)] - pub scope_contexts: RefCell>>>, + pub scope_contexts: + RefCell>>>, #[allow(clippy::type_complexity)] - pub scope_cleanups: RefCell>>>, + pub scope_cleanups: + RefCell>>>, pub signals: RefCell>>>, - pub signal_subscribers: RefCell>>>, + pub signal_subscribers: + RefCell>>>, pub effects: RefCell>>, - pub effect_sources: RefCell>>>, + pub effect_sources: + RefCell>>>, pub resources: RefCell>, } diff --git a/leptos_reactive/src/scope.rs b/leptos_reactive/src/scope.rs index f48ce4944..c22ab0961 100644 --- a/leptos_reactive/src/scope.rs +++ b/leptos_reactive/src/scope.rs @@ -14,7 +14,10 @@ use std::{collections::HashMap, fmt}; /// values will not have access to values created under another `create_scope`. /// /// You usually don't need to call this manually. -pub fn create_scope(runtime: RuntimeId, f: impl FnOnce(Scope) + 'static) -> ScopeDisposer { +pub fn create_scope( + runtime: RuntimeId, + f: impl FnOnce(Scope) + 'static, +) -> ScopeDisposer { runtime.run_scope_undisposed(f, None).2 } @@ -37,7 +40,10 @@ pub fn raw_scope_and_disposer(runtime: RuntimeId) -> (Scope, ScopeDisposer) { /// of the synchronous operation. /// /// You usually don't need to call this manually. -pub fn run_scope(runtime: RuntimeId, f: impl FnOnce(Scope) -> T + 'static) -> T { +pub fn run_scope( + runtime: RuntimeId, + f: impl FnOnce(Scope) -> T + 'static, +) -> T { runtime.run_scope(f, None) } @@ -116,13 +122,20 @@ impl Scope { /// This is useful for applications like a list or a router, which may want to create child scopes and /// dispose of them when they are no longer needed (e.g., a list item has been destroyed or the user /// has navigated away from the route.) - pub fn run_child_scope(self, f: impl FnOnce(Scope) -> T) -> (T, ScopeDisposer) { - let (res, child_id, disposer) = self.runtime.run_scope_undisposed(f, Some(self)); + pub fn run_child_scope( + 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| { let mut children = runtime.scope_children.borrow_mut(); children .entry(self.id) - .expect("trying to add a child to a Scope that has already been disposed") + .expect( + "trying to add a child to a Scope that has already been \ + disposed", + ) .or_default() .push(child_id); }); @@ -161,7 +174,10 @@ impl Scope { runtime.observer.set(prev_observer); untracked_result }) - .expect("tried to run untracked function in a runtime that has been disposed") + .expect( + "tried to run untracked function in a runtime that has been \ + disposed", + ) } } @@ -192,7 +208,9 @@ impl Scope { } } // run cleanups - if let Some(cleanups) = runtime.scope_cleanups.borrow_mut().remove(self.id) { + if let Some(cleanups) = + runtime.scope_cleanups.borrow_mut().remove(self.id) + { for cleanup in cleanups { cleanup(); } @@ -209,14 +227,20 @@ impl Scope { ScopeProperty::Signal(id) => { // remove the signal runtime.signals.borrow_mut().remove(id); - let subs = runtime.signal_subscribers.borrow_mut().remove(id); + let subs = runtime + .signal_subscribers + .borrow_mut() + .remove(id); // each of the subs needs to remove the signal from its dependencies // so that it doesn't try to read the (now disposed) signal if let Some(subs) = subs { - let source_map = runtime.effect_sources.borrow(); + let source_map = + runtime.effect_sources.borrow(); for effect in subs.borrow().iter() { - if let Some(effect_sources) = source_map.get(*effect) { + if let Some(effect_sources) = + source_map.get(*effect) + { effect_sources.borrow_mut().remove(&id); } } @@ -235,12 +259,15 @@ impl Scope { }) } - pub(crate) fn with_scope_property(&self, f: impl FnOnce(&mut Vec)) { + pub(crate) fn with_scope_property( + &self, + f: impl FnOnce(&mut Vec), + ) { _ = with_runtime(self.runtime, |runtime| { let scopes = runtime.scopes.borrow(); - let scope = scopes - .get(self.id) - .expect("tried to add property to a scope that has been disposed"); + let scope = scopes.get(self.id).expect( + "tried to add property to a scope that has been disposed", + ); f(&mut scope.borrow_mut()); }) } @@ -310,18 +337,23 @@ impl ScopeDisposer { impl Scope { /// Returns IDs for all [Resource](crate::Resource)s found on any scope. pub fn all_resources(&self) -> Vec { - with_runtime(self.runtime, |runtime| runtime.all_resources()).unwrap_or_default() + 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 { - with_runtime(self.runtime, |runtime| runtime.pending_resources()).unwrap_or_default() + 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> { - with_runtime(self.runtime, |runtime| runtime.serialization_resolvers()).unwrap_or_default() + pub fn serialization_resolvers( + &self, + ) -> FuturesUnordered> { + with_runtime(self.runtime, |runtime| runtime.serialization_resolvers()) + .unwrap_or_default() } /// Registers the given [SuspenseContext](crate::SuspenseContext) with the current scope, @@ -341,7 +373,8 @@ impl Scope { let (tx, mut rx) = futures::channel::mpsc::unbounded(); create_isomorphic_effect(*self, move |_| { - let pending = context.pending_resources.try_with(|n| *n).unwrap_or(0); + let pending = + context.pending_resources.try_with(|n| *n).unwrap_or(0); if pending == 0 { _ = tx.unbounded_send(()); } @@ -363,7 +396,9 @@ impl Scope { /// The set of all HTML fragments currently pending. /// Returns a tuple of the hydration ID of the previous element, and a pinned `Future` that will yield the /// `` HTML when all resources are resolved. - pub fn pending_fragments(&self) -> HashMap)> { + pub fn pending_fragments( + &self, + ) -> HashMap)> { with_runtime(self.runtime, |runtime| { let mut shared_context = runtime.shared_context.borrow_mut(); std::mem::take(&mut shared_context.pending_fragments) diff --git a/leptos_reactive/src/selector.rs b/leptos_reactive/src/selector.rs index 8fc27830a..d588026a0 100644 --- a/leptos_reactive/src/selector.rs +++ b/leptos_reactive/src/selector.rs @@ -1,7 +1,10 @@ #![forbid(unsafe_code)] -use std::{cell::RefCell, collections::HashMap, fmt::Debug, hash::Hash, rc::Rc}; - -use crate::{create_isomorphic_effect, create_signal, ReadSignal, Scope, WriteSignal}; +use crate::{ + create_isomorphic_effect, create_signal, ReadSignal, Scope, WriteSignal, +}; +use std::{ + cell::RefCell, collections::HashMap, fmt::Debug, hash::Hash, rc::Rc, +}; /// Creates a conditional signal that only notifies subscribers when a change /// in the source signal’s value changes whether it is equal to the key value @@ -16,26 +19,29 @@ use crate::{create_isomorphic_effect, create_signal, ReadSignal, Scope, WriteSig /// # use std::rc::Rc; /// # use std::cell::RefCell; /// # create_scope(create_runtime(), |cx| { -/// let (a, set_a) = create_signal(cx, 0); -/// let is_selected = create_selector(cx, a); -/// let total_notifications = Rc::new(RefCell::new(0)); -/// let not = Rc::clone(&total_notifications); -/// create_isomorphic_effect(cx, {let is_selected = is_selected.clone(); move |_| { -/// if is_selected(5) { -/// *not.borrow_mut() += 1; -/// } -/// }}); +/// let (a, set_a) = create_signal(cx, 0); +/// let is_selected = create_selector(cx, a); +/// let total_notifications = Rc::new(RefCell::new(0)); +/// let not = Rc::clone(&total_notifications); +/// create_isomorphic_effect(cx, { +/// let is_selected = is_selected.clone(); +/// move |_| { +/// if is_selected(5) { +/// *not.borrow_mut() += 1; +/// } +/// } +/// }); /// -/// assert_eq!(is_selected(5), false); -/// assert_eq!(*total_notifications.borrow(), 0); -/// set_a(5); -/// assert_eq!(is_selected(5), true); -/// assert_eq!(*total_notifications.borrow(), 1); -/// set_a(5); -/// assert_eq!(is_selected(5), true); -/// assert_eq!(*total_notifications.borrow(), 1); -/// set_a(4); -/// assert_eq!(is_selected(5), false); +/// assert_eq!(is_selected(5), false); +/// assert_eq!(*total_notifications.borrow(), 0); +/// set_a(5); +/// assert_eq!(is_selected(5), true); +/// assert_eq!(*total_notifications.borrow(), 1); +/// set_a(5); +/// assert_eq!(is_selected(5), true); +/// assert_eq!(*total_notifications.borrow(), 1); +/// set_a(4); +/// assert_eq!(is_selected(5), false); /// # }) /// # .dispose() /// ``` @@ -64,8 +70,9 @@ where T: PartialEq + Eq + Debug + Clone + Hash + 'static, { #[allow(clippy::type_complexity)] - let subs: Rc, WriteSignal)>>> = - Rc::new(RefCell::new(HashMap::new())); + let subs: Rc< + RefCell, WriteSignal)>>, + > = Rc::new(RefCell::new(HashMap::new())); let v = Rc::new(RefCell::new(None)); create_isomorphic_effect(cx, { @@ -78,7 +85,9 @@ where if prev.as_ref() != Some(&next_value) { let subs = { subs.borrow().clone() }; for (key, signal) in subs.into_iter() { - if f(&key, &next_value) || (prev.is_some() && f(&key, prev.as_ref().unwrap())) { + if f(&key, &next_value) + || (prev.is_some() && f(&key, prev.as_ref().unwrap())) + { signal.1.update(|n| *n = true); } } diff --git a/leptos_reactive/src/signal.rs b/leptos_reactive/src/signal.rs index 1e199112d..5b0cb8ed0 100644 --- a/leptos_reactive/src/signal.rs +++ b/leptos_reactive/src/signal.rs @@ -2,7 +2,8 @@ use crate::{ macros::debug_warn, runtime::{with_runtime, RuntimeId}, - Runtime, Scope, ScopeProperty, UntrackedGettableSignal, UntrackedSettableSignal, + Runtime, Scope, ScopeProperty, UntrackedGettableSignal, + UntrackedSettableSignal, }; use cfg_if::cfg_if; use futures::Stream; @@ -59,7 +60,10 @@ use thiserror::Error; ) )] #[track_caller] -pub fn create_signal(cx: Scope, value: T) -> (ReadSignal, WriteSignal) { +pub fn create_signal( + cx: Scope, + value: T, +) -> (ReadSignal, WriteSignal) { let s = cx.runtime.create_signal(value); cx.with_scope_property(|prop| prop.push(ScopeProperty::Signal(s.0.id))); s @@ -285,8 +289,12 @@ where /// Applies the function to the current Signal, if it exists, and subscribes /// the running effect. - pub(crate) fn try_with(&self, f: impl FnOnce(&T) -> U) -> Result { - match with_runtime(self.runtime, |runtime| self.id.try_with(runtime, f)) { + pub(crate) fn try_with( + &self, + f: impl FnOnce(&T) -> U, + ) -> Result { + 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), @@ -453,7 +461,10 @@ where ) ) )] - fn update_returning_untracked(&self, f: impl FnOnce(&mut T) -> U) -> Option { + fn update_returning_untracked( + &self, + f: impl FnOnce(&mut T) -> U, + ) -> Option { self.id.update_with_no_effect(self.runtime, f) } } @@ -511,11 +522,17 @@ where /// let (count, set_count) = create_signal(cx, 0); /// /// // notifies subscribers - /// let value = set_count.update_returning(|n| { *n = 1; *n * 10 }); + /// let value = set_count.update_returning(|n| { + /// *n = 1; + /// *n * 10 + /// }); /// assert_eq!(value, Some(10)); /// assert_eq!(count(), 1); /// - /// let value = set_count.update_returning(|n| { *n += 1; *n * 10 }); + /// let value = set_count.update_returning(|n| { + /// *n += 1; + /// *n * 10 + /// }); /// assert_eq!(value, Some(20)); /// assert_eq!(count(), 2); /// # }).dispose(); @@ -533,7 +550,10 @@ where ) ) )] - pub fn update_returning(&self, f: impl FnOnce(&mut T) -> U) -> Option { + pub fn update_returning( + &self, + f: impl FnOnce(&mut T) -> U, + ) -> Option { self.id.update(self.runtime, f) } @@ -793,7 +813,10 @@ impl UntrackedSettableSignal for RwSignal { ) ) )] - fn update_returning_untracked(&self, f: impl FnOnce(&mut T) -> U) -> Option { + fn update_returning_untracked( + &self, + f: impl FnOnce(&mut T) -> U, + ) -> Option { self.id.update_with_no_effect(self.runtime, f) } } @@ -885,7 +908,11 @@ where /// /// // you can include arbitrary logic in this update function /// // also notifies subscribers, even though the value hasn't changed - /// count.update(|n| if *n > 3 { *n += 1 }); + /// count.update(|n| { + /// if *n > 3 { + /// *n += 1 + /// } + /// }); /// assert_eq!(count(), 1); /// # }).dispose(); /// ``` @@ -916,11 +943,17 @@ where /// let count = create_rw_signal(cx, 0); /// /// // notifies subscribers - /// let value = count.update_returning(|n| { *n = 1; *n * 10 }); + /// let value = count.update_returning(|n| { + /// *n = 1; + /// *n * 10 + /// }); /// assert_eq!(value, Some(10)); /// assert_eq!(count(), 1); /// - /// let value = count.update_returning(|n| { *n += 1; *n * 10 }); + /// let value = count.update_returning(|n| { + /// *n += 1; + /// *n * 10 + /// }); /// assert_eq!(value, Some(20)); /// assert_eq!(count(), 2); /// # }).dispose(); @@ -938,7 +971,10 @@ where ) ) )] - pub fn update_returning(&self, f: impl FnOnce(&mut T) -> U) -> Option { + pub fn update_returning( + &self, + f: impl FnOnce(&mut T) -> U, + ) -> Option { self.id.update(self.runtime, f) } @@ -1208,7 +1244,9 @@ impl SignalId { }?; let value = value.try_borrow().unwrap_or_else(|e| { debug_warn!( - "Signal::try_with_no_subscription failed on Signal<{}>. It seems you're trying to read the value of a signal within an effect caused by updating the signal.", + "Signal::try_with_no_subscription failed on Signal<{}>. It \ + seems you're trying to read the value of a signal within an \ + effect caused by updating the signal.", std::any::type_name::() ); panic!("{e}"); @@ -1246,15 +1284,25 @@ impl SignalId { .expect("tried to access a signal in a runtime that has been disposed") } - pub(crate) fn with(&self, runtime: RuntimeId, f: impl FnOnce(&T) -> U) -> U + pub(crate) fn with( + &self, + runtime: RuntimeId, + f: impl FnOnce(&T) -> U, + ) -> U where 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") + .expect( + "tried to access a signal in a runtime that has been disposed", + ) } - fn update_value(&self, runtime: RuntimeId, f: impl FnOnce(&mut T) -> U) -> Option + fn update_value( + &self, + runtime: RuntimeId, + f: impl FnOnce(&mut T) -> U, + ) -> Option where T: 'static, { @@ -1269,14 +1317,19 @@ impl SignalId { Some(f(value)) } else { debug_warn!( - "[Signal::update] failed when downcasting to Signal<{}>", + "[Signal::update] failed when downcasting to \ + Signal<{}>", std::any::type_name::() ); None } } else { debug_warn!( - "[Signal::update] You’re trying to update a Signal<{}> that has already been disposed of. This is probably either a logic error in a component that creates and disposes of scopes, or a Resource resolving after its scope has been dropped without having been cleaned up.", + "[Signal::update] You’re trying to update a Signal<{}> \ + that has already been disposed of. This is probably \ + either a logic error in a component that creates and \ + disposes of scopes, or a Resource resolving after its \ + scope has been dropped without having been cleaned up.", std::any::type_name::() ); None diff --git a/leptos_reactive/src/signal_wrappers_read.rs b/leptos_reactive/src/signal_wrappers_read.rs index ce839315c..cd85fffa8 100644 --- a/leptos_reactive/src/signal_wrappers_read.rs +++ b/leptos_reactive/src/signal_wrappers_read.rs @@ -1,5 +1,8 @@ #![forbid(unsafe_code)] -use crate::{store_value, Memo, ReadSignal, RwSignal, Scope, StoredValue, UntrackedGettableSignal}; +use crate::{ + store_value, Memo, ReadSignal, RwSignal, Scope, StoredValue, + UntrackedGettableSignal, +}; /// Helper trait for converting `Fn() -> T` closures into /// [`Signal`]. @@ -33,9 +36,9 @@ where /// /// // this function takes any kind of wrapped signal /// fn above_3(arg: &Signal) -> bool { -/// // ✅ calling the signal clones and returns the value -/// // it is a shorthand for arg.get() -/// arg() > 3 +/// // ✅ calling the signal clones and returns the value +/// // it is a shorthand for arg.get() +/// arg() > 3 /// } /// /// assert_eq!(above_3(&count.into()), false); @@ -112,7 +115,7 @@ where /// /// // this function takes any kind of wrapped signal /// fn above_3(arg: &Signal) -> bool { - /// arg() > 3 + /// arg() > 3 /// } /// /// assert_eq!(above_3(&count.into()), false); @@ -139,7 +142,10 @@ where }; Self { - inner: SignalTypes::DerivedSignal(cx, store_value(cx, Box::new(derived_signal))), + inner: SignalTypes::DerivedSignal( + cx, + store_value(cx, Box::new(derived_signal)), + ), #[cfg(debug_assertions)] defined_at: std::panic::Location::caller(), } @@ -207,7 +213,7 @@ where /// /// // this function takes any kind of wrapped signal /// fn above_3(arg: &Signal) -> bool { - /// arg.get() > 3 + /// arg.get() > 3 /// } /// /// assert_eq!(above_3(&count.into()), false); @@ -294,7 +300,9 @@ impl Clone for SignalTypes { match self { Self::ReadSignal(arg0) => Self::ReadSignal(*arg0), Self::Memo(arg0) => Self::Memo(*arg0), - Self::DerivedSignal(arg0, arg1) => Self::DerivedSignal(*arg0, *arg1), + Self::DerivedSignal(arg0, arg1) => { + Self::DerivedSignal(*arg0, *arg1) + } } } } @@ -307,9 +315,13 @@ where { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - Self::ReadSignal(arg0) => f.debug_tuple("ReadSignal").field(arg0).finish(), + Self::ReadSignal(arg0) => { + f.debug_tuple("ReadSignal").field(arg0).finish() + } Self::Memo(arg0) => f.debug_tuple("Memo").field(arg0).finish(), - Self::DerivedSignal(_, _) => f.debug_tuple("DerivedSignal").finish(), + Self::DerivedSignal(_, _) => { + f.debug_tuple("DerivedSignal").finish() + } } } } @@ -322,7 +334,9 @@ where match (self, other) { (Self::ReadSignal(l0), Self::ReadSignal(r0)) => l0 == r0, (Self::Memo(l0), Self::Memo(r0)) => l0 == r0, - (Self::DerivedSignal(_, l0), Self::DerivedSignal(_, r0)) => std::ptr::eq(l0, r0), + (Self::DerivedSignal(_, l0), Self::DerivedSignal(_, r0)) => { + std::ptr::eq(l0, r0) + } _ => false, } } @@ -377,9 +391,9 @@ where /// /// // this function takes either a reactive or non-reactive value /// fn above_3(arg: &MaybeSignal) -> bool { -/// // ✅ calling the signal clones and returns the value -/// // it is a shorthand for arg.get() -/// arg() > 3 +/// // ✅ calling the signal clones and returns the value +/// // it is a shorthand for arg.get() +/// arg() > 3 /// } /// /// assert_eq!(above_3(&static_value.into()), true); @@ -441,7 +455,7 @@ where /// /// // this function takes any kind of wrapped signal /// fn above_3(arg: &Signal) -> bool { - /// arg() > 3 + /// arg() > 3 /// } /// /// assert_eq!(above_3(&count.into()), false); @@ -527,7 +541,7 @@ where /// /// // this function takes any kind of wrapped signal /// fn above_3(arg: &MaybeSignal) -> bool { - /// arg.get() > 3 + /// arg.get() > 3 /// } /// /// assert_eq!(above_3(&count.into()), false); diff --git a/leptos_reactive/src/signal_wrappers_write.rs b/leptos_reactive/src/signal_wrappers_write.rs index f3287faa0..a272195c6 100644 --- a/leptos_reactive/src/signal_wrappers_write.rs +++ b/leptos_reactive/src/signal_wrappers_write.rs @@ -32,9 +32,9 @@ where /// /// // this function takes any kind of signal setter /// fn set_to_4(setter: &SignalSetter) { -/// // ✅ calling the signal sets the value -/// // it is a shorthand for arg.set() -/// setter(4); +/// // ✅ calling the signal sets the value +/// // it is a shorthand for arg.set() +/// setter(4); /// } /// /// set_to_4(&set_count.into()); @@ -90,9 +90,9 @@ where /// /// // this function takes any kind of signal setter /// fn set_to_4(setter: &SignalSetter) { - /// // ✅ calling the signal sets the value - /// // it is a shorthand for arg.set() - /// setter(4) + /// // ✅ calling the signal sets the value + /// // it is a shorthand for arg.set() + /// setter(4) /// } /// /// set_to_4(&set_count.into()); @@ -114,7 +114,10 @@ where )] pub fn map(cx: Scope, mapped_setter: impl Fn(T) + 'static) -> Self { Self { - inner: SignalSetterTypes::Mapped(cx, store_value(cx, Box::new(mapped_setter))), + inner: SignalSetterTypes::Mapped( + cx, + store_value(cx, Box::new(mapped_setter)), + ), #[cfg(debug_assertions)] defined_at: std::panic::Location::caller(), } @@ -209,7 +212,9 @@ where { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - Self::Write(arg0) => f.debug_tuple("WriteSignal").field(arg0).finish(), + Self::Write(arg0) => { + f.debug_tuple("WriteSignal").field(arg0).finish() + } Self::Mapped(_, _) => f.debug_tuple("Mapped").finish(), Self::Default => f.debug_tuple("SignalSetter").finish(), } diff --git a/leptos_reactive/src/slice.rs b/leptos_reactive/src/slice.rs index 70f9490a0..98736bd7b 100644 --- a/leptos_reactive/src/slice.rs +++ b/leptos_reactive/src/slice.rs @@ -1,4 +1,6 @@ -use crate::{create_memo, IntoSignalSetter, RwSignal, Scope, Signal, SignalSetter}; +use crate::{ + create_memo, IntoSignalSetter, RwSignal, Scope, Signal, SignalSetter, +}; /// Derives a reactive slice of an [RwSignal](crate::RwSignal). /// diff --git a/leptos_reactive/src/stored_value.rs b/leptos_reactive/src/stored_value.rs index ebaf0035d..ebcbdc629 100644 --- a/leptos_reactive/src/stored_value.rs +++ b/leptos_reactive/src/stored_value.rs @@ -1,5 +1,8 @@ #![forbid(unsafe_code)] -use crate::{create_rw_signal, RwSignal, Scope, UntrackedGettableSignal, UntrackedSettableSignal}; +use crate::{ + create_rw_signal, RwSignal, Scope, UntrackedGettableSignal, + UntrackedSettableSignal, +}; /// A **non-reactive** wrapper for any value, which can be created with [store_value]. /// @@ -109,7 +112,10 @@ where /// assert_eq!(updated, Some(String::from("b"))); /// }); /// ``` - pub fn update_returning(&self, f: impl FnOnce(&mut T) -> U) -> Option { + pub fn update_returning( + &self, + f: impl FnOnce(&mut T) -> U, + ) -> Option { self.0.update_returning_untracked(f) } @@ -157,7 +163,7 @@ where /// # create_scope(create_runtime(), |cx| { /// // this structure is neither `Copy` nor `Clone` /// pub struct MyUncloneableData { -/// pub value: String +/// pub value: String, /// } /// /// // ✅ you can move the `StoredValue` and access it with .with() diff --git a/leptos_reactive/tests/effect.rs b/leptos_reactive/tests/effect.rs index 688a4851e..2f044ce3c 100644 --- a/leptos_reactive/tests/effect.rs +++ b/leptos_reactive/tests/effect.rs @@ -1,13 +1,13 @@ #[cfg(not(feature = "stable"))] use leptos_reactive::{ - create_isomorphic_effect, create_memo, create_runtime, create_scope, create_signal, + create_isomorphic_effect, create_memo, create_runtime, create_scope, + create_signal, }; #[cfg(not(feature = "stable"))] #[test] fn effect_runs() { - use std::cell::RefCell; - use std::rc::Rc; + use std::{cell::RefCell, rc::Rc}; create_scope(create_runtime(), |cx| { let (a, set_a) = create_signal(cx, -1); @@ -35,8 +35,7 @@ fn effect_runs() { #[cfg(not(feature = "stable"))] #[test] fn effect_tracks_memo() { - use std::cell::RefCell; - use std::rc::Rc; + use std::{cell::RefCell, rc::Rc}; create_scope(create_runtime(), |cx| { let (a, set_a) = create_signal(cx, -1); @@ -66,8 +65,7 @@ fn effect_tracks_memo() { #[cfg(not(feature = "stable"))] #[test] fn untrack_mutes_effect() { - use std::cell::RefCell; - use std::rc::Rc; + use std::{cell::RefCell, rc::Rc}; create_scope(create_runtime(), |cx| { let (a, set_a) = create_signal(cx, -1); diff --git a/leptos_reactive/tests/memo.rs b/leptos_reactive/tests/memo.rs index 0aed086d0..b5a34a052 100644 --- a/leptos_reactive/tests/memo.rs +++ b/leptos_reactive/tests/memo.rs @@ -1,5 +1,7 @@ #[cfg(not(feature = "stable"))] -use leptos_reactive::{create_memo, create_runtime, create_scope, create_signal}; +use leptos_reactive::{ + create_memo, create_runtime, create_scope, create_signal, +}; #[cfg(not(feature = "stable"))] #[test] diff --git a/leptos_reactive/tests/untracked.rs b/leptos_reactive/tests/untracked.rs index 67d73da7f..ccd305408 100644 --- a/leptos_reactive/tests/untracked.rs +++ b/leptos_reactive/tests/untracked.rs @@ -1,14 +1,13 @@ //#[cfg(not(feature = "stable"))] use leptos_reactive::{ - create_isomorphic_effect, create_runtime, create_scope, create_signal, UntrackedGettableSignal, - UntrackedSettableSignal, + create_isomorphic_effect, create_runtime, create_scope, create_signal, + UntrackedGettableSignal, UntrackedSettableSignal, }; //#[cfg(not(feature = "stable"))] #[test] fn untracked_set_doesnt_trigger_effect() { - use std::cell::RefCell; - use std::rc::Rc; + use std::{cell::RefCell, rc::Rc}; create_scope(create_runtime(), |cx| { let (a, set_a) = create_signal(cx, -1); @@ -39,8 +38,7 @@ fn untracked_set_doesnt_trigger_effect() { #[test] fn untracked_get_doesnt_trigger_effect() { - use std::cell::RefCell; - use std::rc::Rc; + use std::{cell::RefCell, rc::Rc}; create_scope(create_runtime(), |cx| { let (a, set_a) = create_signal(cx, -1); @@ -52,7 +50,8 @@ fn untracked_get_doesnt_trigger_effect() { create_isomorphic_effect(cx, { let b = b.clone(); move |_| { - let formatted = format!("Values are {} and {}", a(), a2.get_untracked()); + let formatted = + format!("Values are {} and {}", a(), a2.get_untracked()); *b.borrow_mut() = formatted; } }); diff --git a/leptos_server/src/action.rs b/leptos_server/src/action.rs index 35bad12a9..3175eecfd 100644 --- a/leptos_server/src/action.rs +++ b/leptos_server/src/action.rs @@ -1,6 +1,7 @@ use crate::{ServerFn, ServerFnError}; use leptos_reactive::{ - create_rw_signal, spawn_local, store_value, ReadSignal, RwSignal, Scope, StoredValue, + create_rw_signal, spawn_local, store_value, ReadSignal, RwSignal, Scope, + StoredValue, }; use std::{future::Future, pin::Pin, rc::Rc}; @@ -65,15 +66,16 @@ use std::{future::Future, pin::Pin, rc::Rc}; /// # run_scope(create_runtime(), |cx| { /// // if there's a single argument, just use that /// let action1 = create_action(cx, |input: &String| { -/// let input = input.clone(); -/// async move { todo!() } +/// let input = input.clone(); +/// async move { todo!() } /// }); /// /// // if there are no arguments, use the unit type `()` /// let action2 = create_action(cx, |input: &()| async { todo!() }); /// /// // if there are multiple arguments, use a tuple -/// let action3 = create_action(cx, |input: &(usize, String)| async { todo!() }); +/// let action3 = +/// create_action(cx, |input: &(usize, String)| async { todo!() }); /// # }); /// ``` pub struct Action(StoredValue>) @@ -259,15 +261,16 @@ where /// # run_scope(create_runtime(), |cx| { /// // if there's a single argument, just use that /// let action1 = create_action(cx, |input: &String| { -/// let input = input.clone(); -/// async move { todo!() } +/// let input = input.clone(); +/// async move { todo!() } /// }); /// /// // if there are no arguments, use the unit type `()` /// let action2 = create_action(cx, |input: &()| async { todo!() }); /// /// // if there are multiple arguments, use a tuple -/// let action3 = create_action(cx, |input: &(usize, String)| async { todo!() }); +/// let action3 = +/// create_action(cx, |input: &(usize, String)| async { todo!() }); /// # }); /// ``` pub fn create_action(cx: Scope, action_fn: F) -> Action @@ -306,14 +309,16 @@ where /// /// #[server(MyServerFn)] /// async fn my_server_fn() -> Result<(), ServerFnError> { -/// todo!() +/// todo!() /// } /// /// # run_scope(create_runtime(), |cx| { /// let my_server_action = create_server_action::(cx); /// # }); /// ``` -pub fn create_server_action(cx: Scope) -> Action> +pub fn create_server_action( + cx: Scope, +) -> Action> where S: Clone + ServerFn, { diff --git a/leptos_server/src/lib.rs b/leptos_server/src/lib.rs index 745910024..ec7e8709e 100644 --- a/leptos_server/src/lib.rs +++ b/leptos_server/src/lib.rs @@ -80,7 +80,6 @@ pub use form_urlencoded; use leptos_reactive::*; - use proc_macro2::{Literal, TokenStream}; use quote::TokenStreamExt; use serde::{de::DeserializeOwned, Deserialize, Serialize}; @@ -95,7 +94,6 @@ mod action; mod multi_action; pub use action::*; pub use multi_action::*; - #[cfg(any(feature = "ssr", doc))] use std::{ collections::HashMap, @@ -103,7 +101,10 @@ use std::{ }; #[cfg(any(feature = "ssr", doc))] -type ServerFnTraitObj = dyn Fn(Scope, &[u8]) -> Pin>>> +type ServerFnTraitObj = dyn Fn( + Scope, + &[u8], + ) -> Pin>>> + Send + Sync; @@ -302,16 +303,18 @@ where // serialize the output let result = match Self::encoding() { Encoding::Url => match serde_json::to_string(&result) - .map_err(|e| ServerFnError::Serialization(e.to_string())) - { + .map_err(|e| { + ServerFnError::Serialization(e.to_string()) + }) { Ok(r) => Payload::Url(r), Err(e) => return Err(e), }, Encoding::Cbor => { let mut buffer: Vec = Vec::new(); match ciborium::ser::into_writer(&result, &mut buffer) - .map_err(|e| ServerFnError::Serialization(e.to_string())) - { + .map_err(|e| { + ServerFnError::Serialization(e.to_string()) + }) { Ok(_) => Payload::Binary(buffer), Err(e) => return Err(e), } @@ -319,7 +322,8 @@ where }; Ok(result) - }) as Pin>>> + }) + as Pin>>> }); // store it in the hashmap @@ -332,8 +336,9 @@ where // return Err match prev { Some(_) => Err(ServerFnError::Registration(format!( - "There was already a server function registered at {:?}. \ - This can happen if you use the same server function name in two different modules + "There was already a server function registered at {:?}. This \ + can happen if you use the same server function name in two \ + different modules on `stable` or in `release` mode.", Self::url() ))), @@ -452,6 +457,7 @@ where .map_err(|e| ServerFnError::Deserialization(e.to_string()))?; let mut deserializer = JSONDeserializer::from_str(&text); - T::deserialize(&mut deserializer).map_err(|e| ServerFnError::Deserialization(e.to_string())) + T::deserialize(&mut deserializer) + .map_err(|e| ServerFnError::Deserialization(e.to_string())) } } diff --git a/leptos_server/src/multi_action.rs b/leptos_server/src/multi_action.rs index 24f85bd37..7335beba9 100644 --- a/leptos_server/src/multi_action.rs +++ b/leptos_server/src/multi_action.rs @@ -1,6 +1,7 @@ use crate::{ServerFn, ServerFnError}; use leptos_reactive::{ - create_rw_signal, spawn_local, store_value, ReadSignal, RwSignal, Scope, StoredValue, + create_rw_signal, spawn_local, store_value, ReadSignal, RwSignal, Scope, + StoredValue, }; use std::{future::Future, pin::Pin, rc::Rc}; @@ -46,15 +47,16 @@ use std::{future::Future, pin::Pin, rc::Rc}; /// # run_scope(create_runtime(), |cx| { /// // if there's a single argument, just use that /// let action1 = create_multi_action(cx, |input: &String| { -/// let input = input.clone(); -/// async move { todo!() } +/// let input = input.clone(); +/// async move { todo!() } /// }); /// /// // if there are no arguments, use the unit type `()` /// let action2 = create_multi_action(cx, |input: &()| async { todo!() }); /// /// // if there are multiple arguments, use a tuple -/// let action3 = create_multi_action(cx, |input: &(usize, String)| async { todo!() }); +/// let action3 = +/// create_multi_action(cx, |input: &(usize, String)| async { todo!() }); /// # }); /// ``` pub struct MultiAction(StoredValue>) @@ -269,18 +271,22 @@ where /// # run_scope(create_runtime(), |cx| { /// // if there's a single argument, just use that /// let action1 = create_multi_action(cx, |input: &String| { -/// let input = input.clone(); -/// async move { todo!() } +/// let input = input.clone(); +/// async move { todo!() } /// }); /// /// // if there are no arguments, use the unit type `()` /// let action2 = create_multi_action(cx, |input: &()| async { todo!() }); /// /// // if there are multiple arguments, use a tuple -/// let action3 = create_multi_action(cx, |input: &(usize, String)| async { todo!() }); +/// let action3 = +/// create_multi_action(cx, |input: &(usize, String)| async { todo!() }); /// # }); /// ``` -pub fn create_multi_action(cx: Scope, action_fn: F) -> MultiAction +pub fn create_multi_action( + cx: Scope, + action_fn: F, +) -> MultiAction where I: 'static, O: 'static, @@ -313,14 +319,16 @@ where /// /// #[server(MyServerFn)] /// async fn my_server_fn() -> Result<(), ServerFnError> { -/// todo!() +/// todo!() /// } /// /// # run_scope(create_runtime(), |cx| { /// let my_server_multi_action = create_server_multi_action::(cx); /// # }); /// ``` -pub fn create_server_multi_action(cx: Scope) -> MultiAction> +pub fn create_server_multi_action( + cx: Scope, +) -> MultiAction> where S: Clone + ServerFn, { diff --git a/meta/src/body.rs b/meta/src/body.rs index 9e55fd6c3..e0282b3f3 100644 --- a/meta/src/body.rs +++ b/meta/src/body.rs @@ -34,19 +34,21 @@ impl std::fmt::Debug for BodyContext { /// /// #[component] /// fn MyApp(cx: Scope) -> impl IntoView { -/// provide_meta_context(cx); -/// let (prefers_dark, set_prefers_dark) = create_signal(cx, false); -/// let body_class = move || if prefers_dark() { -/// "dark".to_string() -/// } else { -/// "light".to_string() -/// }; +/// provide_meta_context(cx); +/// let (prefers_dark, set_prefers_dark) = create_signal(cx, false); +/// let body_class = move || { +/// if prefers_dark() { +/// "dark".to_string() +/// } else { +/// "light".to_string() +/// } +/// }; /// -/// view! { cx, -///
-/// -///
-/// } +/// view! { cx, +///
+/// +///
+/// } /// } /// ``` #[component(transparent)] diff --git a/meta/src/html.rs b/meta/src/html.rs index 5bbaa7346..fb58d0033 100644 --- a/meta/src/html.rs +++ b/meta/src/html.rs @@ -39,13 +39,13 @@ impl std::fmt::Debug for HtmlContext { /// /// #[component] /// fn MyApp(cx: Scope) -> impl IntoView { -/// provide_meta_context(cx); +/// provide_meta_context(cx); /// -/// view! { cx, -///
-/// -///
-/// } +/// view! { cx, +///
+/// +///
+/// } /// } /// ``` #[component(transparent)] diff --git a/meta/src/lib.rs b/meta/src/lib.rs index de0f40783..0274a966c 100644 --- a/meta/src/lib.rs +++ b/meta/src/lib.rs @@ -16,23 +16,22 @@ //! //! #[component] //! fn MyApp(cx: Scope) -> impl IntoView { -//! let (name, set_name) = create_signal(cx, "Alice".to_string()); +//! let (name, set_name) = create_signal(cx, "Alice".to_string()); //! -//! view! { cx, -//! -//! <main> -//! <input -//! prop:value=name -//! on:input=move |ev| set_name(event_target_value(&ev)) +//! view! { cx, +//! <Title +//! // reactively sets document.title when `name` changes +//! text=name +//! // applies the `formatter` function to the `text` value +//! formatter=|text| format!("“{text}” is your name") //! /> -//! </main> -//! } -//! +//! <main> +//! <input +//! prop:value=name +//! on:input=move |ev| set_name(event_target_value(&ev)) +//! /> +//! </main> +//! } //! } //! ``` //! # Feature Flags @@ -46,6 +45,7 @@ //! which mode your app is operating in. use cfg_if::cfg_if; +use leptos::{leptos_dom::debug_warn, *}; use std::{ cell::{Cell, RefCell}, collections::HashMap, @@ -53,8 +53,6 @@ use std::{ rc::Rc, }; -use leptos::{leptos_dom::debug_warn, *}; - mod body; mod html; mod link; @@ -93,7 +91,14 @@ pub struct MetaContext { pub struct MetaTagsContext { next_id: Rc<Cell<MetaTagId>>, #[allow(clippy::type_complexity)] - els: Rc<RefCell<HashMap<String, (HtmlElement<AnyElement>, Scope, Option<web_sys::Element>)>>>, + els: Rc< + RefCell< + HashMap< + String, + (HtmlElement<AnyElement>, Scope, Option<web_sys::Element>), + >, + >, + >, } impl std::fmt::Debug for MetaTagsContext { @@ -109,12 +114,19 @@ impl MetaTagsContext { self.els .borrow() .iter() - .map(|(_, (builder_el, cx, _))| builder_el.clone().into_view(*cx).render_to_string(*cx)) + .map(|(_, (builder_el, cx, _))| { + builder_el.clone().into_view(*cx).render_to_string(*cx) + }) .collect() } #[doc(hidden)] - pub fn register(&self, cx: Scope, id: String, builder_el: HtmlElement<AnyElement>) { + pub fn register( + &self, + cx: Scope, + id: String, + builder_el: HtmlElement<AnyElement>, + ) { cfg_if! { if #[cfg(any(feature = "csr", feature = "hydrate"))] { use leptos::document; @@ -190,10 +202,11 @@ pub fn use_head(cx: Scope) -> MetaContext { match use_context::<MetaContext>(cx) { None => { debug_warn!( - "use_head() is being called without a MetaContext being provided. \ - We'll automatically create and provide one, but if this is being called in a child \ - route it may cause bugs. To be safe, you should provide_meta_context(cx) \ - somewhere in the root of the app." + "use_head() is being called without a MetaContext being \ + provided. We'll automatically create and provide one, but if \ + this is being called in a child route it may cause bugs. To \ + be safe, you should provide_meta_context(cx) somewhere in \ + the root of the app." ); let meta = MetaContext::new(); provide_context(cx, meta.clone()); diff --git a/meta/src/link.rs b/meta/src/link.rs index 6420b5987..0ce216909 100644 --- a/meta/src/link.rs +++ b/meta/src/link.rs @@ -9,18 +9,18 @@ use leptos::*; /// /// #[component] /// fn MyApp(cx: Scope) -> impl IntoView { -/// provide_meta_context(cx); +/// provide_meta_context(cx); /// -/// view! { cx, -/// <main> -/// <Link rel="preload" -/// href="myFont.woff2" -/// as_="font" -/// type_="font/woff2" -/// crossorigin="anonymous" -/// /> -/// </main> -/// } +/// view! { cx, +/// <main> +/// <Link rel="preload" +/// href="myFont.woff2" +/// as_="font" +/// type_="font/woff2" +/// crossorigin="anonymous" +/// /> +/// </main> +/// } /// } /// ``` #[component(transparent)] diff --git a/meta/src/meta_tags.rs b/meta/src/meta_tags.rs index 2c7b3000d..f07b063bf 100644 --- a/meta/src/meta_tags.rs +++ b/meta/src/meta_tags.rs @@ -1,6 +1,5 @@ -use leptos::{component, IntoView, Scope}; - use crate::{use_head, TextProp}; +use leptos::{component, IntoView, Scope}; /// Injects an [HTMLMetaElement](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMetaElement) into the document /// head to set metadata diff --git a/meta/src/script.rs b/meta/src/script.rs index 5bd296243..fcb0ec5ae 100644 --- a/meta/src/script.rs +++ b/meta/src/script.rs @@ -9,15 +9,15 @@ use leptos::*; /// /// #[component] /// fn MyApp(cx: Scope) -> impl IntoView { -/// provide_meta_context(cx); +/// provide_meta_context(cx); /// -/// view! { cx, -/// <main> -/// <Script> -/// "console.log('Hello, world!');" -/// </Script> -/// </main> -/// } +/// view! { cx, +/// <main> +/// <Script> +/// "console.log('Hello, world!');" +/// </Script> +/// </main> +/// } /// } /// ``` #[component(transparent)] @@ -86,7 +86,9 @@ pub fn Script( for node in frag.nodes { match node { View::Text(text) => script.push_str(&text.content), - _ => leptos::warn!("Only text nodes are supported as children of <Script/>."), + _ => leptos::warn!( + "Only text nodes are supported as children of <Script/>." + ), } } builder_el.child(script) diff --git a/meta/src/style.rs b/meta/src/style.rs index 70381a789..029d63987 100644 --- a/meta/src/style.rs +++ b/meta/src/style.rs @@ -9,15 +9,15 @@ use leptos::*; /// /// #[component] /// fn MyApp(cx: Scope) -> impl IntoView { -/// provide_meta_context(cx); +/// provide_meta_context(cx); /// -/// view! { cx, -/// <main> -/// <Style> -/// "body { font-weight: bold; }" -/// </Style> -/// </main> -/// } +/// view! { cx, +/// <main> +/// <Style> +/// "body { font-weight: bold; }" +/// </Style> +/// </main> +/// } /// } /// ``` #[component(transparent)] @@ -58,7 +58,9 @@ pub fn Style( for node in frag.nodes { match node { View::Text(text) => style.push_str(&text.content), - _ => leptos::warn!("Only text nodes are supported as children of <Style/>."), + _ => leptos::warn!( + "Only text nodes are supported as children of <Style/>." + ), } } builder_el.child(style) diff --git a/meta/src/stylesheet.rs b/meta/src/stylesheet.rs index 907198c05..7c9ba0707 100644 --- a/meta/src/stylesheet.rs +++ b/meta/src/stylesheet.rs @@ -10,13 +10,13 @@ use leptos::*; /// /// #[component] /// fn MyApp(cx: Scope) -> impl IntoView { -/// provide_meta_context(cx); +/// provide_meta_context(cx); /// -/// view! { cx, -/// <main> -/// <Stylesheet href="/style.css"/> -/// </main> -/// } +/// view! { cx, +/// <main> +/// <Stylesheet href="/style.css"/> +/// </main> +/// } /// } /// ``` #[component(transparent)] diff --git a/meta/src/title.rs b/meta/src/title.rs index ffbc35ff5..5a0b7b400 100644 --- a/meta/src/title.rs +++ b/meta/src/title.rs @@ -55,33 +55,33 @@ where /// /// #[component] /// fn MyApp(cx: Scope) -> impl IntoView { -/// provide_meta_context(cx); -/// let formatter = |text| format!("{text} — Leptos Online"); +/// provide_meta_context(cx); +/// let formatter = |text| format!("{text} — Leptos Online"); /// -/// view! { cx, -/// <main> -/// <Title formatter/> -/// // ... routing logic here -/// </main> -/// } +/// view! { cx, +/// <main> +/// <Title formatter/> +/// // ... routing logic here +/// </main> +/// } /// } /// /// #[component] /// fn PageA(cx: Scope) -> impl IntoView { -/// view! { cx, -/// <main> -/// <Title text="Page A"/> // sets title to "Page A — Leptos Online" -/// </main> -/// } +/// view! { cx, +/// <main> +/// <Title text="Page A"/> // sets title to "Page A — Leptos Online" +/// </main> +/// } /// } /// /// #[component] /// fn PageB(cx: Scope) -> impl IntoView { -/// view! { cx, -/// <main> -/// <Title text="Page B"/> // sets title to "Page B — Leptos Online" -/// </main> -/// } +/// view! { cx, +/// <main> +/// <Title text="Page B"/> // sets title to "Page B — Leptos Online" +/// </main> +/// } /// } /// ``` #[component(transparent)] diff --git a/router/src/components/form.rs b/router/src/components/form.rs index ee33391b3..fba55642f 100644 --- a/router/src/components/form.rs +++ b/router/src/components/form.rs @@ -68,12 +68,16 @@ where let (form, method, action, enctype) = extract_form_attributes(&ev); - let form_data = web_sys::FormData::new_with_form(&form).unwrap_throw(); + let form_data = + web_sys::FormData::new_with_form(&form).unwrap_throw(); if let Some(on_form_data) = on_form_data.clone() { on_form_data(&form_data); } let params = - web_sys::UrlSearchParams::new_with_str_sequence_sequence(&form_data).unwrap_throw(); + web_sys::UrlSearchParams::new_with_str_sequence_sequence( + &form_data, + ) + .unwrap_throw(); let action = use_resolved_path(cx, move || action.clone()) .get() .unwrap_or_default(); @@ -108,8 +112,13 @@ where } if resp.status() == 303 { - if let Some(redirect_url) = resp.headers().get("Location") { - _ = navigate(&redirect_url, Default::default()); + if let Some(redirect_url) = + resp.headers().get("Location") + { + _ = navigate( + &redirect_url, + Default::default(), + ); } } } @@ -119,7 +128,9 @@ where // otherwise, GET else { let params = params.to_string().as_string().unwrap_or_default(); - if navigate(&format!("{action}?{params}"), Default::default()).is_ok() { + if navigate(&format!("{action}?{params}"), Default::default()) + .is_ok() + { ev.prevent_default(); } } @@ -179,7 +190,10 @@ where let action_url = if let Some(url) = action.url() { url } else { - debug_warn!("<ActionForm/> action needs a URL. Either use create_server_action() or Action::using_server_fn()."); + debug_warn!( + "<ActionForm/> action needs a URL. Either use \ + create_server_action() or Action::using_server_fn()." + ); String::new() }; let version = action.version(); @@ -200,17 +214,21 @@ where let on_response = Rc::new(move |resp: &web_sys::Response| { let resp = resp.clone().expect("couldn't get Response"); spawn_local(async move { - let body = - JsFuture::from(resp.text().expect("couldn't get .text() from Response")).await; + let body = JsFuture::from( + resp.text().expect("couldn't get .text() from Response"), + ) + .await; match body { Ok(json) => { match O::from_json( - &json.as_string().expect("couldn't get String from JsString"), + &json + .as_string() + .expect("couldn't get String from JsString"), ) { Ok(res) => value.set(Some(Ok(res))), - Err(e) => { - value.set(Some(Err(ServerFnError::Deserialization(e.to_string())))) - } + Err(e) => value.set(Some(Err( + ServerFnError::Deserialization(e.to_string()), + ))), } } Err(e) => log::error!("{e:?}"), @@ -258,7 +276,10 @@ where let action = if let Some(url) = multi_action.url() { url } else { - debug_warn!("<MultiActionForm/> action needs a URL. Either use create_server_action() or Action::using_server_fn()."); + debug_warn!( + "<MultiActionForm/> action needs a URL. Either use \ + create_server_action() or Action::using_server_fn()." + ); String::new() }; @@ -309,10 +330,14 @@ fn extract_form_attributes( .unwrap_or_default() .to_lowercase(), form.get_attribute("enctype") - .unwrap_or_else(|| "application/x-www-form-urlencoded".to_string()) + .unwrap_or_else(|| { + "application/x-www-form-urlencoded".to_string() + }) .to_lowercase(), ) - } else if let Some(input) = el.dyn_ref::<web_sys::HtmlInputElement>() { + } else if let Some(input) = + el.dyn_ref::<web_sys::HtmlInputElement>() + { let form = ev .target() .unwrap() @@ -331,11 +356,15 @@ fn extract_form_attributes( }), input.get_attribute("enctype").unwrap_or_else(|| { form.get_attribute("enctype") - .unwrap_or_else(|| "application/x-www-form-urlencoded".to_string()) + .unwrap_or_else(|| { + "application/x-www-form-urlencoded".to_string() + }) .to_lowercase() }), ) - } else if let Some(button) = el.dyn_ref::<web_sys::HtmlButtonElement>() { + } else if let Some(button) = + el.dyn_ref::<web_sys::HtmlButtonElement>() + { let form = ev .target() .unwrap() @@ -354,18 +383,25 @@ fn extract_form_attributes( }), button.get_attribute("enctype").unwrap_or_else(|| { form.get_attribute("enctype") - .unwrap_or_else(|| "application/x-www-form-urlencoded".to_string()) + .unwrap_or_else(|| { + "application/x-www-form-urlencoded".to_string() + }) .to_lowercase() }), ) } else { - leptos_dom::debug_warn!("<Form/> cannot be submitted from a tag other than <form>, <input>, or <button>"); + leptos_dom::debug_warn!( + "<Form/> cannot be submitted from a tag other than \ + <form>, <input>, or <button>" + ); panic!() } } None => match ev.target() { None => { - leptos_dom::debug_warn!("<Form/> SubmitEvent fired without a target."); + leptos_dom::debug_warn!( + "<Form/> SubmitEvent fired without a target." + ); panic!() } Some(form) => { @@ -375,8 +411,9 @@ fn extract_form_attributes( form.get_attribute("method") .unwrap_or_else(|| "get".to_string()), form.get_attribute("action").unwrap_or_default(), - form.get_attribute("enctype") - .unwrap_or_else(|| "application/x-www-form-urlencoded".to_string()), + form.get_attribute("enctype").unwrap_or_else(|| { + "application/x-www-form-urlencoded".to_string() + }), ) } }, @@ -386,7 +423,9 @@ fn extract_form_attributes( fn action_input_from_form_data<I: serde::de::DeserializeOwned>( form_data: &web_sys::FormData, ) -> Result<I, serde_urlencoded::de::Error> { - let data = web_sys::UrlSearchParams::new_with_str_sequence_sequence(form_data).unwrap_throw(); + let data = + web_sys::UrlSearchParams::new_with_str_sequence_sequence(form_data) + .unwrap_throw(); let data = data.to_string().as_string().unwrap_or_default(); serde_urlencoded::from_str::<I>(&data) } diff --git a/router/src/components/link.rs b/router/src/components/link.rs index a1106d279..97bbc8fb0 100644 --- a/router/src/components/link.rs +++ b/router/src/components/link.rs @@ -1,7 +1,5 @@ -use leptos::leptos_dom::IntoView; -use leptos::*; - use crate::{use_location, use_resolved_path, State}; +use leptos::{leptos_dom::IntoView, *}; /// Describes a value that is either a static or a reactive URL, i.e., /// a [String], a [&str], or a reactive `Fn() -> String`. diff --git a/router/src/components/outlet.rs b/router/src/components/outlet.rs index 8ac93adf5..a51cf80d2 100644 --- a/router/src/components/outlet.rs +++ b/router/src/components/outlet.rs @@ -1,7 +1,6 @@ -use std::{cell::Cell, rc::Rc}; - use crate::use_route; use leptos::*; +use std::{cell::Cell, rc::Rc}; /// Displays the child route nested in a parent route, allowing you to control exactly where /// that child route is displayed. Renders nothing if there is no nested child. @@ -19,7 +18,9 @@ pub fn Outlet(cx: Scope) -> impl IntoView { } set_outlet.set(None); } - (Some(child), Some((is_showing_val, _))) if child.id() == *is_showing_val => { + (Some(child), Some((is_showing_val, _))) + if child.id() == *is_showing_val => + { // do nothing: we don't need to rerender the component, because it's the same } (Some(child), prev) => { @@ -28,7 +29,8 @@ pub fn Outlet(cx: Scope) -> impl IntoView { } _ = cx.child_scope(|child_cx| { provide_context(child_cx, child.clone()); - set_outlet.set(Some(child.outlet(child_cx).into_view(child_cx))); + set_outlet + .set(Some(child.outlet(child_cx).into_view(child_cx))); is_showing.set(Some((child.id(), child_cx))); }); } diff --git a/router/src/components/route.rs b/router/src/components/route.rs index 8bdc77d6d..ed4d46fb0 100644 --- a/router/src/components/route.rs +++ b/router/src/components/route.rs @@ -1,14 +1,12 @@ -use std::{ - cell::{Cell, RefCell}, - rc::Rc, -}; - -use leptos::*; - use crate::{ matching::{resolve_path, PathMatch, RouteDefinition, RouteMatch}, ParamsMap, RouterContext, }; +use leptos::*; +use std::{ + cell::{Cell, RefCell}, + rc::Rc, +}; thread_local! { static ROUTE_ID: Cell<usize> = Cell::new(0); @@ -161,7 +159,11 @@ impl RouteContext { self.inner.params } - pub(crate) fn base(cx: Scope, path: &str, fallback: Option<fn(Scope) -> View>) -> Self { + pub(crate) fn base( + cx: Scope, + path: &str, + fallback: Option<fn(Scope) -> View>, + ) -> Self { Self { inner: Rc::new(RouteContextInner { cx, @@ -171,14 +173,17 @@ impl RouteContext { path: RefCell::new(path.to_string()), original_path: path.to_string(), params: create_memo(cx, |_| ParamsMap::new()), - outlet: Box::new(move |cx| fallback.as_ref().map(move |f| f(cx))), + outlet: Box::new(move |cx| { + fallback.as_ref().map(move |f| f(cx)) + }), }), } } /// Resolves a relative route, relative to the current route's 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) + resolve_path(&self.inner.base_path, to, Some(&self.inner.path.borrow())) + .map(String::from) } /// The nested child route, if any. diff --git a/router/src/components/router.rs b/router/src/components/router.rs index 425c327b9..c7c496b89 100644 --- a/router/src/components/router.rs +++ b/router/src/components/router.rs @@ -1,22 +1,17 @@ -use cfg_if::cfg_if; -use std::{cell::RefCell, rc::Rc}; - -use leptos::*; -use thiserror::Error; - -#[cfg(not(feature = "ssr"))] -use wasm_bindgen::JsCast; - -#[cfg(feature = "transition")] -use leptos_reactive::use_transition; - use crate::{ - create_location, matching::resolve_path, Branch, History, Location, LocationChange, - RouteContext, RouterIntegrationContext, State, + create_location, matching::resolve_path, Branch, History, Location, + LocationChange, RouteContext, RouterIntegrationContext, State, }; - #[cfg(not(feature = "ssr"))] use crate::{unescape, Url}; +use cfg_if::cfg_if; +use leptos::*; +#[cfg(feature = "transition")] +use leptos_reactive::use_transition; +use std::{cell::RefCell, rc::Rc}; +use thiserror::Error; +#[cfg(not(feature = "ssr"))] +use wasm_bindgen::JsCast; /// Provides for client-side and server-side routing. This should usually be somewhere near /// the root of the application. @@ -120,10 +115,12 @@ impl RouterContext { } // the current URL - let (reference, set_reference) = create_signal(cx, source.with(|s| s.value.clone())); + let (reference, set_reference) = + create_signal(cx, source.with(|s| s.value.clone())); // the current History.state - let (state, set_state) = create_signal(cx, source.with(|s| s.state.clone())); + let (state, set_state) = + create_signal(cx, source.with(|s| s.state.clone())); // we'll use this transition to wait for async resources to load when navigating to a new route #[cfg(feature = "transition")] @@ -131,7 +128,8 @@ impl RouterContext { // Each field of `location` reactively represents a different part of the current location let location = create_location(cx, reference, state); - let referrers: Rc<RefCell<Vec<LocationChange>>> = Rc::new(RefCell::new(Vec::new())); + let referrers: Rc<RefCell<Vec<LocationChange>>> = + Rc::new(RefCell::new(Vec::new())); // Create base route with fallback element let base_path = base_path.unwrap_or_default(); @@ -220,7 +218,9 @@ impl RouterContextInner { return Err(NavigationError::MaxRedirects); } - if resolved_to != this.reference.get() || options.state != (this.state).get() { + if resolved_to != this.reference.get() + || options.state != (this.state).get() + { if cfg!(feature = "server") { self.history.navigate(&LocationChange { value: resolved_to, @@ -230,12 +230,14 @@ impl RouterContextInner { }); } else { { - self.referrers.borrow_mut().push(LocationChange { - value: self.reference.get(), - replace: options.replace, - scroll: options.scroll, - state: self.state.get(), - }); + self.referrers.borrow_mut().push( + LocationChange { + value: self.reference.get(), + replace: options.replace, + scroll: options.scroll, + state: self.state.get(), + }, + ); } let len = self.referrers.borrow().len(); @@ -319,7 +321,9 @@ impl RouterContextInner { // let browser handle this event if link has target, // or if it doesn't have href or state // TODO "state" is set as a prop, not an attribute - if !target.is_empty() || (href.is_empty() && !a.has_attribute("state")) { + if !target.is_empty() + || (href.is_empty() && !a.has_attribute("state")) + { return; } @@ -347,15 +351,15 @@ impl RouterContextInner { } let to = path_name + &unescape(&url.search) + &unescape(&url.hash); - let state = get_property(a.unchecked_ref(), "state") - .ok() - .and_then(|value| { + let state = get_property(a.unchecked_ref(), "state").ok().and_then( + |value| { if value == wasm_bindgen::JsValue::UNDEFINED { None } else { Some(value) } - }); + }, + ); ev.prevent_default(); diff --git a/router/src/components/routes.rs b/router/src/components/routes.rs index 964109272..aee9c1f95 100644 --- a/router/src/components/routes.rs +++ b/router/src/components/routes.rs @@ -1,3 +1,11 @@ +use crate::{ + matching::{ + expand_optionals, get_route_matches, join_paths, Branch, Matcher, + RouteDefinition, RouteMatch, + }, + RouteContext, RouterContext, +}; +use leptos::*; use std::{ cell::{Cell, RefCell}, cmp::Reverse, @@ -5,16 +13,6 @@ use std::{ rc::Rc, }; -use leptos::*; - -use crate::{ - matching::{ - expand_optionals, get_route_matches, join_paths, Branch, Matcher, RouteDefinition, - RouteMatch, - }, - RouteContext, RouterContext, -}; - /// Contains route definitions and manages the actual routing process. /// /// You should locate the `<Routes/>` component wherever on the page you want the routes to appear. @@ -92,7 +90,8 @@ pub fn Routes( { 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()); + prev_one + .set_path(next_match.path_match.path.clone()); } if i >= next.borrow().len() { next.borrow_mut().push(prev_one); @@ -116,10 +115,12 @@ pub fn Routes( { let next = next.clone(); move |cx| { - if let Some(route_states) = use_context::<Memo<RouterState>>(cx) + if let Some(route_states) = + use_context::<Memo<RouterState>>(cx) { route_states.with(|route_states| { - let routes = route_states.routes.borrow(); + let routes = + route_states.routes.borrow(); routes.get(i + 1).cloned() }) } else { @@ -184,11 +185,15 @@ pub fn Routes( if prev.is_none() || !root_equal.get() { let (root_view, _) = cx.run_child_scope(|cx| { - let prev_cx = std::mem::replace(&mut *root_cx.borrow_mut(), Some(cx)); + let prev_cx = std::mem::replace( + &mut *root_cx.borrow_mut(), + Some(cx), + ); if let Some(prev_cx) = prev_cx { prev_cx.dispose(); } - root.as_ref().map(|route| route.outlet(cx).into_view(cx)) + root.as_ref() + .map(|route| route.outlet(cx).into_view(cx)) }); root_view } else { @@ -231,7 +236,9 @@ impl RouteData { #[allow(clippy::bool_to_int_with_if)] // on the splat.is_none() segments.iter().fold( (segments.len() as i32) - if splat.is_none() { 0 } else { 1 }, - |score, segment| score + if segment.starts_with(':') { 2 } else { 3 }, + |score, segment| { + score + if segment.starts_with(':') { 2 } else { 3 } + }, ) } } diff --git a/router/src/extract_routes.rs b/router/src/extract_routes.rs index 978cca2c7..5ef7ea476 100644 --- a/router/src/extract_routes.rs +++ b/router/src/extract_routes.rs @@ -1,8 +1,7 @@ +use crate::{Branch, RouterIntegrationContext, ServerIntegration}; use leptos::*; use std::{cell::RefCell, rc::Rc}; -use crate::{Branch, RouterIntegrationContext, ServerIntegration}; - /// Context to contain all possible routes. #[derive(Clone, Default, Debug)] pub struct PossibleBranchContext(pub(crate) Rc<RefCell<Vec<Branch>>>); @@ -10,7 +9,9 @@ pub struct PossibleBranchContext(pub(crate) Rc<RefCell<Vec<Branch>>>); /// Generates a list of all routes this application could possibly serve. This returns the raw routes in the leptos_router /// format. Odds are you want `generate_route_list()` from either the actix or axum integrations if you want /// to work with their router -pub fn generate_route_list_inner<IV>(app_fn: impl FnOnce(Scope) -> IV + 'static) -> Vec<String> +pub fn generate_route_list_inner<IV>( + app_fn: impl FnOnce(Scope) -> IV + 'static, +) -> Vec<String> where IV: IntoView + 'static, { @@ -29,7 +30,9 @@ where let branches = branches.0.borrow(); branches .iter() - .flat_map(|branch| branch.routes.last().map(|route| route.pattern.clone())) + .flat_map(|branch| { + branch.routes.last().map(|route| route.pattern.clone()) + }) .collect() }) } diff --git a/router/src/history/location.rs b/router/src/history/location.rs index a9b40ac5f..104ece4ae 100644 --- a/router/src/history/location.rs +++ b/router/src/history/location.rs @@ -1,11 +1,13 @@ +use super::params::ParamsMap; +use crate::{State, Url}; use leptos::*; -use crate::{State, Url}; - -use super::params::ParamsMap; - /// Creates a reactive location from the given path and state. -pub fn create_location(cx: Scope, path: ReadSignal<String>, state: ReadSignal<State>) -> Location { +pub fn create_location( + cx: Scope, + path: ReadSignal<String>, + state: ReadSignal<State>, +) -> Location { let url = create_memo(cx, move |prev: Option<&Url>| { path.with(|path| match Url::try_from(path.as_str()) { Ok(url) => url, @@ -16,10 +18,12 @@ pub fn create_location(cx: Scope, path: ReadSignal<String>, state: ReadSignal<St }) }); - let pathname = create_memo(cx, move |_| url.with(|url| url.pathname.clone())); + let pathname = + create_memo(cx, move |_| url.with(|url| url.pathname.clone())); let search = create_memo(cx, move |_| url.with(|url| url.search.clone())); let hash = create_memo(cx, move |_| url.with(|url| url.hash.clone())); - let query = create_memo(cx, move |_| url.with(|url| url.search_params.clone())); + let query = + create_memo(cx, move |_| url.with(|url| url.search_params.clone())); Location { pathname, diff --git a/router/src/history/mod.rs b/router/src/history/mod.rs index 8040e4594..5060e83c0 100644 --- a/router/src/history/mod.rs +++ b/router/src/history/mod.rs @@ -1,6 +1,5 @@ -use std::rc::Rc; - use leptos::*; +use std::rc::Rc; mod location; mod params; @@ -83,11 +82,19 @@ impl History for BrowserIntegration { if loc.replace { history - .replace_state_with_url(&loc.state.to_js_value(), "", Some(&loc.value)) + .replace_state_with_url( + &loc.state.to_js_value(), + "", + Some(&loc.value), + ) .unwrap_throw(); } else { history - .push_state_with_url(&loc.state.to_js_value(), "", Some(&loc.value)) + .push_state_with_url( + &loc.state.to_js_value(), + "", + Some(&loc.value), + ) .unwrap_throw(); } // scroll to el @@ -116,7 +123,9 @@ impl History for BrowserIntegration { /// # use leptos_router::*; /// # use leptos::*; /// # run_scope(create_runtime(), |cx| { -/// let integration = ServerIntegration { path: "insert/current/path/here".to_string() }; +/// let integration = ServerIntegration { +/// path: "insert/current/path/here".to_string(), +/// }; /// provide_context(cx, RouterIntegrationContext::new(integration)); /// # }); /// ``` diff --git a/router/src/history/params.rs b/router/src/history/params.rs index 57bf33a0c..a937ac431 100644 --- a/router/src/history/params.rs +++ b/router/src/history/params.rs @@ -104,7 +104,8 @@ pub trait IntoParam where Self: Sized, { - fn into_param(value: Option<&str>, name: &str) -> Result<Self, ParamsError>; + fn into_param(value: Option<&str>, name: &str) + -> Result<Self, ParamsError>; } impl<T> IntoParam for Option<T> @@ -112,7 +113,10 @@ where T: FromStr, <T as FromStr>::Err: std::error::Error + 'static, { - fn into_param(value: Option<&str>, _name: &str) -> Result<Self, ParamsError> { + fn into_param( + value: Option<&str>, + _name: &str, + ) -> Result<Self, ParamsError> { match value { None => Ok(None), Some(value) => match T::from_str(value) { diff --git a/router/src/history/url.rs b/router/src/history/url.rs index 8a61fd851..db718b462 100644 --- a/router/src/history/url.rs +++ b/router/src/history/url.rs @@ -22,7 +22,8 @@ pub fn unescape(s: &str) -> String { #[cfg(feature = "ssr")] pub fn escape(s: &str) -> String { - percent_encoding::utf8_percent_encode(s, percent_encoding::NON_ALPHANUMERIC).to_string() + percent_encoding::utf8_percent_encode(s, percent_encoding::NON_ALPHANUMERIC) + .to_string() } #[cfg(not(feature = "ssr"))] @@ -36,7 +37,8 @@ impl TryFrom<&str> for Url { fn try_from(url: &str) -> Result<Self, Self::Error> { let fake_host = String::from("http://leptos"); - let url = web_sys::Url::new_with_base(url, &fake_host).map_js_error()?; + let url = + web_sys::Url::new_with_base(url, &fake_host).map_js_error()?; Ok(Self { origin: url.origin(), pathname: url.pathname(), @@ -44,15 +46,30 @@ impl TryFrom<&str> for Url { search_params: ParamsMap( try_iter(&url.search_params()) .map_js_error()? - .ok_or("Failed to use URLSearchParams as an iterator".to_string())? + .ok_or( + "Failed to use URLSearchParams as an iterator" + .to_string(), + )? .map(|value| { - let array: Array = value.map_js_error()?.dyn_into().map_js_error()?; + let array: Array = + value.map_js_error()?.dyn_into().map_js_error()?; Ok(( - array.get(0).dyn_into::<JsString>().map_js_error()?.into(), - array.get(1).dyn_into::<JsString>().map_js_error()?.into(), + array + .get(0) + .dyn_into::<JsString>() + .map_js_error()? + .into(), + array + .get(1) + .dyn_into::<JsString>() + .map_js_error()? + .into(), )) }) - .collect::<Result<linear_map::LinearMap<String, String>, Self::Error>>()?, + .collect::<Result< + linear_map::LinearMap<String, String>, + Self::Error, + >>()?, ), hash: url.hash(), }) diff --git a/router/src/hooks.rs b/router/src/hooks.rs index 1b05631cc..9c9e13b6c 100644 --- a/router/src/hooks.rs +++ b/router/src/hooks.rs @@ -1,18 +1,18 @@ -use std::rc::Rc; - -use leptos::{create_memo, use_context, Memo, Scope}; - use crate::{ - Location, NavigateOptions, NavigationError, Params, ParamsError, ParamsMap, RouteContext, - RouterContext, + Location, NavigateOptions, NavigationError, Params, ParamsError, ParamsMap, + RouteContext, RouterContext, }; +use leptos::{create_memo, use_context, Memo, Scope}; +use std::rc::Rc; /// Returns the current [RouterContext], containing information about the router's state. pub fn use_router(cx: Scope) -> RouterContext { if let Some(router) = use_context::<RouterContext>(cx) { router } else { - leptos::leptos_dom::debug_warn!("You must call use_router() within a <Router/> component"); + leptos::leptos_dom::debug_warn!( + "You must call use_router() within a <Router/> component" + ); panic!("You must call use_router() within a <Router/> component"); } } @@ -59,7 +59,10 @@ where } /// Resolves the given path relative to the current route. -pub fn use_resolved_path(cx: Scope, path: impl Fn() -> String + 'static) -> Memo<Option<String>> { +pub fn use_resolved_path( + cx: Scope, + path: impl Fn() -> String + 'static, +) -> Memo<Option<String>> { let route = use_route(cx); create_memo(cx, move |_| { @@ -73,7 +76,11 @@ pub fn use_resolved_path(cx: Scope, path: impl Fn() -> String + 'static) -> Memo } /// Returns a function that can be used to navigate to a new route. -pub fn use_navigate(cx: Scope) -> impl Fn(&str, NavigateOptions) -> Result<(), NavigationError> { +pub fn use_navigate( + cx: Scope, +) -> impl Fn(&str, NavigateOptions) -> Result<(), NavigationError> { let router = use_router(cx); - move |to, options| Rc::clone(&router.inner).navigate_from_route(to, &options) + move |to, options| { + Rc::clone(&router.inner).navigate_from_route(to, &options) + } } diff --git a/router/src/lib.rs b/router/src/lib.rs index 5656d403e..4f1208874 100644 --- a/router/src/lib.rs +++ b/router/src/lib.rs @@ -28,7 +28,7 @@ //! ## Example //! //! ```rust -//! +//! //! use leptos::*; //! use leptos_router::*; //! @@ -128,7 +128,6 @@ //! fn About(cx: Scope) -> impl IntoView { //! todo!() //! } -//! //! ``` //! //! ## Module Route Definitions @@ -195,11 +194,9 @@ mod history; mod hooks; #[doc(hidden)] pub mod matching; -pub use matching::RouteDefinition; - pub use components::*; #[cfg(any(feature = "ssr", doc))] pub use extract_routes::*; pub use history::*; pub use hooks::*; -pub use matching::*; +pub use matching::{RouteDefinition, *}; diff --git a/router/src/matching/expand_optionals.rs b/router/src/matching/expand_optionals.rs index a2605dccf..39d2f8450 100644 --- a/router/src/matching/expand_optionals.rs +++ b/router/src/matching/expand_optionals.rs @@ -14,31 +14,38 @@ pub fn expand_optionals(pattern: &str) -> Vec<Cow<str>> { match captures { None => vec![pattern.into()], Some(matched) => { - let start: usize = js_sys::Reflect::get(&matched, &JsValue::from_str("index")) - .unwrap() - .as_f64() - .unwrap() as usize; + let start: usize = + js_sys::Reflect::get(&matched, &JsValue::from_str("index")) + .unwrap() + .as_f64() + .unwrap() as usize; let mut prefix = pattern[0..start].to_string(); - let mut suffix = &pattern[start + matched.get(1).as_string().unwrap().len()..]; + let mut suffix = + &pattern[start + matched.get(1).as_string().unwrap().len()..]; let mut prefixes = vec![prefix.clone()]; prefix += &matched.get(1).as_string().unwrap(); prefixes.push(prefix.clone()); - while let Some(matched) = OPTIONAL_RE_2.exec(suffix.trim_start_matches('?')) { + while let Some(matched) = + OPTIONAL_RE_2.exec(suffix.trim_start_matches('?')) + { prefix += &matched.get(1).as_string().unwrap(); prefixes.push(prefix.clone()); suffix = &suffix[matched.get(0).as_string().unwrap().len()..]; } - expand_optionals(suffix) - .iter() - .fold(Vec::new(), |mut results, expansion| { + expand_optionals(suffix).iter().fold( + Vec::new(), + |mut results, expansion| { results.extend(prefixes.iter().map(|prefix| { - Cow::Owned(prefix.clone() + expansion.trim_start_matches('?')) + Cow::Owned( + prefix.clone() + expansion.trim_start_matches('?'), + ) })); results - }) + }, + ) } } } @@ -65,20 +72,25 @@ pub fn expand_optionals(pattern: &str) -> Vec<Cow<str>> { prefix += &captures[1]; prefixes.push(prefix.clone()); - while let Some(captures) = OPTIONAL_RE_2.captures(suffix.trim_start_matches('?')) { + while let Some(captures) = + OPTIONAL_RE_2.captures(suffix.trim_start_matches('?')) + { prefix += &captures[1]; prefixes.push(prefix.clone()); suffix = &suffix[captures[0].len()..]; } - expand_optionals(suffix) - .iter() - .fold(Vec::new(), |mut results, expansion| { + expand_optionals(suffix).iter().fold( + Vec::new(), + |mut results, expansion| { results.extend(prefixes.iter().map(|prefix| { - Cow::Owned(prefix.clone() + expansion.trim_start_matches('?')) + Cow::Owned( + prefix.clone() + expansion.trim_start_matches('?'), + ) })); results - }) + }, + ) } } } diff --git a/router/src/matching/matcher.rs b/router/src/matching/matcher.rs index d498cc169..3e5c90ab0 100644 --- a/router/src/matching/matcher.rs +++ b/router/src/matching/matcher.rs @@ -60,7 +60,9 @@ impl Matcher { // quick path: not a match if // 1) matcher has add'l segments not found in location // 2) location has add'l segments, there's no splat, and partial matches not allowed - if loc_len < self.len || (len_diff > 0 && self.splat.is_none() && !self.partial) { + if loc_len < self.len + || (len_diff > 0 && self.splat.is_none() && !self.partial) + { None } // otherwise, start building a match @@ -68,7 +70,9 @@ impl Matcher { let mut path = String::new(); let mut params = ParamsMap::new(); - for (segment, loc_segment) in self.segments.iter().zip(loc_segments.iter()) { + for (segment, loc_segment) in + self.segments.iter().zip(loc_segments.iter()) + { if let Some(param_name) = segment.strip_prefix(':') { params.insert(param_name.into(), (*loc_segment).into()); } else if segment != loc_segment { diff --git a/router/src/matching/mod.rs b/router/src/matching/mod.rs index 240c2f12a..e6e288a79 100644 --- a/router/src/matching/mod.rs +++ b/router/src/matching/mod.rs @@ -3,20 +3,22 @@ mod matcher; mod resolve_path; mod route; +use crate::RouteData; pub use expand_optionals::*; pub use matcher::*; pub use resolve_path::*; pub use route::*; -use crate::RouteData; - #[derive(Debug, Clone, PartialEq)] pub(crate) struct RouteMatch { pub path_match: PathMatch, pub route: RouteData, } -pub(crate) fn get_route_matches(branches: Vec<Branch>, location: String) -> Vec<RouteMatch> { +pub(crate) fn get_route_matches( + branches: Vec<Branch>, + location: String, +) -> Vec<RouteMatch> { for branch in branches { if let Some(matches) = branch.matcher(&location) { return matches; diff --git a/router/src/matching/resolve_path.rs b/router/src/matching/resolve_path.rs index 4c0e31d35..69d64f172 100644 --- a/router/src/matching/resolve_path.rs +++ b/router/src/matching/resolve_path.rs @@ -17,7 +17,9 @@ pub fn resolve_path<'a>( let result = if let Some(from_path) = from_path { if path.starts_with('/') { base_path - } else if from_path.to_lowercase().find(&base_path.to_lowercase()) != Some(0) { + } else if from_path.to_lowercase().find(&base_path.to_lowercase()) + != Some(0) + { base_path + from_path } else { from_path diff --git a/router/src/matching/route.rs b/router/src/matching/route.rs index fffe5fa2e..c5f6c80e8 100644 --- a/router/src/matching/route.rs +++ b/router/src/matching/route.rs @@ -1,8 +1,6 @@ +use leptos::{leptos_dom::View, *}; use std::rc::Rc; -use leptos::leptos_dom::View; -use leptos::*; - /// Defines a single route in a nested route tree. This is the return /// type of the [`<Route/>`](crate::Route) component, but can also be /// used to build your own configuration-based or filesystem-based routing. diff --git a/rustfmt.toml b/rustfmt.toml index ae355e565..dd07b125b 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1,6 +1,5 @@ imports_granularity = "Crate" max_width = 80 format_strings = true -imports_layout = "vertical" group_imports = "One" -format_code_in_doc_comments = true \ No newline at end of file +format_code_in_doc_comments = true