From 397503dc8a2cbe755e13e62accb29ee22150e81c Mon Sep 17 00:00:00 2001 From: geoffreygarrett Date: Wed, 8 Jan 2025 13:15:09 +0200 Subject: [PATCH 1/7] Update Workspace for Leptos 0.7 --- Cargo.toml | 109 ++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 86 insertions(+), 23 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ce06992c..aceb6cde 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,34 +1,56 @@ [workspace] -# Temporarily disabled to upgrade individual packages to Leptos 0.7. -# members = [ -# "book-examples/*/*", -# "packages/colors", -# "packages/icons/*", -# "packages/primitives/*/*", -# "packages/themes/*", -# "scripts", -# "stories/*", -# ] +# Temporarily disabled subcrates to be upgraded individually to Leptos 0.7. +# Once a crate is ready, uncomment it here. members = [ "book-examples/*/*", "packages/colors", - "packages/icons/*", - "packages/primitives/leptos/accessible-icon", - "packages/primitives/leptos/arrow", - "packages/primitives/leptos/aspect-ratio", - "packages/primitives/leptos/direction", - "packages/primitives/leptos/id", - "packages/primitives/leptos/label", - "packages/primitives/leptos/use-controllable-state", - "packages/primitives/leptos/use-escape-keydown", - "packages/primitives/leptos/use-previous", - "packages/primitives/leptos/use-size", - "packages/primitives/leptos/visually-hidden", + "packages/icons/dioxus", + "packages/icons/yew", + + # -- Leptos Primitives (commented until they're upgraded) -- + # "packages/primitives/leptos/accessible-icon", + # "packages/primitives/leptos/arrow", + # "packages/primitives/leptos/aspect-ratio", + # "packages/primitives/leptos/avatar", + # "packages/primitives/leptos/checkbox", + # "packages/primitives/leptos/collection", + # "packages/primitives/leptos/compose-refs", + # "packages/primitives/leptos/direction", + # "packages/primitives/leptos/dismissable-layer", + # "packages/primitives/leptos/dropdown-menu", + # "packages/primitives/leptos/focus-guards", + # "packages/primitives/leptos/focus-scope", + # "packages/primitives/leptos/id", + # "packages/primitives/leptos/label", + # "packages/primitives/leptos/menu", + # "packages/primitives/leptos/popover", + # "packages/primitives/leptos/popper", + # "packages/primitives/leptos/portal", + # "packages/primitives/leptos/presence", + # "packages/primitives/leptos/primitive", + # "packages/primitives/leptos/progress", + # "packages/primitives/leptos/roving-focus", + # "packages/primitives/leptos/select", + # "packages/primitives/leptos/separator", + # "packages/primitives/leptos/slot", + # "packages/primitives/leptos/switch", + # "packages/primitives/leptos/tabs", + # "packages/primitives/leptos/toggle", + # "packages/primitives/leptos/use-controllable-state", + # "packages/primitives/leptos/use-escape-keydown", + # "packages/primitives/leptos/use-previous", + # "packages/primitives/leptos/use-size", + # "packages/primitives/leptos/visually-hidden", + + # -- Yew Primitives -- "packages/primitives/yew/*", + + # -- Themes, Scripts, and Stories -- "packages/themes/yew", "scripts", "stories/*", ] + resolver = "2" [workspace.package] @@ -39,14 +61,17 @@ repository = "https://github.com/RustForWeb/radix" version = "0.0.2" [workspace.dependencies] -console_log = "1.0.0" console_error_panic_hook = "0.1.7" +console_log = "1.0.0" dioxus = "0.6.1" leptos = "0.7.2" leptos_dom = "0.7.2" leptos_router = "0.7.2" leptos-node-ref = "0.0.3" +leptos-maybe-callback = "0.0.3" leptos-style = "0.0.3" +leptos-typed-fallback-show = "0.0.3" +leptos-use = "0.15.2" log = "0.4.22" send_wrapper = "0.6.0" serde = "1.0.198" @@ -58,6 +83,44 @@ yew-router = "0.18.0" yew-struct-component = "0.1.4" yew-style = "0.1.4" +# Subcrate packages (paths remain the same; you can comment out any subcrate not yet upgraded). +# We centralize shared dependencies in [workspace.dependencies] for a single source of truth, +# reducing duplication, preventing version drift, and keeping the Cargo.lock consistent. +#radix-leptos-arrow.path = "./packages/primitives/leptos/arrow" +#radix-leptos-aspect-ratio.path = "./packages/primitives/leptos/aspect-ratio" +#radix-leptos-accessible-icon.path = "./packages/primitives/leptos/accessible-icon" +#radix-leptos-avatar.path = "./packages/primitives/leptos/avatar" +#radix-leptos-checkbox.path = "./packages/primitives/leptos/checkbox" +#radix-leptos-collection.path = "./packages/primitives/leptos/collection" +#radix-leptos-compose-refs.path = "./packages/primitives/leptos/compose-refs" +#radix-leptos-direction.path = "./packages/primitives/leptos/direction" +#radix-leptos-dismissable-layer.path = "./packages/primitives/leptos/dismissable-layer" +#radix-leptos-dropdown-menu.path = "./packages/primitives/leptos/dropdown-menu" +#radix-leptos-focus-guards.path = "./packages/primitives/leptos/focus-guards" +#radix-leptos-focus-scope.path = "./packages/primitives/leptos/focus-scope" +#radix-leptos-id.path = "./packages/primitives/leptos/id" +#radix-leptos-label.path = "./packages/primitives/leptos/label" +#radix-leptos-menu.path = "./packages/primitives/leptos/menu" +#radix-leptos-popper.path = "./packages/primitives/leptos/popper" +#radix-leptos-portal.path = "./packages/primitives/leptos/portal" +#radix-leptos-presence.path = "./packages/primitives/leptos/presence" +#radix-leptos-primitive.path = "./packages/primitives/leptos/primitive" +#radix-leptos-progress.path = "./packages/primitives/leptos/progress" +#radix-leptos-roving-focus.path = "./packages/primitives/leptos/roving-focus" +#radix-leptos-select.path = "./packages/primitives/leptos/select" +#radix-leptos-separator.path = "./packages/primitives/leptos/separator" +#radix-leptos-slot.path = "./packages/primitives/leptos/slot" +#radix-leptos-switch.path = "./packages/primitives/leptos/switch" +#radix-leptos-tabs.path = "./packages/primitives/leptos/tabs" +#radix-leptos-toggle.path = "./packages/primitives/leptos/toggle" +#radix-leptos-use-controllable-state.path = "./packages/primitives/leptos/use-controllable-state" +#radix-leptos-use-escape-keydown.path = "./packages/primitives/leptos/use-escape-keydown" +#radix-leptos-use-previous.path = "./packages/primitives/leptos/use-previous" +#radix-leptos-use-size.path = "./packages/primitives/leptos/use-size" +#radix-leptos-visually-hidden.path = "./packages/primitives/leptos/visually-hidden" + [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" } +radix-leptos-primitive = { git = "https://github.com/geoffreygarrett/radix", branch = "update/leptos-0.7-primitive" } From 70159621139cc0c0150299b518aafaa4cb819d50 Mon Sep 17 00:00:00 2001 From: geoffreygarrett Date: Wed, 8 Jan 2025 14:16:57 +0200 Subject: [PATCH 2/7] Update Workspace for Leptos 0.7 --- Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index aceb6cde..ab7670fe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -123,4 +123,3 @@ yew-style = "0.1.4" 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" } -radix-leptos-primitive = { git = "https://github.com/geoffreygarrett/radix", branch = "update/leptos-0.7-primitive" } From e896ace2c06b0f85a58bd61957d1d352f6c9695b Mon Sep 17 00:00:00 2001 From: geoffreygarrett Date: Mon, 6 Jan 2025 19:19:46 +0200 Subject: [PATCH 3/7] Update Primitive to Leptos 0.7 --- .../primitives/leptos/primitive/Cargo.toml | 14 +++ .../primitives/leptos/primitive/README.md | 98 ++++++++++++++++ .../primitives/leptos/primitive/src/lib.rs | 9 ++ .../leptos/primitive/src/primitive.rs | 109 ++++++++++++++++++ 4 files changed, 230 insertions(+) create mode 100644 packages/primitives/leptos/primitive/Cargo.toml create mode 100644 packages/primitives/leptos/primitive/README.md create mode 100644 packages/primitives/leptos/primitive/src/lib.rs create mode 100644 packages/primitives/leptos/primitive/src/primitive.rs diff --git a/packages/primitives/leptos/primitive/Cargo.toml b/packages/primitives/leptos/primitive/Cargo.toml new file mode 100644 index 00000000..fecb0b9d --- /dev/null +++ b/packages/primitives/leptos/primitive/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "radix-leptos-primitive" +description = "Leptos port of Radix Primitive." + +authors.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true +version.workspace = true + +[dependencies] +leptos.workspace = true +leptos-node-ref.workspace = true +leptos-typed-fallback-show.workspace = true \ No newline at end of file diff --git a/packages/primitives/leptos/primitive/README.md b/packages/primitives/leptos/primitive/README.md new file mode 100644 index 00000000..296e16dc --- /dev/null +++ b/packages/primitives/leptos/primitive/README.md @@ -0,0 +1,98 @@ + +

