diff --git a/Cargo.toml b/Cargo.toml
index ce06992c..16f109e0 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,43 @@ 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" }
diff --git a/packages/primitives/leptos/primitive/Cargo.toml b/packages/primitives/leptos/primitive/Cargo.toml
new file mode 100644
index 00000000..5a387636
--- /dev/null
+++ b/packages/primitives/leptos/primitive/Cargo.toml
@@ -0,0 +1,19 @@
+[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
+
+[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, optional = 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 @@
+
+
+
+
+
+
+
+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..5308e7a2
--- /dev/null
+++ b/packages/primitives/leptos/primitive/src/primitive.rs
@@ -0,0 +1,159 @@
+use leptos::{
+ ev::Event,
+ html::{ElementType, HtmlElement},
+ prelude::*,
+ wasm_bindgen::JsCast,
+ 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(
+ 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))}
+
+ }
+}
+
+#[cfg(feature = "any-view-any-attr")]
+#[component]
+#[allow(non_snake_case)]
+pub fn Primitive(
+ 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(
+ 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))}
+
+ }
+}
+
+#[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
+ * -----------------------------------------------------------------------------------------------*/
+
+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);
+ }
+ }
+ }
+}