mirror of
https://github.com/leptos-rs/leptos
synced 2024-11-10 06:44:17 +00:00
Make necessary changes for stable
support for router
and meta
This commit is contained in:
parent
7f696a9ac4
commit
5c45538e9f
10 changed files with 278 additions and 159 deletions
|
@ -1,5 +1,6 @@
|
|||
use std::time::Duration;
|
||||
|
||||
use cfg_if::cfg_if;
|
||||
use wasm_bindgen::convert::FromWasmAbi;
|
||||
use wasm_bindgen::{prelude::Closure, JsCast, JsValue, UnwrapThrowExt};
|
||||
|
||||
|
@ -254,6 +255,8 @@ pub fn set_interval(
|
|||
Ok(IntervalHandle(handle))
|
||||
}
|
||||
|
||||
cfg_if! {
|
||||
if #[cfg(not(feature = "stable"))] {
|
||||
/// Adds an event listener to the target DOM element using implicit event delegation.
|
||||
pub fn add_event_listener<E>(
|
||||
target: &web_sys::Element,
|
||||
|
@ -279,6 +282,32 @@ pub fn add_event_listener_undelegated<E>(
|
|||
let cb = Closure::wrap(Box::new(cb) as Box<dyn FnMut(E)>).into_js_value();
|
||||
_ = target.add_event_listener_with_callback(event_name, cb.unchecked_ref());
|
||||
}
|
||||
} else {
|
||||
/// Adds an event listener to the target DOM element using implicit event delegation.
|
||||
pub fn add_event_listener(
|
||||
target: &web_sys::Element,
|
||||
event_name: &'static str,
|
||||
cb: impl FnMut(web_sys::Event) + 'static,
|
||||
)
|
||||
{
|
||||
let cb = Closure::wrap(Box::new(cb) as Box<dyn FnMut(web_sys::Event)>).into_js_value();
|
||||
let key = event_delegation::event_delegation_key(event_name);
|
||||
_ = js_sys::Reflect::set(target, &JsValue::from_str(&key), &cb);
|
||||
event_delegation::add_event_listener(event_name);
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn add_event_listener_undelegated(
|
||||
target: &web_sys::Element,
|
||||
event_name: &'static str,
|
||||
cb: impl FnMut(web_sys::Event) + 'static,
|
||||
)
|
||||
{
|
||||
let cb = Closure::wrap(Box::new(cb) as Box<dyn FnMut(web_sys::Event)>).into_js_value();
|
||||
_ = target.add_event_listener_with_callback(event_name, cb.unchecked_ref());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[inline(always)]
|
||||
|
|
|
@ -564,6 +564,18 @@ fn attr_to_tokens(
|
|||
let event_type = event_type.parse::<TokenStream>().expect("couldn't parse event name");
|
||||
|
||||
if mode != Mode::Ssr {
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(feature = "stable")] {
|
||||
if NON_BUBBLING_EVENTS.contains(&name.as_str()) {
|
||||
expressions.push(quote_spanned! {
|
||||
span => ::leptos::add_event_listener_undelegated(#el_id.unchecked_ref(), #name, #handler);
|
||||
});
|
||||
} else {
|
||||
expressions.push(quote_spanned! {
|
||||
span => ::leptos::add_event_listener(#el_id.unchecked_ref(), #name, #handler);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if NON_BUBBLING_EVENTS.contains(&name.as_str()) {
|
||||
expressions.push(quote_spanned! {
|
||||
span => ::leptos::add_event_listener_undelegated::<web_sys::#event_type>(#el_id.unchecked_ref(), #name, #handler);
|
||||
|
@ -573,6 +585,8 @@ fn attr_to_tokens(
|
|||
span => ::leptos::add_event_listener::<web_sys::#event_type>(#el_id.unchecked_ref(), #name, #handler);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
// this is here to avoid warnings about unused signals
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "leptos_meta"
|
||||
version = "0.0.4"
|
||||
version = "0.0.5"
|
||||
edition = "2021"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "leptos_router"
|
||||
version = "0.0.5"
|
||||
version = "0.0.6"
|
||||
edition = "2021"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
|
@ -58,7 +58,8 @@ default = ["csr"]
|
|||
csr = ["leptos/csr"]
|
||||
hydrate = ["leptos/hydrate"]
|
||||
ssr = ["leptos/ssr", "dep:url", "dep:regex"]
|
||||
stable = ["leptos/stable"]
|
||||
|
||||
[package.metadata.cargo-all-features]
|
||||
# No need to test optional dependencies as they are enabled by the ssr feature
|
||||
denylist = ["url", "regex"]
|
||||
denylist = ["url", "regex", "stable"]
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use crate::{use_navigate, use_resolved_path, TextProp};
|
||||
use cfg_if::cfg_if;
|
||||
use leptos::*;
|
||||
use std::{error::Error, rc::Rc};
|
||||
use typed_builder::TypedBuilder;
|
||||
|
@ -131,6 +132,14 @@ where
|
|||
|
||||
let children = children();
|
||||
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "stable")] {
|
||||
let on_submit = move |ev: web_sys::Event| on_submit(ev.unchecked_into());
|
||||
}
|
||||
};
|
||||
|
||||
cfg_if! {
|
||||
if #[cfg(not(feature = "stable"))] {
|
||||
view! { cx,
|
||||
<form
|
||||
method=method
|
||||
|
@ -142,6 +151,20 @@ where
|
|||
</form>
|
||||
}
|
||||
}
|
||||
else {
|
||||
view! { cx,
|
||||
<form
|
||||
method=method
|
||||
action=move || action.get()
|
||||
enctype=enctype
|
||||
on:submit=on_submit
|
||||
>
|
||||
{children}
|
||||
</form>
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Properties that can be passed to the [ActionForm] component, which
|
||||
/// automatically turns a server [Action](leptos_server::Action) into an HTML
|
||||
|
@ -282,6 +305,12 @@ where
|
|||
|
||||
let children = (props.children)();
|
||||
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "stable")] {
|
||||
let on_submit = move |ev: web_sys::Event| on_submit(ev.unchecked_into());
|
||||
}
|
||||
};
|
||||
|
||||
view! { cx,
|
||||
<form
|
||||
method="POST"
|
||||
|
|
|
@ -68,7 +68,6 @@ impl std::fmt::Debug for RouterContextInner {
|
|||
f.debug_struct("RouterContextInner")
|
||||
.field("location", &self.location)
|
||||
.field("base", &self.base)
|
||||
.field("history", &std::any::type_name_of_val(&self.history))
|
||||
.field("cx", &self.cx)
|
||||
.field("reference", &self.reference)
|
||||
.field("set_reference", &self.set_reference)
|
||||
|
@ -103,14 +102,16 @@ impl RouterContext {
|
|||
let base = base.unwrap_or_default();
|
||||
let base_path = resolve_path("", base, None);
|
||||
|
||||
if let Some(base_path) = &base_path && source.with(|s| s.value.is_empty()) {
|
||||
if let Some(base_path) = &base_path {
|
||||
if source.with(|s| s.value.is_empty()) {
|
||||
history.navigate(&LocationChange {
|
||||
value: base_path.to_string(),
|
||||
replace: true,
|
||||
scroll: false,
|
||||
state: State(None)
|
||||
state: State(None),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// the current URL
|
||||
let (reference, set_reference) = create_signal(cx, source.with(|s| s.value.clone()));
|
||||
|
@ -136,9 +137,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);
|
||||
}
|
||||
|
|
|
@ -1,9 +1,20 @@
|
|||
use std::{cmp::Reverse, rc::Rc, cell::{RefCell, Cell}, ops::IndexMut};
|
||||
use std::{
|
||||
cell::{Cell, RefCell},
|
||||
cmp::Reverse,
|
||||
ops::IndexMut,
|
||||
rc::Rc,
|
||||
};
|
||||
|
||||
use leptos::*;
|
||||
use typed_builder::TypedBuilder;
|
||||
|
||||
use crate::{matching::{expand_optionals, join_paths, Branch, Matcher, RouteDefinition, get_route_matches, RouteMatch}, RouterContext, RouteContext};
|
||||
use crate::{
|
||||
matching::{
|
||||
expand_optionals, get_route_matches, join_paths, Branch, Matcher, RouteDefinition,
|
||||
RouteMatch,
|
||||
},
|
||||
RouteContext, RouterContext,
|
||||
};
|
||||
|
||||
/// Props for the [Routes] component, which contains route definitions and manages routing.
|
||||
#[derive(TypedBuilder)]
|
||||
|
@ -34,9 +45,7 @@ pub fn Routes(cx: Scope, props: RoutesProps) -> impl IntoChild {
|
|||
// whenever path changes, update matches
|
||||
let matches = create_memo(cx, {
|
||||
let router = router.clone();
|
||||
move |_| {
|
||||
get_route_matches(branches.clone(), router.pathname().get())
|
||||
}
|
||||
move |_| get_route_matches(branches.clone(), router.pathname().get())
|
||||
});
|
||||
|
||||
// Rebuild the list of nested routes conservatively, and show the root route here
|
||||
|
@ -68,14 +77,18 @@ pub fn Routes(cx: Scope, props: RoutesProps) -> impl IntoChild {
|
|||
let prev_match = prev_matches.and_then(|p| p.get(i));
|
||||
let next_match = next_matches.get(i).unwrap();
|
||||
|
||||
if let Some(prev) = prev_routes && let Some(prev_match) = prev_match && next_match.route.key == prev_match.route.key {
|
||||
match (prev_routes, prev_match) {
|
||||
(Some(prev), Some(prev_match))
|
||||
if next_match.route.key == prev_match.route.key =>
|
||||
{
|
||||
let prev_one = { prev.borrow()[i].clone() };
|
||||
if i >= next.borrow().len() {
|
||||
next.borrow_mut().push(prev_one);
|
||||
} else {
|
||||
*(next.borrow_mut().index_mut(i)) = prev_one;
|
||||
}
|
||||
} else {
|
||||
}
|
||||
_ => {
|
||||
equal = false;
|
||||
if i == 0 {
|
||||
root_equal.set(false);
|
||||
|
@ -92,7 +105,9 @@ pub fn Routes(cx: Scope, props: RoutesProps) -> impl IntoChild {
|
|||
{
|
||||
let next = next.clone();
|
||||
move || {
|
||||
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();
|
||||
routes.get(i + 1).cloned()
|
||||
|
@ -102,9 +117,7 @@ pub fn Routes(cx: Scope, props: RoutesProps) -> impl IntoChild {
|
|||
}
|
||||
}
|
||||
},
|
||||
move || {
|
||||
matches.with(|m| m.get(i).cloned())
|
||||
}
|
||||
move || matches.with(|m| m.get(i).cloned()),
|
||||
);
|
||||
|
||||
if let Some(next_ctx) = next_ctx {
|
||||
|
@ -126,6 +139,7 @@ pub fn Routes(cx: Scope, props: RoutesProps) -> impl IntoChild {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if disposers.borrow().len() > next_matches.len() {
|
||||
let surplus_disposers = disposers.borrow_mut().split_off(next_matches.len() + 1);
|
||||
|
@ -134,7 +148,8 @@ pub fn Routes(cx: Scope, props: RoutesProps) -> impl IntoChild {
|
|||
}
|
||||
}
|
||||
|
||||
if let Some(prev) = &prev && equal {
|
||||
if let Some(prev) = &prev {
|
||||
if equal {
|
||||
RouterState {
|
||||
matches: next_matches.to_vec(),
|
||||
routes: prev_routes.cloned().unwrap_or_default(),
|
||||
|
@ -145,14 +160,22 @@ pub fn Routes(cx: Scope, props: RoutesProps) -> impl IntoChild {
|
|||
RouterState {
|
||||
matches: next_matches.to_vec(),
|
||||
routes: Rc::new(RefCell::new(next.borrow().to_vec())),
|
||||
root
|
||||
root,
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let root = next.borrow().get(0).cloned();
|
||||
RouterState {
|
||||
matches: next_matches.to_vec(),
|
||||
routes: Rc::new(RefCell::new(next.borrow().to_vec())),
|
||||
root,
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 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();
|
||||
|
@ -162,14 +185,20 @@ 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_child(cx))
|
||||
} else {
|
||||
prev.cloned().unwrap()
|
||||
}
|
||||
})
|
||||
})
|
||||
});
|
||||
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(feature = "stable")] {
|
||||
move || root.get()
|
||||
} else {
|
||||
root
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
|
|
|
@ -107,6 +107,22 @@ where
|
|||
fn into_param(value: Option<&str>, name: &str) -> Result<Self, ParamsError>;
|
||||
}
|
||||
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(not(feature = "stable"))] {
|
||||
auto trait NotOption {}
|
||||
impl<T> !NotOption for Option<T> {}
|
||||
|
||||
impl<T> IntoParam for T
|
||||
where
|
||||
T: FromStr + NotOption,
|
||||
<T as FromStr>::Err: std::error::Error + Send + Sync + 'static,
|
||||
{
|
||||
fn into_param(value: Option<&str>, name: &str) -> Result<Self, ParamsError> {
|
||||
let value = value.ok_or_else(|| ParamsError::MissingParam(name.to_string()))?;
|
||||
Self::from_str(value).map_err(|e| ParamsError::Params(Rc::new(e)))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> IntoParam for Option<T>
|
||||
where
|
||||
T: FromStr,
|
||||
|
@ -125,13 +141,10 @@ where
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto trait NotOption {}
|
||||
impl<T> !NotOption for Option<T> {}
|
||||
|
||||
} else {
|
||||
impl<T> IntoParam for T
|
||||
where
|
||||
T: FromStr + NotOption,
|
||||
T: FromStr,
|
||||
<T as FromStr>::Err: std::error::Error + Send + Sync + 'static,
|
||||
{
|
||||
fn into_param(value: Option<&str>, name: &str) -> Result<Self, ParamsError> {
|
||||
|
@ -139,6 +152,8 @@ where
|
|||
Self::from_str(value).map_err(|e| ParamsError::Params(Rc::new(e)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Errors that can occur while parsing params using [Params](crate::Params).
|
||||
#[derive(Error, Debug, Clone)]
|
||||
|
|
|
@ -138,10 +138,9 @@
|
|||
//!
|
||||
//! ```
|
||||
|
||||
#![feature(auto_traits)]
|
||||
#![feature(let_chains)]
|
||||
#![feature(negative_impls)]
|
||||
#![feature(type_name_of_val)]
|
||||
#![cfg_attr(not(feature = "stable"), feature(auto_traits))]
|
||||
#![cfg_attr(not(feature = "stable"), feature(negative_impls))]
|
||||
#![cfg_attr(not(feature = "stable"), feature(type_name_of_val))]
|
||||
|
||||
mod components;
|
||||
mod history;
|
||||
|
|
|
@ -80,7 +80,8 @@ impl Matcher {
|
|||
path.push_str(loc_segment);
|
||||
}
|
||||
|
||||
if let Some(splat) = &self.splat && !splat.is_empty() {
|
||||
if let Some(splat) = &self.splat {
|
||||
if !splat.is_empty() {
|
||||
let value = if len_diff > 0 {
|
||||
loc_segments[self.len..].join("/")
|
||||
} else {
|
||||
|
@ -88,6 +89,7 @@ impl Matcher {
|
|||
};
|
||||
params.insert(splat.into(), value);
|
||||
}
|
||||
}
|
||||
|
||||
Some(PathMatch { path, params })
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue