mirror of
https://github.com/leptos-rs/leptos
synced 2024-11-10 06:44:17 +00:00
Get router working with new renderer
This commit is contained in:
parent
f9cc57acb9
commit
5bdd150347
8 changed files with 79 additions and 65 deletions
|
@ -40,13 +40,13 @@ where
|
|||
#[builder(default, setter(strip_option))]
|
||||
pub on_response: Option<Rc<dyn Fn(&web_sys::Response)>>,
|
||||
/// Component children; should include the HTML of the form elements.
|
||||
pub children: Box<dyn Fn() -> Vec<Element>>,
|
||||
pub children: Box<dyn Fn() -> Vec<View>>,
|
||||
}
|
||||
|
||||
/// An HTML [`form`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form) progressively
|
||||
/// enhanced to use client-side routing.
|
||||
#[allow(non_snake_case)]
|
||||
pub fn Form<A>(cx: Scope, props: FormProps<A>) -> Element
|
||||
pub fn Form<A>(cx: Scope, props: FormProps<A>) -> View
|
||||
where
|
||||
A: ToHref + 'static,
|
||||
{
|
||||
|
@ -130,11 +130,12 @@ where
|
|||
};
|
||||
|
||||
let children = children();
|
||||
let method = method.unwrap_or("get");
|
||||
|
||||
view! { cx,
|
||||
<form
|
||||
method=method
|
||||
action=action
|
||||
action=move || action.get()
|
||||
enctype=enctype
|
||||
on:submit=on_submit
|
||||
>
|
||||
|
@ -158,14 +159,14 @@ where
|
|||
/// manually using [leptos_server::Action::using_server_fn].
|
||||
pub action: Action<I, Result<O, ServerFnError>>,
|
||||
/// Component children; should include the HTML of the form elements.
|
||||
pub children: Box<dyn Fn() -> Vec<Element>>,
|
||||
pub children: Box<dyn Fn() -> Vec<View>>,
|
||||
}
|
||||
|
||||
/// Automatically turns a server [Action](leptos_server::Action) into an HTML
|
||||
/// [`form`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form)
|
||||
/// progressively enhanced to use client-side routing.
|
||||
#[allow(non_snake_case)]
|
||||
pub fn ActionForm<I, O>(cx: Scope, props: ActionFormProps<I, O>) -> Element
|
||||
pub fn ActionForm<I, O>(cx: Scope, props: ActionFormProps<I, O>) -> View
|
||||
where
|
||||
I: Clone + ServerFn + 'static,
|
||||
O: Clone + Serializable + 'static,
|
||||
|
@ -242,14 +243,14 @@ where
|
|||
/// manually using [leptos_server::Action::using_server_fn].
|
||||
pub action: MultiAction<I, Result<O, ServerFnError>>,
|
||||
/// Component children; should include the HTML of the form elements.
|
||||
pub children: Box<dyn Fn() -> Vec<Element>>,
|
||||
pub children: Box<dyn Fn() -> Vec<View>>,
|
||||
}
|
||||
|
||||
/// Automatically turns a server [MultiAction](leptos_server::MultiAction) into an HTML
|
||||
/// [`form`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form)
|
||||
/// progressively enhanced to use client-side routing.
|
||||
#[allow(non_snake_case)]
|
||||
pub fn MultiActionForm<I, O>(cx: Scope, props: MultiActionFormProps<I, O>) -> Element
|
||||
pub fn MultiActionForm<I, O>(cx: Scope, props: MultiActionFormProps<I, O>) -> View
|
||||
where
|
||||
I: Clone + ServerFn + 'static,
|
||||
O: Clone + Serializable + 'static,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use cfg_if::cfg_if;
|
||||
use leptos::leptos_dom::IntoChild;
|
||||
use leptos::leptos_dom::IntoView;
|
||||
use leptos::*;
|
||||
use typed_builder::TypedBuilder;
|
||||
|
||||
|
@ -43,9 +43,8 @@ where
|
|||
/// [`a`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a)
|
||||
/// progressively enhanced to use client-side routing.
|
||||
#[derive(TypedBuilder)]
|
||||
pub struct AProps<C, H>
|
||||
pub struct AProps<H>
|
||||
where
|
||||
C: IntoChild,
|
||||
H: ToHref + 'static,
|
||||
{
|
||||
/// Used to calculate the link's `href` attribute. Will be resolved relative
|
||||
|
@ -63,15 +62,14 @@ where
|
|||
#[builder(default)]
|
||||
pub replace: bool,
|
||||
/// The nodes or elements to be shown inside the link.
|
||||
pub children: Box<dyn Fn() -> Vec<C>>,
|
||||
pub children: Box<dyn Fn() -> Vec<View>>,
|
||||
}
|
||||
|
||||
/// An HTML [`a`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a)
|
||||
/// progressively enhanced to use client-side routing.
|
||||
#[allow(non_snake_case)]
|
||||
pub fn A<C, H>(cx: Scope, props: AProps<C, H>) -> Element
|
||||
pub fn A<H>(cx: Scope, props: AProps<H>) -> View
|
||||
where
|
||||
C: IntoChild,
|
||||
H: ToHref + 'static,
|
||||
{
|
||||
let location = use_location(cx);
|
||||
|
@ -94,12 +92,6 @@ where
|
|||
}
|
||||
});
|
||||
|
||||
let mut children = (props.children)();
|
||||
if children.len() != 1 {
|
||||
debug_warn!("[Link] Pass exactly one child to <A/>. If you want to pass more than one child, nest them within an element.");
|
||||
}
|
||||
let child = children.remove(0);
|
||||
|
||||
cfg_if! {
|
||||
if #[cfg(any(feature = "csr", feature = "hydrate"))] {
|
||||
view! { cx,
|
||||
|
@ -109,7 +101,7 @@ where
|
|||
prop:replace={props.replace}
|
||||
aria-current=move || if is_active.get() { Some("page") } else { None }
|
||||
>
|
||||
{child}
|
||||
{props.children}
|
||||
</a>
|
||||
}
|
||||
} else {
|
||||
|
@ -118,7 +110,7 @@ where
|
|||
href=move || href().unwrap_or_default()
|
||||
aria-current=move || if is_active() { Some("page") } else { None }
|
||||
>
|
||||
{child}
|
||||
{props.children}
|
||||
</a>
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,16 +1,33 @@
|
|||
use std::{cell::RefCell, rc::Rc};
|
||||
|
||||
use crate::use_route;
|
||||
use leptos::*;
|
||||
|
||||
/// 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.
|
||||
#[component]
|
||||
pub fn Outlet(cx: Scope) -> Child {
|
||||
pub fn Outlet(cx: Scope) -> View {
|
||||
let route = use_route(cx);
|
||||
create_memo(cx, move |_| {
|
||||
route.child().map(|child| {
|
||||
provide_context(child.cx(), child.clone());
|
||||
child.outlet().into_child(child.cx())
|
||||
})
|
||||
})
|
||||
.into_child(cx)
|
||||
let is_showing = Rc::new(RefCell::new(None));
|
||||
let (outlet, set_outlet) = create_signal(cx, None);
|
||||
create_effect(cx, move |_| {
|
||||
let is_showing_val = { is_showing.borrow().clone() };
|
||||
let child = route.child();
|
||||
match (route.child(), &is_showing_val) {
|
||||
(None, _) => {
|
||||
set_outlet.set(None);
|
||||
}
|
||||
(Some(child), Some(path))
|
||||
if Some(child.original_path().to_string()) == is_showing_val =>
|
||||
{
|
||||
// do nothing: we don't need to rerender the component, because it's the same
|
||||
}
|
||||
(Some(child), _) => {
|
||||
*is_showing.borrow_mut() = Some(child.original_path().to_string());
|
||||
provide_context(child.cx(), child.clone());
|
||||
set_outlet.set(Some(child.outlet().into_view(cx)))
|
||||
}
|
||||
}
|
||||
});
|
||||
(move || outlet.get()).into_view(cx)
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ use crate::{
|
|||
#[derive(TypedBuilder)]
|
||||
pub struct RouteProps<E, F>
|
||||
where
|
||||
E: IntoChild,
|
||||
E: IntoView,
|
||||
F: Fn(Scope) -> E + 'static,
|
||||
{
|
||||
/// The path fragment that this route should match. This can be static (`users`),
|
||||
|
@ -35,13 +35,13 @@ where
|
|||
#[allow(non_snake_case)]
|
||||
pub fn Route<E, F>(_cx: Scope, props: RouteProps<E, F>) -> RouteDefinition
|
||||
where
|
||||
E: IntoChild,
|
||||
E: IntoView,
|
||||
F: Fn(Scope) -> E + 'static,
|
||||
{
|
||||
RouteDefinition {
|
||||
path: props.path,
|
||||
children: props.children.map(|c| c()).unwrap_or_default(),
|
||||
element: Rc::new(move |cx| (props.element)(cx).into_child(cx)),
|
||||
element: Rc::new(move |cx| (props.element)(cx).into_view(cx)),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -87,17 +87,30 @@ impl RouteContext {
|
|||
self.inner.cx
|
||||
}
|
||||
|
||||
/// Returns the URL path of the current route.
|
||||
/// Returns the URL path of the current route,
|
||||
/// including param values in their places.
|
||||
///
|
||||
/// e.g., this will return `/article/0` rather than `/article/:id`.
|
||||
/// For the opposite behavior, see [RouteContext::original_path].
|
||||
pub fn path(&self) -> &str {
|
||||
&self.inner.path
|
||||
}
|
||||
|
||||
/// Returns the original URL path of the current route,
|
||||
/// with the param name rather than the matched parameter itself.
|
||||
///
|
||||
/// e.g., this will return `/article/:id` rather than `/article/0`
|
||||
/// For the opposite behavior, see [RouteContext::path].
|
||||
pub fn original_path(&self) -> &str {
|
||||
&self.inner.original_path
|
||||
}
|
||||
|
||||
/// A reactive wrapper for the route parameters that are currently matched.
|
||||
pub fn params(&self) -> Memo<ParamsMap> {
|
||||
self.inner.params
|
||||
}
|
||||
|
||||
pub(crate) fn base(cx: Scope, path: &str, fallback: Option<fn() -> Element>) -> Self {
|
||||
pub(crate) fn base(cx: Scope, path: &str, fallback: Option<fn() -> View>) -> Self {
|
||||
Self {
|
||||
inner: Rc::new(RouteContextInner {
|
||||
cx,
|
||||
|
@ -106,7 +119,7 @@ impl RouteContext {
|
|||
path: path.to_string(),
|
||||
original_path: path.to_string(),
|
||||
params: create_memo(cx, |_| ParamsMap::new()),
|
||||
outlet: Box::new(move || fallback.map(|f| f().into_child(cx))),
|
||||
outlet: Box::new(move || fallback.map(|f| f().into_view(cx))),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
@ -122,7 +135,7 @@ impl RouteContext {
|
|||
}
|
||||
|
||||
/// The view associated with the current route.
|
||||
pub fn outlet(&self) -> impl IntoChild {
|
||||
pub fn outlet(&self) -> impl IntoView {
|
||||
(self.inner.outlet)()
|
||||
}
|
||||
}
|
||||
|
@ -134,7 +147,7 @@ pub(crate) struct RouteContextInner {
|
|||
pub(crate) path: String,
|
||||
pub(crate) original_path: String,
|
||||
pub(crate) params: Memo<ParamsMap>,
|
||||
pub(crate) outlet: Box<dyn Fn() -> Option<Child>>,
|
||||
pub(crate) outlet: Box<dyn Fn() -> Option<View>>,
|
||||
}
|
||||
|
||||
impl PartialEq for RouteContextInner {
|
||||
|
|
|
@ -27,22 +27,22 @@ pub struct RouterProps {
|
|||
pub base: Option<&'static str>,
|
||||
#[builder(default, setter(strip_option))]
|
||||
/// A fallback that should be shown if no route is matched.
|
||||
pub fallback: Option<fn() -> Element>,
|
||||
pub fallback: Option<fn() -> View>,
|
||||
/// The `<Router/>` should usually wrap your whole page. It can contain
|
||||
/// any elements, and should include a [Routes](crate::Routes) component somewhere
|
||||
/// to define and display [Route](crate::Route)s.
|
||||
pub children: Box<dyn Fn() -> Vec<Element>>,
|
||||
pub children: Box<dyn Fn() -> Vec<View>>,
|
||||
}
|
||||
|
||||
/// Provides for client-side and server-side routing. This should usually be somewhere near
|
||||
/// the root of the application.
|
||||
#[allow(non_snake_case)]
|
||||
pub fn Router(cx: Scope, props: RouterProps) -> impl IntoChild {
|
||||
pub fn Router(cx: Scope, props: RouterProps) -> View {
|
||||
// create a new RouterContext and provide it to every component beneath the router
|
||||
let router = RouterContext::new(cx, props.base, props.fallback);
|
||||
provide_context(cx, router);
|
||||
|
||||
props.children
|
||||
props.children.into_view(cx)
|
||||
}
|
||||
|
||||
/// Context type that contains information about the current router state.
|
||||
|
@ -83,7 +83,7 @@ impl RouterContext {
|
|||
pub(crate) fn new(
|
||||
cx: Scope,
|
||||
base: Option<&'static str>,
|
||||
fallback: Option<fn() -> Element>,
|
||||
fallback: Option<fn() -> View>,
|
||||
) -> Self {
|
||||
cfg_if! {
|
||||
if #[cfg(any(feature = "csr", feature = "hydrate"))] {
|
||||
|
@ -136,9 +136,9 @@ impl RouterContext {
|
|||
// 3) update the state
|
||||
// this will trigger the new route match below
|
||||
create_render_effect(cx, move |_| {
|
||||
let LocationChange { value, state, .. } = source();
|
||||
let LocationChange { value, state, .. } = source.get();
|
||||
cx.untrack(move || {
|
||||
if value != reference() {
|
||||
if value != reference.get() {
|
||||
set_reference.update(move |r| *r = value);
|
||||
set_state.update(move |s| *s = state);
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ pub struct RoutesProps {
|
|||
///
|
||||
/// You should locate the `<Routes/>` component wherever on the page you want the routes to appear.
|
||||
#[allow(non_snake_case)]
|
||||
pub fn Routes(cx: Scope, props: RoutesProps) -> impl IntoChild {
|
||||
pub fn Routes(cx: Scope, props: RoutesProps) -> View {
|
||||
let router = use_context::<RouterContext>(cx).unwrap_or_else(|| {
|
||||
log::warn!("<Routes/> component should be nested within a <Router/>.");
|
||||
panic!()
|
||||
|
@ -120,7 +120,7 @@ pub fn Routes(cx: Scope, props: RoutesProps) -> impl IntoChild {
|
|||
if disposers.borrow().len() > i + 1 {
|
||||
let mut disposers = disposers.borrow_mut();
|
||||
let old_route_disposer = std::mem::replace(&mut disposers[i], disposer);
|
||||
old_route_disposer.dispose();
|
||||
//old_route_disposer.dispose();
|
||||
} else {
|
||||
disposers.borrow_mut().push(disposer);
|
||||
}
|
||||
|
@ -147,7 +147,7 @@ pub fn Routes(cx: Scope, props: RoutesProps) -> impl IntoChild {
|
|||
});
|
||||
|
||||
// show the root route
|
||||
create_memo(cx, move |prev| {
|
||||
let root = create_memo(cx, move |prev| {
|
||||
provide_context(cx, route_states);
|
||||
route_states.with(|state| {
|
||||
let root = state.routes.borrow();
|
||||
|
@ -157,12 +157,13 @@ pub fn Routes(cx: Scope, props: RoutesProps) -> impl IntoChild {
|
|||
}
|
||||
|
||||
if prev.is_none() || !root_equal.get() {
|
||||
root.as_ref().map(|route| route.outlet().into_child(cx))
|
||||
root.as_ref().map(|route| route.outlet().into_view(cx))
|
||||
} else {
|
||||
prev.cloned().unwrap()
|
||||
}
|
||||
})
|
||||
})
|
||||
});
|
||||
(move || root.get()).into_view(cx)
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
|
|
|
@ -39,7 +39,7 @@
|
|||
//! use leptos::*;
|
||||
//! use leptos_router::*;
|
||||
//!
|
||||
//! pub fn router_example(cx: Scope) -> Element {
|
||||
//! pub fn router_example(cx: Scope) -> View {
|
||||
//! view! {
|
||||
//! cx,
|
||||
//! <div id="root">
|
||||
|
@ -100,7 +100,7 @@
|
|||
//! }
|
||||
//!
|
||||
//! #[component]
|
||||
//! fn ContactList(cx: Scope) -> Element {
|
||||
//! fn ContactList(cx: Scope) -> View {
|
||||
//! // loads the contact list data once; doesn't reload when nested routes change
|
||||
//! let contacts = create_resource(cx, || (), |_| contact_list_data());
|
||||
//! view! {
|
||||
|
@ -118,7 +118,7 @@
|
|||
//! }
|
||||
//!
|
||||
//! #[component]
|
||||
//! fn Contact(cx: Scope) -> Element {
|
||||
//! fn Contact(cx: Scope) -> View {
|
||||
//! let params = use_params_map(cx);
|
||||
//! let data = create_resource(
|
||||
//! cx,
|
||||
|
@ -129,7 +129,7 @@
|
|||
//! }
|
||||
//!
|
||||
//! #[component]
|
||||
//! fn About(cx: Scope) -> Element {
|
||||
//! fn About(cx: Scope) -> View {
|
||||
//! todo!()
|
||||
//! }
|
||||
//!
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
use std::rc::Rc;
|
||||
|
||||
use leptos::leptos_dom::Child;
|
||||
use leptos::leptos_dom::View;
|
||||
use leptos::*;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct RouteDefinition {
|
||||
pub path: &'static str,
|
||||
pub children: Vec<RouteDefinition>,
|
||||
pub element: Rc<dyn Fn(Scope) -> Child>,
|
||||
pub element: Rc<dyn Fn(Scope) -> View>,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for RouteDefinition {
|
||||
|
@ -24,13 +24,3 @@ impl PartialEq for RouteDefinition {
|
|||
self.path == other.path && self.children == other.children
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for RouteDefinition {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
path: Default::default(),
|
||||
children: Default::default(),
|
||||
element: Rc::new(|_| Child::Null),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue