From 2bd97eff39e69fddc3405196b2dc959bf326190c Mon Sep 17 00:00:00 2001 From: geoffreygarrett Date: Tue, 31 Dec 2024 18:13:45 +0200 Subject: [PATCH 01/15] refactor(primitive): revamp for leptos 0.7 - Add TypedFallbackShow for attribute handling across views - Utilize AnyNodeRef/Attr for robust node references - Fix attribute spreading in conditional branches --- Cargo.toml | 8 +- .../primitives/leptos/primitive/Cargo.toml | 2 + .../leptos/primitive/src/primitive.rs | 145 +++++++++++------- 3 files changed, 100 insertions(+), 55 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 9bcec600..622e4f27 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,9 +22,11 @@ console_log = "1.0.0" console_error_panic_hook = "0.1.7" dioxus = "0.5.6" dioxus-router = "0.5.6" -leptos = "0.6.13" -leptos_dom = "0.6.13" -leptos_router = "0.6.13" +leptos = "0.7.2" +leptos_dom = "0.7.2" +leptos_router = "0.7.2" +leptos-node-ref = { git = "https://github.com/geoffreygarrett/leptos-utils", branch = "update/leptos-0.7" } +leptos-use = "0.15.0" log = "0.4.22" serde = "1.0.198" serde_json = "1.0.116" diff --git a/packages/primitives/leptos/primitive/Cargo.toml b/packages/primitives/leptos/primitive/Cargo.toml index a688555f..b5c2aafe 100644 --- a/packages/primitives/leptos/primitive/Cargo.toml +++ b/packages/primitives/leptos/primitive/Cargo.toml @@ -10,3 +10,5 @@ version.workspace = true [dependencies] leptos.workspace = true +leptos-node-ref.workspace = true +wasm-bindgen = "0.2.99" diff --git a/packages/primitives/leptos/primitive/src/primitive.rs b/packages/primitives/leptos/primitive/src/primitive.rs index f1f75e2b..2cb8a559 100644 --- a/packages/primitives/leptos/primitive/src/primitive.rs +++ b/packages/primitives/leptos/primitive/src/primitive.rs @@ -1,73 +1,114 @@ use leptos::{ + attr::Attribute, + either::Either, ev::Event, - html::{AnyElement, ElementDescriptor}, - *, + html::{ElementType, HtmlElement}, + prelude::*, + tachys::html::{class::IntoClass, node_ref::NodeRefContainer, style::IntoStyle}, }; +use leptos_node_ref::{any_node_ref, AnyNodeRef}; +use wasm_bindgen::JsCast; +/// We need our own show instead of leptos' Show because attribute spreading does not work +/// across AnyView as of 0.7.3, which is required here. #[component] -pub fn Primitive( - #[prop()] element: fn() -> HtmlElement, - #[prop(into, optional)] as_child: MaybeProp, - #[prop(optional)] node_ref: NodeRef, - #[prop(attrs)] attrs: Vec<(&'static str, Attribute)>, - children: ChildrenFn, -) -> impl IntoView { - let attrs = StoredValue::new(attrs); - let children = StoredValue::new(children); +#[allow(non_snake_case)] +pub fn TypedFallbackShow( + children: TypedChildrenFn, + when: W, + fallback: F, +) -> impl IntoView +where + W: Fn() -> bool + Send + Sync + 'static, + F: Fn() -> IV + Send + Sync + 'static, + IV: IntoView + 'static, + C: IntoView + 'static, +{ + let memoized_when = ArcMemo::new(move |_| when()); + let children = children.into_inner(); + + move || match memoized_when.get() { + true => Either::Left(children()), + false => Either::Right(fallback().into_view()), + } +} + +#[component] +pub fn Primitive( + element: fn() -> HtmlElement, + children: TypedChildrenFn, + #[prop(optional, into)] as_child: MaybeProp, + #[prop(optional, into)] node_ref: AnyNodeRef, +) -> impl IntoView +where + E: ElementType + 'static, + C: IntoView + 'static, + View: RenderHtml, + HtmlElement: ElementChild>, + as ElementChild>>::Output: IntoView, + ::Output: JsCast, + AnyNodeRef: NodeRefContainer, +{ + let children = StoredValue::new(children.into_inner()); view! { - - {map_children(children.with_value(|children| children()).as_children(), node_ref, attrs.get_value())} - + {children.with_value(|children| children()).add_any_attr(any_node_ref(node_ref))} + } } -fn map_children( - children: &[View], - node_ref: NodeRef, - attrs: Vec<(&'static str, Attribute)>, -) -> View { - children - .iter() - .map(|child| match child { - View::Element(element) => element - .clone() - .into_html_element() - .node_ref(node_ref) - .attrs(attrs.clone()) - .into_view(), - View::Component(component) => { - map_children(&component.children, node_ref, attrs.clone()) - } - _ => child.into_view(), - }) - .collect_view() +#[component] +pub fn VoidPrimitive( + element: fn() -> HtmlElement, + children: TypedChildrenFn, + #[prop(into, optional)] as_child: MaybeProp, + #[prop(into, optional)] node_ref: AnyNodeRef, +) -> impl IntoView +where + E: ElementType + 'static, + C: IntoView + 'static, + View: RenderHtml, + ::Output: JsCast, + AnyNodeRef: NodeRefContainer, +{ + let children = StoredValue::new(children.into_inner()); + view! { + + {children.with_value(|children| children()).add_any_attr(any_node_ref(node_ref))} + + } } -pub fn compose_callbacks>( - original_event_handler: Option>, - our_event_handler: Option>, - check_for_default_prevented: Option, -) -> impl Fn(E) { - let check_for_default_prevented = check_for_default_prevented.unwrap_or(true); +pub fn compose_callbacks( + original_handler: Option>, + our_handler: Option>, + check_default_prevented: Option, +) -> impl Fn(E) +where + E: Clone + Into + 'static, +{ + let check_default_prevented = check_default_prevented.unwrap_or(true); move |event: E| { - if let Some(original_event_handler) = original_event_handler { - original_event_handler.call(event.clone()); + // Run original handler first, matching TypeScript behavior + if let Some(original) = &original_handler { + original.run(event.clone()); } - if !check_for_default_prevented || !event.clone().into().default_prevented() { - if let Some(our_event_handler) = our_event_handler { - our_event_handler.call(event); + // Only run our handler if default wasn't prevented (when checking is enabled) + if !check_default_prevented || !event.clone().into().default_prevented() { + if let Some(our) = &our_handler { + our.run(event); } } } -} +} \ No newline at end of file From 38a94671160668408becab39563f02d69c0f7afc Mon Sep 17 00:00:00 2001 From: geoffreygarrett Date: Tue, 31 Dec 2024 18:19:23 +0200 Subject: [PATCH 02/15] fix: required import --- packages/primitives/leptos/primitive/Cargo.toml | 1 - packages/primitives/leptos/primitive/src/primitive.rs | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/primitives/leptos/primitive/Cargo.toml b/packages/primitives/leptos/primitive/Cargo.toml index b5c2aafe..e05a77a4 100644 --- a/packages/primitives/leptos/primitive/Cargo.toml +++ b/packages/primitives/leptos/primitive/Cargo.toml @@ -11,4 +11,3 @@ version.workspace = true [dependencies] leptos.workspace = true leptos-node-ref.workspace = true -wasm-bindgen = "0.2.99" diff --git a/packages/primitives/leptos/primitive/src/primitive.rs b/packages/primitives/leptos/primitive/src/primitive.rs index 2cb8a559..5f2556c2 100644 --- a/packages/primitives/leptos/primitive/src/primitive.rs +++ b/packages/primitives/leptos/primitive/src/primitive.rs @@ -4,10 +4,10 @@ use leptos::{ ev::Event, html::{ElementType, HtmlElement}, prelude::*, + wasm_bindgen::JsCast, tachys::html::{class::IntoClass, node_ref::NodeRefContainer, style::IntoStyle}, }; use leptos_node_ref::{any_node_ref, AnyNodeRef}; -use wasm_bindgen::JsCast; /// We need our own show instead of leptos' Show because attribute spreading does not work /// across AnyView as of 0.7.3, which is required here. From 958d944c7d0e9c6589284201255a06853bbb082e Mon Sep 17 00:00:00 2001 From: geoffreygarrett Date: Tue, 31 Dec 2024 18:27:42 +0200 Subject: [PATCH 03/15] fix: doc version of leptos --- packages/primitives/leptos/primitive/src/primitive.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/primitives/leptos/primitive/src/primitive.rs b/packages/primitives/leptos/primitive/src/primitive.rs index 5f2556c2..7ed1e6de 100644 --- a/packages/primitives/leptos/primitive/src/primitive.rs +++ b/packages/primitives/leptos/primitive/src/primitive.rs @@ -10,7 +10,7 @@ use leptos::{ use leptos_node_ref::{any_node_ref, AnyNodeRef}; /// We need our own show instead of leptos' Show because attribute spreading does not work -/// across AnyView as of 0.7.3, which is required here. +/// across AnyView as of 0.7.2, which is required here. #[component] #[allow(non_snake_case)] pub fn TypedFallbackShow( From f85f380d831c43c9ed9159f7dbf9550ce1e6fc2c Mon Sep 17 00:00:00 2001 From: geoffreygarrett Date: Wed, 1 Jan 2025 09:13:06 +0200 Subject: [PATCH 04/15] chore(deps): update label to leptos 0.7 --- packages/primitives/leptos/label/Cargo.toml | 1 + packages/primitives/leptos/label/src/label.rs | 43 +++++++++++++------ 2 files changed, 30 insertions(+), 14 deletions(-) diff --git a/packages/primitives/leptos/label/Cargo.toml b/packages/primitives/leptos/label/Cargo.toml index a5ed04a6..1cea2245 100644 --- a/packages/primitives/leptos/label/Cargo.toml +++ b/packages/primitives/leptos/label/Cargo.toml @@ -12,4 +12,5 @@ version.workspace = true [dependencies] leptos.workspace = true radix-leptos-primitive = { path = "../primitive", version = "0.0.2" } +leptos-node-ref.workspace = true web-sys.workspace = true diff --git a/packages/primitives/leptos/label/src/label.rs b/packages/primitives/leptos/label/src/label.rs index 92c88939..a4096c75 100644 --- a/packages/primitives/leptos/label/src/label.rs +++ b/packages/primitives/leptos/label/src/label.rs @@ -1,38 +1,53 @@ -use leptos::{ev::MouseEvent, html::AnyElement, *}; +use leptos::{ev::MouseEvent, html, prelude::*}; use radix_leptos_primitive::Primitive; +use leptos_node_ref::prelude::*; + +/* ------------------------------------------------------------------------------------------------- + * Label + * -----------------------------------------------------------------------------------------------*/ + +const NAME: &str = "Label"; #[component] +#[allow(non_snake_case)] pub fn Label( + children: TypedChildrenFn, #[prop(into, optional)] as_child: MaybeProp, #[prop(into, optional)] on_mouse_down: Option>, - #[prop(attrs)] attrs: Vec<(&'static str, Attribute)>, - #[prop(optional)] node_ref: NodeRef, - children: ChildrenFn, + #[prop(into, optional)] node_ref: AnyNodeRef, ) -> impl IntoView { view! { 1 { event.prevent_default(); } } node_ref=node_ref - attrs=attrs - > - {children()} - + /> } } + +/* -----------------------------------------------------------------------------------------------*/ + +pub mod primitive { + pub use super::*; + pub use Label as Root; +} From caf5323819a1a3358a5c18af921fdba6279d7ab7 Mon Sep 17 00:00:00 2001 From: geoffreygarrett Date: Wed, 1 Jan 2025 10:09:36 +0200 Subject: [PATCH 05/15] chore(deps): update compose-refs to leptos 0.7 --- .../primitives/leptos/compose-refs/Cargo.toml | 1 + .../leptos/compose-refs/src/compose_refs.rs | 251 +++++++++++++++++- 2 files changed, 243 insertions(+), 9 deletions(-) diff --git a/packages/primitives/leptos/compose-refs/Cargo.toml b/packages/primitives/leptos/compose-refs/Cargo.toml index e980a089..6771c149 100644 --- a/packages/primitives/leptos/compose-refs/Cargo.toml +++ b/packages/primitives/leptos/compose-refs/Cargo.toml @@ -10,3 +10,4 @@ version.workspace = true [dependencies] leptos.workspace = true +leptos-node-ref.workspace = true \ No newline at end of file diff --git a/packages/primitives/leptos/compose-refs/src/compose_refs.rs b/packages/primitives/leptos/compose-refs/src/compose_refs.rs index 2529cdf0..dfbf0ba5 100644 --- a/packages/primitives/leptos/compose-refs/src/compose_refs.rs +++ b/packages/primitives/leptos/compose-refs/src/compose_refs.rs @@ -1,21 +1,254 @@ -use leptos::{html::ElementDescriptor, Effect, NodeRef}; +use leptos::{ + html::{self, ElementType}, + prelude::*, + tachys::html::node_ref::NodeRefContainer, + wasm_bindgen::JsCast, + web_sys::Element, +}; +use leptos_node_ref::prelude::*; +use std::{rc::Rc, iter::IntoIterator}; -fn compose_refs(refs: Vec>) -> NodeRef { - let composed_ref = NodeRef::new(); +/// A trait for composable node references that can be combined, +/// while maintaining static dispatch (tuples) and dynamic dispatch (iterables). +pub trait ComposeRefs { + /// Applies the composition to a given DOM node. + fn compose_with(&self, node: &Element); +} + +// ------------------------------------- +// 1. Static Implementations +// ------------------------------------- + +impl ComposeRefs for AnyNodeRef { + #[inline(always)] + fn compose_with(&self, node: &Element) { + >::load(*self, node); + } +} + +impl ComposeRefs for NodeRef +where + T: ElementType, + T::Output: JsCast, +{ + #[inline(always)] + fn compose_with(&self, node: &Element) { + as NodeRefContainer>::load(*self, node); + } +} + +// NOTE: See macro ahead, replaces these. These are +// left for illustration for now. +// impl ComposeRefs for (A, B) +// where +// A: ComposeRefs, +// B: ComposeRefs, +// { +// #[inline(always)] +// fn compose_with(&self, node: &Element) { +// self.0.compose_with(node); +// self.1.compose_with(node); +// } +// } + +// impl ComposeRefs for (A, B, C) +// where +// A: ComposeRefs, +// B: ComposeRefs, +// C: ComposeRefs, +// { +// #[inline(always)] +// fn compose_with(&self, node: &Element) { +// self.0.compose_with(node); +// self.1.compose_with(node); +// self.2.compose_with(node); +// } +// } + +macro_rules! impl_compose_refs_tuple { + ($($idx:tt $type:ident),+) => { + impl<$($type),+> ComposeRefs for ($($type),+) + where + $($type: ComposeRefs),+ + { + #[inline(always)] + fn compose_with(&self, node: &Element) { + $( + self.$idx.compose_with(node); + )+ + } + } + } +} + +impl_compose_refs_tuple!(0 A, 1 B); +impl_compose_refs_tuple!(0 A, 1 B, 2 C); +impl_compose_refs_tuple!(0 A, 1 B, 2 C, 3 D); +impl_compose_refs_tuple!(0 A, 1 B, 2 C, 3 D, 4 E); +impl_compose_refs_tuple!(0 A, 1 B, 2 C, 3 D, 4 E, 5 F); + +// ------------------------------------- +// 2. Dynamic Implementations +// ------------------------------------- + +/// Implementation for arrays of any size +impl ComposeRefs for [T; N] { + fn compose_with(&self, node: &Element) { + for item in self.iter() { + item.compose_with(node); + } + } +} + +/// Implementation for slice references +impl ComposeRefs for &[T] { + fn compose_with(&self, node: &Element) { + for item in (*self).iter() { + item.compose_with(node); + } + } +} + +/// Implementation for Vec +impl ComposeRefs for Vec { + fn compose_with(&self, node: &Element) { + for item in self.iter() { + item.compose_with(node); + } + } +} + +// ------------------------------------- +// 3. compose_refs + Hook +// ------------------------------------- +/// Combines multiple node references into a single reference that, when set, +/// updates all input references to point to the same DOM node. +/// +/// - **Static**: Tuples (`(ref1, ref2, ...)`)—no heap allocation. +/// - **Dynamic**: Any iterable (`Vec`, slice, array) of references. +/// +/// # Examples +/// ```rust +/// use leptos::{html::Div, html::Button}; +/// use leptos::prelude::NodeRef; +/// use leptos_node_ref::prelude::*; +/// use radix_leptos_compose_refs::compose_refs; +/// +/// // 1) Static composition (tuples): +/// let div_ref = NodeRef::
::new(); +/// let btn_ref = NodeRef::