+ + Rust Radix Logo + +

+ +

radix-leptos-primitive

+ +This is an internal utility, not intended for public usage. + +[Rust Radix](https://github.com/RustForWeb/radix) is a Rust port of [Radix](https://www.radix-ui.com/primitives). + +## Overview + +```rust +use leptos::*; +use leptos_node_ref::AnyNodeRef; +use leptos_typed_fallback_show::TypedFallbackShow; + +/// A generic Primitive component. Renders `element()` by default, or its +/// children directly if `as_child` is `true`. We rely on `TypedChildrenFn` +/// so that attributes can pass through at runtime—critical in Leptos v0.7 +/// because `Children`-based types block such passthrough. +#[component] +#[allow(non_snake_case)] +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, +{ + let children = StoredValue::new(children.into_inner()); + view! { + + {children.with_value(|c| c()) + .add_any_attr(leptos_node_ref::any_node_ref(node_ref))} + + } +} + +/// Same idea, but for elements that do not take children (e.g. `img`, `input`). +#[component] +#[allow(non_snake_case)] +pub fn VoidPrimitive( + element: fn() -> HtmlElement, + children: TypedChildrenFn, + #[prop(optional, into)] as_child: MaybeProp, + #[prop(optional, into)] node_ref: AnyNodeRef, +) -> impl IntoView +where + E: ElementType + 'static, +{ + let children = StoredValue::new(children.into_inner()); + view! { + + {children.with_value(|c| c()) + .add_any_attr(leptos_node_ref::any_node_ref(node_ref))} + + } +} + +// (Compose callbacks is an internal piece from Radix Core; omitted for brevity.) +``` + +## Notes + +- **Why `TypedChildrenFn`?**: Leptos attribute passthrough only works if a component doesn't rely on `AnyView` or `Children`. Using typed children ensures classes, events, etc. from the parent can flow to the rendered DOM node. +- **`as_child`**: Mimics `asChild` in Radix’s React version, but we skip an explicit ``: Leptos’s approach to typed fallback rendering covers “slot-like” logic. +- **Class Handling**: Static classes from a parent can overwrite child-defined classes. No built-in merging exists. +- **Attribute System Limitations**: Leptos limits you to 26 dynamic attributes. Past that, nest components or try a custom approach. +- **Parity with React**: In React, `...props` merges everything automatically. In Leptos, we rely on typed props/attributes and can intercept unknown ones with `AttributeInterceptor`. + +## Documentation + +See [the Rust Radix book](https://radix.rustforweb.org/) for documentation. + +## Rust For Web + +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/primitive/src/lib.rs b/packages/primitives/leptos/primitive/src/lib.rs new file mode 100644 index 00000000..304fec6d --- /dev/null +++ b/packages/primitives/leptos/primitive/src/lib.rs @@ -0,0 +1,9 @@ +//! Leptos port of [Radix Primitive](https://www.radix-ui.com/primitives). +//! +//! This is an internal utility, not intended for public usage. + +//! See [`@radix-ui/react-primitive`](https://www.npmjs.com/package/@radix-ui/react-primitive) for the original package. + +mod primitive; + +pub use primitive::*; diff --git a/packages/primitives/leptos/primitive/src/primitive.rs b/packages/primitives/leptos/primitive/src/primitive.rs new file mode 100644 index 00000000..7d4c94ab --- /dev/null +++ b/packages/primitives/leptos/primitive/src/primitive.rs @@ -0,0 +1,109 @@ +use leptos::{ + attr::Attribute, + 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 leptos_typed_fallback_show::TypedFallbackShow; + +/* ------------------------------------------------------------------------------------------------- + * Primitive + * -----------------------------------------------------------------------------------------------*/ + +#[component] +#[allow(non_snake_case)] +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! { + + {children.with_value(|children| children()).add_any_attr(any_node_ref(node_ref))} + + } +} + +#[component] +#[allow(non_snake_case)] +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))} + + } +} + +/* ------------------------------------------------------------------------------------------------- + * Utils + * -----------------------------------------------------------------------------------------------*/ + +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| { + // Run original handler first, matching TypeScript behavior + if let Some(original) = &original_handler { + original.run(event.clone()); + } + + // 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); + } + } + } +} + +/* ------------------------------------------------------------------------------------------------- + * Primitive re-exports + * -----------------------------------------------------------------------------------------------*/ + +pub mod primitive { + pub use super::*; + pub use Primitive as Root; +} \ No newline at end of file From 2ee135bf5ed6fc773a14fa68ec5af2a40ea2a37a Mon Sep 17 00:00:00 2001 From: geoffreygarrett Date: Wed, 8 Jan 2025 14:13:52 +0200 Subject: [PATCH 4/7] Update Primitive for Leptos 0.7 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index ab7670fe..a16ac6ef 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -104,7 +104,7 @@ yew-style = "0.1.4" #radix-leptos-popper.path = "./packages/primitives/leptos/popper" #radix-leptos-portal.path = "./packages/primitives/leptos/portal" #radix-leptos-presence.path = "./packages/primitives/leptos/presence" -#radix-leptos-primitive.path = "./packages/primitives/leptos/primitive" +radix-leptos-primitive.path = "./packages/primitives/leptos/primitive" #radix-leptos-progress.path = "./packages/primitives/leptos/progress" #radix-leptos-roving-focus.path = "./packages/primitives/leptos/roving-focus" #radix-leptos-select.path = "./packages/primitives/leptos/select" From 08e08acfb65091751421c61b46fbe721a36929bb Mon Sep 17 00:00:00 2001 From: geoffreygarrett Date: Wed, 8 Jan 2025 14:24:49 +0200 Subject: [PATCH 5/7] Update Primitive for Leptos 0.7 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index a16ac6ef..16f109e0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,7 +27,7 @@ members = [ # "packages/primitives/leptos/popper", # "packages/primitives/leptos/portal", # "packages/primitives/leptos/presence", - # "packages/primitives/leptos/primitive", + "packages/primitives/leptos/primitive", # "packages/primitives/leptos/progress", # "packages/primitives/leptos/roving-focus", # "packages/primitives/leptos/select", From 98abf14db7ce26e07e4e4621b9bc9d0acacc5189 Mon Sep 17 00:00:00 2001 From: geoffreygarrett Date: Wed, 8 Jan 2025 14:26:14 +0200 Subject: [PATCH 6/7] Update Primitive for Leptos 0.7 --- .../primitives/leptos/primitive/src/primitive.rs | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/packages/primitives/leptos/primitive/src/primitive.rs b/packages/primitives/leptos/primitive/src/primitive.rs index 7d4c94ab..fd39d291 100644 --- a/packages/primitives/leptos/primitive/src/primitive.rs +++ b/packages/primitives/leptos/primitive/src/primitive.rs @@ -1,10 +1,9 @@ use leptos::{ - attr::Attribute, ev::Event, html::{ElementType, HtmlElement}, prelude::*, wasm_bindgen::JsCast, - tachys::html::{class::IntoClass, node_ref::NodeRefContainer, style::IntoStyle}, + tachys::html::{node_ref::NodeRefContainer}, }; use leptos_node_ref::{any_node_ref, AnyNodeRef}; use leptos_typed_fallback_show::TypedFallbackShow; @@ -98,12 +97,3 @@ where } } } - -/* ------------------------------------------------------------------------------------------------- - * Primitive re-exports - * -----------------------------------------------------------------------------------------------*/ - -pub mod primitive { - pub use super::*; - pub use Primitive as Root; -} \ No newline at end of file From 1d149b809b36220c325e099367ed2a833e175584 Mon Sep 17 00:00:00 2001 From: geoffreygarrett Date: Wed, 8 Jan 2025 22:02:59 +0200 Subject: [PATCH 7/7] Update Primitive for AddAnyAttr on AnyView Closes #436 --- .../primitives/leptos/primitive/Cargo.toml | 7 ++- .../leptos/primitive/src/primitive.rs | 62 ++++++++++++++++++- 2 files changed, 67 insertions(+), 2 deletions(-) diff --git a/packages/primitives/leptos/primitive/Cargo.toml b/packages/primitives/leptos/primitive/Cargo.toml index fecb0b9d..5a387636 100644 --- a/packages/primitives/leptos/primitive/Cargo.toml +++ b/packages/primitives/leptos/primitive/Cargo.toml @@ -8,7 +8,12 @@ license.workspace = true repository.workspace = true version.workspace = true +[features] +default = ["no-any-view-any-attr"] +any-view-any-attr = [] +no-any-view-any-attr = ["dep:leptos-typed-fallback-show"] + [dependencies] leptos.workspace = true leptos-node-ref.workspace = true -leptos-typed-fallback-show.workspace = true \ No newline at end of file +leptos-typed-fallback-show = { workspace = true, optional = true } \ No newline at end of file diff --git a/packages/primitives/leptos/primitive/src/primitive.rs b/packages/primitives/leptos/primitive/src/primitive.rs index fd39d291..5308e7a2 100644 --- a/packages/primitives/leptos/primitive/src/primitive.rs +++ b/packages/primitives/leptos/primitive/src/primitive.rs @@ -6,12 +6,18 @@ use leptos::{ tachys::html::{node_ref::NodeRefContainer}, }; use leptos_node_ref::{any_node_ref, AnyNodeRef}; + +#[cfg(all(feature = "any-view-any-attr", feature = "no-any-view-any-attr"))] +compile_error!("Features 'any-view-any-attr' and 'no-any-view-any-attr' cannot be enabled at the same time"); + +#[cfg(not(feature = "any-view-any-attr"))] use leptos_typed_fallback_show::TypedFallbackShow; /* ------------------------------------------------------------------------------------------------- * Primitive * -----------------------------------------------------------------------------------------------*/ +#[cfg(not(feature = "any-view-any-attr"))] #[component] #[allow(non_snake_case)] pub fn Primitive( @@ -30,7 +36,6 @@ where AnyNodeRef: NodeRefContainer, { let children = StoredValue::new(children.into_inner()); - view! { ( + element: fn() -> HtmlElement, + children: ChildrenFn, + #[prop(optional, into)] as_child: MaybeProp, + #[prop(optional, into)] node_ref: AnyNodeRef, +) -> impl IntoView +where + E: ElementType + 'static, + HtmlElement: ElementChild, + as ElementChild>::Output: IntoView, + ::Output: JsCast, + AnyNodeRef: NodeRefContainer, +{ + let children = StoredValue::new(children); + view! { + + {children.with_value(|children| children()).add_any_attr(any_node_ref(node_ref))} + + } +} + +#[cfg(not(feature = "any-view-any-attr"))] #[component] #[allow(non_snake_case)] pub fn VoidPrimitive( @@ -69,6 +104,31 @@ where } } +#[cfg(feature = "any-view-any-attr")] +#[component] +#[allow(non_snake_case)] +pub fn VoidPrimitive( + element: fn() -> HtmlElement, + children: ChildrenFn, + #[prop(into, optional)] as_child: MaybeProp, + #[prop(into, optional)] node_ref: AnyNodeRef, +) -> impl IntoView +where + E: ElementType + 'static, + ::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))} + + } +} + /* ------------------------------------------------------------------------------------------------- * Utils * -----------------------------------------------------------------------------------------------*/