From 5c45538e9f9e8e6a684142df4bc8b7976ebaf107 Mon Sep 17 00:00:00 2001 From: Greg Johnston Date: Mon, 5 Dec 2022 18:55:03 -0500 Subject: [PATCH] Make necessary changes for `stable` support for `router` and `meta` --- leptos_dom/src/operations.rs | 75 ++++++++++----- leptos_macro/src/view.rs | 30 ++++-- meta/Cargo.toml | 2 +- router/Cargo.toml | 5 +- router/src/components/form.rs | 47 ++++++++-- router/src/components/router.rs | 23 ++--- router/src/components/routes.rs | 161 +++++++++++++++++++------------- router/src/history/params.rs | 71 ++++++++------ router/src/lib.rs | 7 +- router/src/matching/matcher.rs | 16 ++-- 10 files changed, 278 insertions(+), 159 deletions(-) diff --git a/leptos_dom/src/operations.rs b/leptos_dom/src/operations.rs index a338bea92..a18b40740 100644 --- a/leptos_dom/src/operations.rs +++ b/leptos_dom/src/operations.rs @@ -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,30 +255,58 @@ pub fn set_interval( Ok(IntervalHandle(handle)) } -/// 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(E) + 'static, -) where - E: FromWasmAbi + 'static, -{ - let cb = Closure::wrap(Box::new(cb) as Box).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); -} +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( + target: &web_sys::Element, + event_name: &'static str, + cb: impl FnMut(E) + 'static, + ) where + E: FromWasmAbi + 'static, + { + let cb = Closure::wrap(Box::new(cb) as Box).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(E) + 'static, -) where - E: FromWasmAbi + 'static, -{ - let cb = Closure::wrap(Box::new(cb) as Box).into_js_value(); - _ = target.add_event_listener_with_callback(event_name, cb.unchecked_ref()); + #[doc(hidden)] + pub fn add_event_listener_undelegated( + target: &web_sys::Element, + event_name: &'static str, + cb: impl FnMut(E) + 'static, + ) where + E: FromWasmAbi + 'static, + { + let cb = Closure::wrap(Box::new(cb) as Box).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).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).into_js_value(); + _ = target.add_event_listener_with_callback(event_name, cb.unchecked_ref()); + } + } } #[doc(hidden)] diff --git a/leptos_macro/src/view.rs b/leptos_macro/src/view.rs index 77c6c474e..d75eb4ccd 100644 --- a/leptos_macro/src/view.rs +++ b/leptos_macro/src/view.rs @@ -564,14 +564,28 @@ fn attr_to_tokens( let event_type = event_type.parse::().expect("couldn't parse event name"); if mode != Mode::Ssr { - 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); - }); + 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::(#el_id.unchecked_ref(), #name, #handler); + }); + } else { + expressions.push(quote_spanned! { + span => ::leptos::add_event_listener::(#el_id.unchecked_ref(), #name, #handler); + }); + } + } } } else { diff --git a/meta/Cargo.toml b/meta/Cargo.toml index 50cea5b4b..9d3f9730f 100644 --- a/meta/Cargo.toml +++ b/meta/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "leptos_meta" -version = "0.0.4" +version = "0.0.5" edition = "2021" authors = ["Greg Johnston"] license = "MIT" diff --git a/router/Cargo.toml b/router/Cargo.toml index 4df8099c0..cd7875c39 100644 --- a/router/Cargo.toml +++ b/router/Cargo.toml @@ -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"] diff --git a/router/src/components/form.rs b/router/src/components/form.rs index c072e1230..b0054de8f 100644 --- a/router/src/components/form.rs +++ b/router/src/components/form.rs @@ -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,15 +132,37 @@ where let children = children(); - view! { cx, -
- {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, +
+ {children} +
+ } + } + else { + view! { cx, +
+ {children} +
+ } + } } } @@ -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,
Vec>, } -/// Contains route definitions and manages the actual routing process. -/// +/// Contains route definitions and manages the actual routing process. +/// /// You should locate the `` component wherever on the page you want the routes to appear. #[allow(non_snake_case)] pub fn Routes(cx: Scope, props: RoutesProps) -> impl IntoChild { @@ -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,61 +77,66 @@ 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 { - 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); + 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; + } } + _ => { + equal = false; + if i == 0 { + root_equal.set(false); + } - let disposer = cx.child_scope({ - let next = next.clone(); - let router = Rc::clone(&router.inner); - move |cx| { + let disposer = cx.child_scope({ let next = next.clone(); - let next_ctx = RouteContext::new( - cx, - &RouterContext { inner: router }, - { - let next = next.clone(); - move || { - if let Some(route_states) = use_context::>(cx) { - route_states.with(|route_states| { - let routes = route_states.routes.borrow(); - routes.get(i + 1).cloned() - }) - } else { - next.borrow().get(i + 1).cloned() + let router = Rc::clone(&router.inner); + move |cx| { + let next = next.clone(); + let next_ctx = RouteContext::new( + cx, + &RouterContext { inner: router }, + { + let next = next.clone(); + move || { + if let Some(route_states) = + use_context::>(cx) + { + route_states.with(|route_states| { + let routes = route_states.routes.borrow(); + routes.get(i + 1).cloned() + }) + } else { + next.borrow().get(i + 1).cloned() + } } - } - }, - move || { - matches.with(|m| m.get(i).cloned()) - } - ); + }, + move || matches.with(|m| m.get(i).cloned()), + ); - if let Some(next_ctx) = next_ctx { - if next.borrow().len() > i + 1 { - next.borrow_mut()[i] = next_ctx; - } else { - next.borrow_mut().push(next_ctx); + if let Some(next_ctx) = next_ctx { + if next.borrow().len() > i + 1 { + next.borrow_mut()[i] = next_ctx; + } else { + next.borrow_mut().push(next_ctx); + } } } - } - }); + }); - 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(); - } else { - disposers.borrow_mut().push(disposer); + 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(); + } else { + disposers.borrow_mut().push(disposer); + } } } } @@ -134,25 +148,34 @@ pub fn Routes(cx: Scope, props: RoutesProps) -> impl IntoChild { } } - if let Some(prev) = &prev && equal { - RouterState { - matches: next_matches.to_vec(), - routes: prev_routes.cloned().unwrap_or_default(), - root: prev.root.clone(), + if let Some(prev) = &prev { + if equal { + RouterState { + matches: next_matches.to_vec(), + routes: prev_routes.cloned().unwrap_or_default(), + root: prev.root.clone(), + } + } else { + let root = next.borrow().get(0).cloned(); + RouterState { + matches: next_matches.to_vec(), + routes: Rc::new(RefCell::new(next.borrow().to_vec())), + 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 + 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)] diff --git a/router/src/history/params.rs b/router/src/history/params.rs index 497b4c903..5dc7ecfe7 100644 --- a/router/src/history/params.rs +++ b/router/src/history/params.rs @@ -107,36 +107,51 @@ where fn into_param(value: Option<&str>, name: &str) -> Result; } -impl IntoParam for Option -where - T: FromStr, - ::Err: std::error::Error + 'static, -{ - fn into_param(value: Option<&str>, _name: &str) -> Result { - match value { - None => Ok(None), - Some(value) => match T::from_str(value) { - Ok(value) => Ok(Some(value)), - Err(e) => { - eprintln!("{}", e); - Err(ParamsError::Params(Rc::new(e))) - } - }, +cfg_if::cfg_if! { + if #[cfg(not(feature = "stable"))] { + auto trait NotOption {} + impl !NotOption for Option {} + + impl IntoParam for T + where + T: FromStr + NotOption, + ::Err: std::error::Error + Send + Sync + 'static, + { + fn into_param(value: Option<&str>, name: &str) -> Result { + let value = value.ok_or_else(|| ParamsError::MissingParam(name.to_string()))?; + Self::from_str(value).map_err(|e| ParamsError::Params(Rc::new(e))) + } } - } -} -auto trait NotOption {} -impl !NotOption for Option {} - -impl IntoParam for T -where - T: FromStr + NotOption, - ::Err: std::error::Error + Send + Sync + 'static, -{ - fn into_param(value: Option<&str>, name: &str) -> Result { - let value = value.ok_or_else(|| ParamsError::MissingParam(name.to_string()))?; - Self::from_str(value).map_err(|e| ParamsError::Params(Rc::new(e))) + impl IntoParam for Option + where + T: FromStr, + ::Err: std::error::Error + 'static, + { + fn into_param(value: Option<&str>, _name: &str) -> Result { + match value { + None => Ok(None), + Some(value) => match T::from_str(value) { + Ok(value) => Ok(Some(value)), + Err(e) => { + eprintln!("{}", e); + Err(ParamsError::Params(Rc::new(e))) + } + }, + } + } + } + } else { + impl IntoParam for T + where + T: FromStr, + ::Err: std::error::Error + Send + Sync + 'static, + { + fn into_param(value: Option<&str>, name: &str) -> Result { + let value = value.ok_or_else(|| ParamsError::MissingParam(name.to_string()))?; + Self::from_str(value).map_err(|e| ParamsError::Params(Rc::new(e))) + } + } } } diff --git a/router/src/lib.rs b/router/src/lib.rs index c41418294..bea59c9a0 100644 --- a/router/src/lib.rs +++ b/router/src/lib.rs @@ -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; diff --git a/router/src/matching/matcher.rs b/router/src/matching/matcher.rs index 37407a34e..d498cc169 100644 --- a/router/src/matching/matcher.rs +++ b/router/src/matching/matcher.rs @@ -80,13 +80,15 @@ impl Matcher { path.push_str(loc_segment); } - if let Some(splat) = &self.splat && !splat.is_empty() { - let value = if len_diff > 0 { - loc_segments[self.len..].join("/") - } else { - "".into() - }; - params.insert(splat.into(), value); + if let Some(splat) = &self.splat { + if !splat.is_empty() { + let value = if len_diff > 0 { + loc_segments[self.len..].join("/") + } else { + "".into() + }; + params.insert(splat.into(), value); + } } Some(PathMatch { path, params })