diff --git a/Cargo.toml b/Cargo.toml index 68450696..8d6fd403 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ members = [ "packages/primitives/leptos/accessible-icon", "packages/primitives/leptos/arrow", "packages/primitives/leptos/aspect-ratio", + "packages/primitives/leptos/compose-refs", "packages/primitives/leptos/direction", "packages/primitives/leptos/id", "packages/primitives/leptos/label", @@ -62,3 +63,4 @@ yew-style = "0.1.4" [patch.crates-io] yew = { git = "https://github.com/RustForWeb/yew.git", branch = "feature/use-composed-ref" } yew-router = { git = "https://github.com/RustForWeb/yew.git", branch = "feature/use-composed-ref" } +leptos-node-ref = { git = "https://github.com/geoffreygarrett/leptos-utils", branch = "feature/any-node-ref" } \ No newline at end of file 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/README.md b/packages/primitives/leptos/compose-refs/README.md index 190ed29c..0bb76a16 100644 --- a/packages/primitives/leptos/compose-refs/README.md +++ b/packages/primitives/leptos/compose-refs/README.md @@ -16,6 +16,6 @@ See [the Rust Radix book](https://radix.rustforweb.org/) for documentation. ## Rust For Web -The Rust Radix project is part of [Rust For Web](https://github.com/RustForWeb). +The Rust Radix project is part of the [Rust For Web](https://github.com/RustForWeb). [Rust For Web](https://github.com/RustForWeb) creates and ports web UI libraries for Rust. All projects are free and open source. diff --git a/packages/primitives/leptos/compose-refs/src/compose_refs.rs b/packages/primitives/leptos/compose-refs/src/compose_refs.rs index 2529cdf0..19a6d9ed 100644 --- a/packages/primitives/leptos/compose-refs/src/compose_refs.rs +++ b/packages/primitives/leptos/compose-refs/src/compose_refs.rs @@ -1,21 +1,255 @@ -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}; -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::