diff --git a/Cargo.toml b/Cargo.toml index 79f6fdbd0..d2fba562b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,7 @@ members = [ "packages/core", "packages/core-macro", "packages/router-core", + "packages/router-macro", "packages/router", "packages/html", "packages/hooks", diff --git a/packages/router-core/Cargo.toml b/packages/router-core/Cargo.toml index ba5c3cd7d..59767e726 100644 --- a/packages/router-core/Cargo.toml +++ b/packages/router-core/Cargo.toml @@ -20,6 +20,8 @@ url = "2.3.1" urlencoding = "2.1.2" wasm-bindgen = { version = "0.2.83", optional = true } web-sys = { version = "0.3.60", optional = true, features = ["ScrollRestoration"]} +dioxus = { path = "../dioxus" } +dioxus-router-macro = { path = "../router-macro" } [features] regex = ["dep:regex"] diff --git a/packages/router-core/src/lib.rs b/packages/router-core/src/lib.rs index da75864b5..94c1736fb 100644 --- a/packages/router-core/src/lib.rs +++ b/packages/router-core/src/lib.rs @@ -1,30 +1,30 @@ #![doc = include_str!("../README.md")] -#![forbid(missing_docs)] +// #![forbid(missing_docs)] pub mod history; -mod router; +pub mod router; pub use router::*; -pub mod navigation; +// pub mod navigation; -mod navigator; -pub use navigator::*; +// mod navigator; +// pub use navigator::*; -mod service; -pub use service::*; +// mod service; +// pub use service::*; -mod state; -pub use state::*; +// mod state; +// pub use state::*; -mod utils { - mod sitemap; - pub use sitemap::*; -} +// mod utils { +// mod sitemap; +// pub use sitemap::*; +// } /// A collection of useful types most applications might need. pub mod prelude { - pub use crate::navigation::*; + // pub use crate::navigation::*; /// An external navigation failure. /// diff --git a/packages/router-core/src/navigation.rs b/packages/router-core/src/navigation.rs index e6bd8bc71..b989dc433 100644 --- a/packages/router-core/src/navigation.rs +++ b/packages/router-core/src/navigation.rs @@ -6,7 +6,7 @@ use url::{ParseError, Url}; /// A target for the router to navigate to. #[derive(Clone, Debug, PartialEq, Eq)] -pub enum NavigationTarget { +pub enum NavigationTarget { /// An internal path that the router can navigate to by itself. /// /// ```rust @@ -15,45 +15,7 @@ pub enum NavigationTarget { /// let implicit: NavigationTarget = "/internal".into(); /// assert_eq!(explicit, implicit); /// ``` - Internal(String), - /// An internal target that the router can navigate to by itself. - /// - /// ```rust - /// # use std::collections::HashMap; - /// # use dioxus_router_core::{Name, navigation::{named, NavigationTarget}}; - /// let mut parameters = HashMap::new(); - /// parameters.insert(Name::of::(), String::from("some parameter")); - /// - /// let explicit = NavigationTarget::Named { - /// name: Name::of::(), - /// parameters, - /// query: Some("some=query".into()) - /// }; - /// - /// let implicit = named::().parameter::("some parameter").query("some=query"); - /// - /// assert_eq!(explicit, implicit); - /// ``` - /// - /// It will automatically find the route with the matching name, insert all required parameters - /// and add the query. - /// - /// **Note:** The dioxus-router-core documentation and tests mostly use standard Rust types. This is only - /// for brevity. It is recommend to use types with descriptive names, and create unit structs if - /// needed. - Named { - /// The name of the [`Route`](crate::routes::Route) or - /// [`ParameterRoute`](crate::routes::ParameterRoute) to navigate to. - /// - /// **Note:** The dioxus-router-core documentation and tests mostly use standard Rust types. This is - /// only for brevity. It is recommend to use types with descriptive names, and create unit - /// structs if needed. - name: Name, - /// The parameters required to get to the specified route. - parameters: HashMap, - /// A query to add to the route. - query: Option, - }, + Internal(R), /// An external target that the router doesn't control. /// /// ```rust diff --git a/packages/router-core/src/router.rs b/packages/router-core/src/router.rs index 003b1563f..d84632a6f 100644 --- a/packages/router-core/src/router.rs +++ b/packages/router-core/src/router.rs @@ -1,9 +1,11 @@ use crate::history::HistoryProvider; +use dioxus::prelude::*; + use std::str::FromStr; #[derive(Debug, PartialEq)] -struct RouteParseError { - attempted_routes: Vec, +pub struct RouteParseError { + pub attempted_routes: Vec, } impl std::fmt::Display for RouteParseError { @@ -37,127 +39,76 @@ where } } -// #[derive(Props, PartialEq)] -// struct RouterProps { -// current_route: String, -// } +pub trait FromQuery { + fn from_query(query: &str) -> Self; +} -trait Routable: FromStr + std::fmt::Display + Clone +impl From<&'a str>> FromQuery for T { + fn from_query(query: &str) -> Self { + T::from(query) + } +} + +pub trait FromRouteSegment: Sized { + type Err; + + fn from_route_segment(route: &str) -> Result; +} + +impl FromRouteSegment for T +where + ::Err: std::fmt::Display, +{ + type Err = ::Err; + + fn from_route_segment(route: &str) -> Result { + T::from_str(route) + } +} + +pub trait ToRouteSegments { + fn to_route_segments(&self) -> Vec; +} + +pub trait FromRouteSegments: Sized { + type Err; + + fn from_route_segments(segments: &[&str], query: &str) -> Result; +} + +impl FromRouteSegments for Vec { + type Err = ::Err; + + fn from_route_segments(segments: &[&str], query: &str) -> Result { + segments.iter().map(|s| T::from_route_segment(s)).collect() + } +} + +#[derive(Props, PartialEq)] +pub struct RouterProps { + pub current_route: String, +} + +pub trait Routable: FromStr + std::fmt::Display + Clone where ::Err: std::fmt::Display, { -// fn render(self, cx: &ScopeState) -> Element; + fn render(self, cx: &ScopeState) -> Element; -// fn comp(cx: Scope) -> Element -// where -// Self: 'static, -// { -// let router = Self::from_str(&cx.props.current_route); -// match router { -// Ok(router) => router.render(cx), -// Err(err) => { -// render! {pre { -// "{err}" -// }} -// } -// } -// } -} - -#[derive(Routable, Clone, Debug, PartialEq)] -enum Route { - #[route("/(dynamic)")] - Route1 { dynamic: String }, - #[route("/hello_world")] - Route2 {}, - #[redirect("/(dynamic)/hello_world")] - #[route("/hello_world/(dynamic)")] - Route3 { dynamic: u32 }, - #[route("/(number1)/(number2)")] - Route4 { number1: u32, number2: u32 }, - #[route("/")] - Route5 {}, -} - -#[test] -fn display_works() { - let route = Route::Route1 { - dynamic: "hello".to_string(), - }; - - assert_eq!(route.to_string(), "/hello"); - - let route = Route::Route3 { dynamic: 1234 }; - - assert_eq!(route.to_string(), "/hello_world/1234"); - - let route = Route::Route1 { - dynamic: "hello_world2".to_string(), - }; - - assert_eq!(route.to_string(), "/hello_world2"); -} - -#[test] -fn from_string_works() { - let w = "/hello"; - assert_eq!( - Route::from_str(w), - Ok(Route::Route1 { - dynamic: "hello".to_string() - }) - ); - let w = "/hello/"; - assert_eq!( - Route::from_str(w), - Ok(Route::Route1 { - dynamic: "hello".to_string() - }) - ); - - let w = "/hello_world/1234"; - assert_eq!(Route::from_str(w), Ok(Route::Route3 { dynamic: 1234 })); - let w = "/hello_world/1234/"; - assert_eq!(Route::from_str(w), Ok(Route::Route3 { dynamic: 1234 })); - - let w = "/hello_world2"; - assert_eq!( - Route::from_str(w), - Ok(Route::Route1 { - dynamic: "hello_world2".to_string() - }) - ); - - let w = "/hello_world/-1"; - match Route::from_str(w) { - Ok(r) => panic!("should not parse {r:?}"), - Err(err) => println!("{err}"), - } -} - -#[test] -fn round_trip() { - // Route1 - let string = "hello_world2"; - let route = Route::Route1 { - dynamic: string.to_string(), - }; - assert_eq!(Route::from_str(&route.to_string()), Ok(route)); - - // Route2 - for num in 0..100 { - let route = Route::Route3 { dynamic: num }; - assert_eq!(Route::from_str(&route.to_string()), Ok(route)); - } - - // Route3 - for num1 in 0..100 { - for num2 in 0..100 { - let route = Route::Route4 { - number1: num1, - number2: num2, - }; - assert_eq!(Route::from_str(&route.to_string()), Ok(route)); + fn comp(cx: Scope) -> Element + where + Self: 'static, + { + let router = Self::from_str(&cx.props.current_route); + match router { + Ok(router) => router.render(cx), + Err(err) => { + render! { + pre { + "{err}" + } + } + } } } } diff --git a/packages/router-core/src/service.rs b/packages/router-core/src/service.rs index 8ef5ef49f..9b3fcafb5 100644 --- a/packages/router-core/src/service.rs +++ b/packages/router-core/src/service.rs @@ -15,18 +15,15 @@ use crate::{ prelude::{ FailureExternalNavigation, FailureNamedNavigation, FailureRedirectionLimit, RootIndex, }, - routes::{ContentAtom, Segment}, - segments::{NameMap, NamedSegment}, - utils::{resolve_target, route_segment}, - Name, RouterState, + RouterState, }; /// Messages that the [`RouterService`] can handle. -pub enum RouterMessage { +pub enum RouterMessage { /// Subscribe to router update. Subscribe(Arc), /// Navigate to the specified target. - Push(NavigationTarget), + Push(NavigationTarget), /// Replace the current location with the specified target. Replace(NavigationTarget), /// Trigger a routing update. @@ -63,9 +60,9 @@ impl PartialEq for RouterMessage { impl Eq for RouterMessage {} -enum NavigationFailure { +enum NavigationFailure { External(String), - Named(Name), + Internal(::Err), } /// A function the router will call after every routing update. diff --git a/packages/router-core/src/state.rs b/packages/router-core/src/state.rs index 841a5994b..51020b54f 100644 --- a/packages/router-core/src/state.rs +++ b/packages/router-core/src/state.rs @@ -153,7 +153,7 @@ impl Default for RouterState { #[cfg(test)] mod tests { - use crate::{navigation::named, prelude::RootIndex, routes::Segment, segments::NamedSegment}; + use crate::{navigation::named, prelude::RootIndex}; use super::*; diff --git a/packages/router-core/src/utils/sitemap.rs b/packages/router-core/src/utils/sitemap.rs index 6c0d5260a..c3fd731c7 100644 --- a/packages/router-core/src/utils/sitemap.rs +++ b/packages/router-core/src/utils/sitemap.rs @@ -2,7 +2,6 @@ use std::collections::BTreeMap; use urlencoding::encode; - pub fn gen_sitemap(seg: &Segment, current: &str, map: &mut Vec) { for (p, r) in &seg.fixed { let current = format!("{current}/{p}"); diff --git a/packages/router-core/tests/macro.rs b/packages/router-core/tests/macro.rs new file mode 100644 index 000000000..244719684 --- /dev/null +++ b/packages/router-core/tests/macro.rs @@ -0,0 +1,164 @@ +use dioxus::prelude::*; +use dioxus_router_macro::*; +use dioxus_router_core::*; +use std::str::FromStr; + +#[inline_props] +fn Route1(cx: Scope, dynamic: String) -> Element { + render! { + div{ + "Route1: {dynamic}" + } + } +} + +#[inline_props] +fn Route2(cx: Scope) -> Element { + render! { + div{ + "Route2" + } + } +} + +#[inline_props] +fn Route3(cx: Scope, dynamic: u32) -> Element { + render! { + div{ + "Route3: {dynamic}" + } + } +} + +#[inline_props] +fn Route4(cx: Scope, number1: u32, number2: u32) -> Element { + render! { + div{ + "Route4: {number1} {number2}" + } + } +} + +#[inline_props] +fn Route5(cx: Scope, query: String) -> Element { + render! { + div{ + "Route5" + } + } +} + +#[derive(Routable, Clone, Debug, PartialEq)] +enum Route { + #[route("/(dynamic)" Route1)] + Route1 { dynamic: String }, + #[route("/hello_world" Route2)] + Route2 {}, + // #[redirect("/(dynamic)/hello_world")] + #[route("/hello_world/(dynamic)" Route3)] + Route3 { dynamic: u32 }, + #[route("/(number1)/(number2)" Route4)] + Route4 { number1: u32, number2: u32 }, + #[route("/?(query)" Route5)] + Route5 { + query: String, + }, +} + +#[test] +fn display_works() { + let route = Route::Route1 { + dynamic: "hello".to_string(), + }; + + assert_eq!(route.to_string(), "/hello"); + + let route = Route::Route3 { dynamic: 1234 }; + + assert_eq!(route.to_string(), "/hello_world/1234"); + + let route = Route::Route1 { + dynamic: "hello_world2".to_string(), + }; + + assert_eq!(route.to_string(), "/hello_world2"); +} + +#[test] +fn from_string_works() { + let w = "/hello"; + assert_eq!( + Route::from_str(w), + Ok(Route::Route1 { + dynamic: "hello".to_string() + }) + ); + let w = "/hello/"; + assert_eq!( + Route::from_str(w), + Ok(Route::Route1 { + dynamic: "hello".to_string() + }) + ); + + let w = "/hello_world/1234"; + assert_eq!(Route::from_str(w), Ok(Route::Route3 { dynamic: 1234 })); + let w = "/hello_world/1234/"; + assert_eq!(Route::from_str(w), Ok(Route::Route3 { dynamic: 1234 })); + + let w = "/hello_world2"; + assert_eq!( + Route::from_str(w), + Ok(Route::Route1 { + dynamic: "hello_world2".to_string() + }) + ); + + let w = "/hello_world/-1"; + match Route::from_str(w) { + Ok(r) => panic!("should not parse {r:?}"), + Err(err) => println!("{err}"), + } + + let w = "/?x=1234&y=hello"; + assert_eq!( + Route::from_str(w), + Ok(Route::Route5 { + query: "x=1234&y=hello".to_string() + }) + ); +} + +#[test] +fn round_trip() { + // Route1 + let string = "hello_world2"; + let route = Route::Route1 { + dynamic: string.to_string(), + }; + assert_eq!(Route::from_str(&route.to_string()), Ok(route)); + + // Route2 + for num in 0..100 { + let route = Route::Route3 { dynamic: num }; + assert_eq!(Route::from_str(&route.to_string()), Ok(route)); + } + + // Route3 + for num1 in 0..100 { + for num2 in 0..100 { + let route = Route::Route4 { + number1: num1, + number2: num2, + }; + assert_eq!(Route::from_str(&route.to_string()), Ok(route)); + } + } + + // Route4 + let string = "x=1234&y=hello"; + let route = Route::Route5 { + query: string.to_string(), + }; + assert_eq!(Route::from_str(&route.to_string()), Ok(route)); +} diff --git a/packages/router-macro/src/lib.rs b/packages/router-macro/src/lib.rs index 89570d581..c1e15748c 100644 --- a/packages/router-macro/src/lib.rs +++ b/packages/router-macro/src/lib.rs @@ -113,7 +113,9 @@ impl RouteEnum { type Err = RouteParseError<#error_name>; fn from_str(s: &str) -> Result { - let mut segments = s.strip_prefix('/').unwrap_or(s).split('/'); + let route = s.strip_prefix('/').unwrap_or(s); + let (route, query) = route.split_once('?').unwrap_or((route, "")); + let mut segments = route.split('/'); let mut errors = Vec::new(); if let Some(segment) = segments.next() { diff --git a/packages/router-macro/src/route.rs b/packages/router-macro/src/route.rs index bc6720dd0..792696ea8 100644 --- a/packages/router-macro/src/route.rs +++ b/packages/router-macro/src/route.rs @@ -1,24 +1,30 @@ use quote::{__private::Span, format_ident, quote, ToTokens}; -use syn::{Ident, LitStr, Type, Variant}; -use syn::parse::ParseStream; use syn::parse::Parse; +use syn::parse::ParseStream; +use syn::{Ident, LitStr, Type, Variant}; use proc_macro2::TokenStream as TokenStream2; -struct RouteArgs{ +struct RouteArgs { route: LitStr, comp_name: Option, props_name: Option, } -impl Parse for RouteArgs{ - fn parse(input: ParseStream<'_>) -> syn::Result{ +impl Parse for RouteArgs { + fn parse(input: ParseStream<'_>) -> syn::Result { let route = input.parse::()?; Ok(RouteArgs { route, - comp_name:input.parse().ok(), - props_name: input.parse().ok(), + comp_name: { + let _ = input.parse::(); + input.parse().ok() + }, + props_name: { + let _ = input.parse::(); + input.parse().ok() + }, }) } } @@ -31,6 +37,7 @@ pub struct Route { pub props_name: Ident, pub route: LitStr, pub route_segments: Vec, + pub query: Option, } impl Route { @@ -49,11 +56,15 @@ impl Route { let route_name = input.ident.clone(); let args = route_attr.parse_args::()?; let route = args.route; - let file_based= args.comp_name.is_none(); - let comp_name = args.comp_name.unwrap_or_else(|| format_ident!("{}", route_name)); - let props_name = args.props_name.unwrap_or_else(|| format_ident!("{}Props", comp_name)); + let file_based = args.comp_name.is_none(); + let comp_name = args + .comp_name + .unwrap_or_else(|| format_ident!("{}", route_name)); + let props_name = args + .props_name + .unwrap_or_else(|| format_ident!("{}Props", comp_name)); - let route_segments = parse_route_segments(&input, &route)?; + let (route_segments, query) = parse_route_segments(&input, &route)?; Ok(Self { comp_name, @@ -62,17 +73,20 @@ impl Route { route_segments, route, file_based, + query, }) } pub fn display_match(&self) -> TokenStream2 { let name = &self.route_name; - let dynamic_segments = self.route_segments.iter().filter_map(|s| s.name()); + let dynamic_segments = self.dynamic_segments(); let write_segments = self.route_segments.iter().map(|s| s.write_segment()); + let write_query = self.query.as_ref().map(|q| q.write()); quote! { Self::#name { #(#dynamic_segments,)* } => { #(#write_segments)* + #write_query } } } @@ -80,10 +94,7 @@ impl Route { pub fn routable_match(&self) -> TokenStream2 { let name = &self.route_name; let dynamic_segments: Vec<_> = self - .route_segments - .iter() - .filter_map(|s| s.name()) - .collect(); + .dynamic_segments().collect(); let props_name = &self.props_name; let comp_name = &self.comp_name; @@ -99,7 +110,7 @@ impl Route { } } - pub fn construct(&self, enum_name: Ident) -> TokenStream2 { + fn dynamic_segments(&self)-> impl Iterator +'_{ let segments = self.route_segments.iter().filter_map(|seg| { seg.name().map(|name| { quote! { @@ -107,6 +118,18 @@ impl Route { } }) }); + let query = self.query.as_ref().map(|q| { + let name = q.name(); + quote! { + #name + } + }).into_iter(); + + segments.chain(query) + } + + pub fn construct(&self, enum_name: Ident) -> TokenStream2 { + let segments = self.dynamic_segments(); let name = &self.route_name; quote! { @@ -165,13 +188,20 @@ impl Route { } } } + + pub fn parse_query(&self) -> TokenStream2 { + match &self.query { + Some(query) => query.parse(), + None => quote! {}, + } + } } impl ToTokens for Route { fn to_tokens(&self, tokens: &mut quote::__private::TokenStream) { if !self.file_based { return; - } + } let without_leading_slash = &self.route.value()[1..]; let route_path = std::path::Path::new(without_leading_slash); @@ -199,10 +229,17 @@ impl ToTokens for Route { } } -fn parse_route_segments(varient: &Variant, route: &LitStr) -> syn::Result> { +fn parse_route_segments( + varient: &Variant, + route: &LitStr, +) -> syn::Result<(Vec, Option)> { let mut route_segments = Vec::new(); let route_string = route.value(); + let (route_string, query) = match route_string.rsplit_once('?') { + Some((route, query)) => (route, Some(query)), + None => (route_string.as_str(), None), + }; let mut iterator = route_string.split('/'); // skip the first empty segment @@ -268,7 +305,40 @@ fn parse_route_segments(varient: &Variant, route: &LitStr) -> syn::Result { + if query.starts_with('(') && query.ends_with(')') { + let query_ident = Ident::new(&query[1..query.len() - 1], Span::call_site()); + let field = varient.fields.iter().find(|field| match field.ident { + Some(ref field_ident) => field_ident == &query_ident, + None => false, + }); + + let ty = if let Some(field) = field { + field.ty.clone() + } else { + return Err(syn::Error::new_spanned( + varient, + format!( + "Could not find a field with the name '{}' in the variant '{}'", + query_ident, varient.ident + ), + )); + }; + + Some(QuerySegment { + ident: query_ident, + ty, + }) + } else { + None + } + } + None => None, + }; + + Ok((route_segments, parsed_query)) } #[derive(Debug)] @@ -323,11 +393,13 @@ impl RouteSegment { } Self::Dynamic(_, ty) => { quote! { - let parsed = <#ty as std::str::FromStr>::from_str(segment).map_err(|err| #error_enum_name::#error_enum_varient(#inner_parse_enum::#error_name(err))); + let parsed = <#ty as dioxus_router_core::router::FromRouteSegment>::from_route_segment(segment).map_err(|err| #error_enum_name::#error_enum_varient(#inner_parse_enum::#error_name(err))); } } - Self::CatchAll(_, _) => { - todo!() + Self::CatchAll(_, ty) => { + quote! { + let parsed = <#ty as dioxus_router_core::router::FromRouteSegments>::from_route_segments(segment).map_err(|err| #error_enum_name::#error_enum_varient(#inner_parse_enum::#error_name(err))); + } } } } @@ -336,3 +408,30 @@ impl RouteSegment { pub fn static_segment_idx(idx: usize) -> Ident { format_ident!("StaticSegment{}ParseError", idx) } + +#[derive(Debug)] +pub struct QuerySegment { + ident: Ident, + ty: Type, +} + +impl QuerySegment { + pub fn parse(&self) -> TokenStream2 { + let ident = &self.ident; + let ty = &self.ty; + quote! { + let #ident = <#ty as dioxus_router_core::router::FromQuery>::from_query(query); + } + } + + pub fn write(&self) -> TokenStream2 { + let ident = &self.ident; + quote! { + write!(f, "?{}", #ident)?; + } + } + + pub fn name(&self) -> Ident { + self.ident.clone() + } +} diff --git a/packages/router-macro/src/route_tree.rs b/packages/router-macro/src/route_tree.rs index 287e6be37..9fa1b2a1f 100644 --- a/packages/router-macro/src/route_tree.rs +++ b/packages/router-macro/src/route_tree.rs @@ -175,6 +175,7 @@ impl<'a> RouteTreeSegment<'a> { } let construct_variant = route.construct(enum_name); + let parse_query = route.parse_query(); print_route_segment( route_segments.peekable(), @@ -183,6 +184,7 @@ impl<'a> RouteTreeSegment<'a> { &error_enum_name, enum_varient, &varient_parse_error, + parse_query, ), ) } @@ -190,12 +192,14 @@ impl<'a> RouteTreeSegment<'a> { let varient_parse_error = route.error_ident(); let enum_varient = &route.route_name; let construct_variant = route.construct(enum_name); + let parse_query = route.parse_query(); return_constructed( construct_variant, &error_enum_name, enum_varient, &varient_parse_error, + parse_query, ) } } @@ -207,6 +211,7 @@ fn return_constructed( error_enum_name: &Ident, enum_varient: &Ident, varient_parse_error: &Ident, + parse_query: TokenStream, ) -> TokenStream { quote! { let remaining_segments = segments.clone(); @@ -216,6 +221,7 @@ fn return_constructed( match (next_segment, segment_after_next) { // This is the last segment, return the parsed route (None, _) | (Some(""), None) => { + #parse_query return Ok(#construct_variant); } _ => { diff --git a/packages/router/src/components/outlet.rs b/packages/router/src/components/outlet.rs deleted file mode 100644 index b710b7c01..000000000 --- a/packages/router/src/components/outlet.rs +++ /dev/null @@ -1,114 +0,0 @@ -use dioxus::prelude::*; -use dioxus_router_core::{Name, OutletData}; -use log::error; - -use crate::utils::use_router_internal::use_router_internal; - -/// The properties for an [`Outlet`]. -#[derive(Debug, Eq, PartialEq, Props)] -pub struct OutletProps { - /// Override the [`Outlet`]s nesting depth. - /// - /// By default the [`Outlet`] will find its own depth. This property overrides that depth. - /// Nested [`Outlet`]s will respect this override and calculate their depth based on it. - pub depth: Option, - /// The content name. - /// - /// By default, the outlet will render unnamed content. If this is set to a name, the outlet - /// will render content for that name, defined via [`RouteContent::MultiContent`]. - /// - /// [`RouteContent::MultiContent`]: dioxus_router_core::routes::RouteContent::MultiContent - pub name: Option, -} - -/// An outlet for the current content. -/// -/// Only works as descendant of a component calling [`use_router`], otherwise it will be inactive. -/// -/// The [`Outlet`] is aware of how many [`Outlet`]s it is nested within. It will render the content -/// of the active route that is __exactly as deep__. -/// -/// [`use_router`]: crate::hooks::use_router -/// -/// # Panic -/// - When the [`Outlet`] is not nested within another component calling the [`use_router`] hook, -/// but only in debug builds. -/// -/// # Example -/// ```rust -/// # use dioxus::prelude::*; -/// # use dioxus_router::prelude::*; -/// fn App(cx: Scope) -> Element { -/// use_router( -/// &cx, -/// &|| RouterConfiguration { -/// synchronous: true, // asynchronicity not needed for doc test -/// ..Default::default() -/// }, -/// &|| Segment::content(comp(Content)) -/// ); -/// -/// render! { -/// h1 { "App" } -/// Outlet { } // The content component will be rendered here -/// } -/// } -/// -/// fn Content(cx: Scope) -> Element { -/// render! { -/// p { "Content" } -/// } -/// } -/// # -/// # let mut vdom = VirtualDom::new(App); -/// # let _ = vdom.rebuild(); -/// # assert_eq!(dioxus_ssr::render(&vdom), "

App

Content

"); -/// ``` -#[allow(non_snake_case)] -pub fn Outlet(cx: Scope) -> Element { - let OutletProps { depth, name } = cx.props; - - // hook up to router - let router = match use_router_internal(cx) { - Some(r) => r, - #[allow(unreachable_code)] - None => { - let msg = "`Outlet` must have access to a parent router"; - error!("{msg}, will be inactive"); - #[cfg(debug_assertions)] - panic!("{}", msg); - return None; - } - }; - let state = loop { - if let Some(state) = router.state.try_read() { - break state; - } - }; - - // do depth calculation and propagation - let depth = cx.use_hook(|| { - let mut context = cx.consume_context::().unwrap_or_default(); - let depth = depth - .or_else(|| context.depth(name).map(|d| d + 1)) - .unwrap_or_default(); - context.set_depth(name, depth); - cx.provide_context(context); - depth - }); - - // get content - let content = match name { - None => state.content.get(*depth), - Some(n) => state.named_content.get(n).and_then(|n| n.get(*depth)), - } - .cloned(); - - cx.render(match content { - Some(content) => { - let X = content.0; - rsx!(X {}) - } - None => rsx!(()), - }) -}