diff --git a/Cargo.lock b/Cargo.lock index 7fda8c7b..71c2c13a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1071,7 +1071,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -1112,9 +1112,9 @@ checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "floating-ui-core" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17ddaac8c05713a3deaee030a40c388807b32ca3e710223c9a21a0a2951f7892" +checksum = "33144e69ad91d34cc8b957bdf03ee3f1e46c89e3b1a25e6a6eff473a2d547fe6" dependencies = [ "dyn_derive", "dyn_std", @@ -1125,20 +1125,35 @@ dependencies = [ [[package]] name = "floating-ui-dom" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "837c61fb0a929dd5fcad207268329e42f1cdf17ad465588a72642ffead3d2657" +checksum = "ba1dda137b7efa08d33f0beba3fe2ccbf538570e43d511bf2d49b5eb1ccb7fc9" dependencies = [ "floating-ui-core", "floating-ui-utils", "web-sys", ] +[[package]] +name = "floating-ui-leptos" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fed4535c2cd9560d4e9e2825d0e03491cad257d63b0a6e21f59a62152d9e4585" +dependencies = [ + "dyn_derive", + "dyn_std", + "floating-ui-dom", + "leptos", + "leptos-node-ref", + "send_wrapper", + "web-sys", +] + [[package]] name = "floating-ui-utils" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a15f62b552ac1f916247a8322aef0f4fd29d1c3ec8af84c62ce24579114d7c46" +checksum = "a193a216061d7701bd01885d868020d8f7ba0dff21d4f2f4cf4308031fbb9e7a" dependencies = [ "cfg-if", "dyn_derive", @@ -1149,9 +1164,9 @@ dependencies = [ [[package]] name = "floating-ui-yew" -version = "0.3.1" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "641f592ec86f0c393dbf5920eb84d49167c86335a5782e9cdfa8db62973c7bdb" +checksum = "aca22fbe7581604ea23434e5424c45baa22f924dc79434001f0d9718602ab240" dependencies = [ "floating-ui-dom", "web-sys", @@ -3116,6 +3131,7 @@ name = "radix-leptos-compose-refs" version = "0.0.2" dependencies = [ "leptos", + "leptos-node-ref", ] [[package]] @@ -3158,6 +3174,24 @@ dependencies = [ "web-sys", ] +[[package]] +name = "radix-leptos-popper" +version = "0.0.2" +dependencies = [ + "floating-ui-leptos", + "leptos", + "leptos-maybe-callback", + "leptos-node-ref", + "radix-leptos-arrow", + "radix-leptos-compose-refs", + "radix-leptos-primitive", + "radix-leptos-use-size", + "send_wrapper", + "serde", + "serde_json", + "web-sys", +] + [[package]] name = "radix-leptos-portal" version = "0.0.2" @@ -3203,9 +3237,11 @@ dependencies = [ "radix-leptos-arrow", "radix-leptos-aspect-ratio", "radix-leptos-label", + "radix-leptos-popper", "radix-leptos-portal", "radix-leptos-separator", "radix-leptos-visually-hidden", + "send_wrapper", "tailwind_fuse", "web-sys", ] @@ -3834,7 +3870,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -4468,7 +4504,7 @@ dependencies = [ "getrandom 0.3.2", "once_cell", "rustix", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -5091,7 +5127,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 9bff9b3b..fe426115 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,6 +21,7 @@ members = [ "packages/primitives/leptos/focus-guards", "packages/primitives/leptos/id", "packages/primitives/leptos/label", + "packages/primitives/leptos/popper", "packages/primitives/leptos/portal", "packages/primitives/leptos/primitive", "packages/primitives/leptos/separator", @@ -68,7 +69,7 @@ radix-leptos-focus-guards = { path = "./packages/primitives/leptos/focus-guards" radix-leptos-icons = { path = "./packages/icons/leptos", version = "0.0.2" } radix-leptos-id = { path = "./packages/primitives/leptos/id", version = "0.0.2" } radix-leptos-label = { path = "./packages/primitives/leptos/label", version = "0.0.2" } -# radix-leptos-popper = { path = "./packages/primitives/leptos/popper", version = "0.0.2" } +radix-leptos-popper = { path = "./packages/primitives/leptos/popper", version = "0.0.2" } radix-leptos-portal = { path = "./packages/primitives/leptos/portal", version = "0.0.2" } # radix-leptos-presence = { path = "./packages/primitives/leptos/presence", version = "0.0.2" } radix-leptos-primitive = { path = "./packages/primitives/leptos/primitive", version = "0.0.2" } diff --git a/packages/primitives/leptos/compose-refs/Cargo.toml b/packages/primitives/leptos/compose-refs/Cargo.toml index e980a089..f7eacd43 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 diff --git a/packages/primitives/leptos/compose-refs/src/compose_refs.rs b/packages/primitives/leptos/compose-refs/src/compose_refs.rs index e9437edf..b2af7f92 100644 --- a/packages/primitives/leptos/compose-refs/src/compose_refs.rs +++ b/packages/primitives/leptos/compose-refs/src/compose_refs.rs @@ -1,15 +1,14 @@ -use leptos::web_sys::Element; -use leptos::{html::ElementType, prelude::*, tachys::html::node_ref::NodeRefContainer}; +use leptos::{html::Div, prelude::*, tachys::html::node_ref::NodeRefContainer}; +use leptos_node_ref::AnyNodeRef; -fn compose_refs + Clone + 'static>( - refs: Vec>, -) -> NodeRef { - let composed_ref = NodeRef::new(); +fn compose_refs(refs: Vec) -> AnyNodeRef { + let composed_ref = AnyNodeRef::new(); Effect::new(move |_| { if let Some(node) = composed_ref.get() { for r#ref in &refs { - r#ref.load(&node); + NodeRefContainer::
::load(*r#ref, &node); + // r#ref.load_any(&node); } } }); @@ -17,8 +16,6 @@ fn compose_refs + Clone + 'static>( composed_ref } -pub fn use_composed_refs + Clone + 'static>( - refs: Vec>, -) -> NodeRef { +pub fn use_composed_refs(refs: Vec) -> AnyNodeRef { compose_refs(refs) } diff --git a/packages/primitives/leptos/popper/Cargo.toml b/packages/primitives/leptos/popper/Cargo.toml index d81cb797..2133f6f8 100644 --- a/packages/primitives/leptos/popper/Cargo.toml +++ b/packages/primitives/leptos/popper/Cargo.toml @@ -9,11 +9,15 @@ repository.workspace = true version.workspace = true [dependencies] -floating-ui-leptos = "0.3.0" +floating-ui-leptos = "0.4.0" leptos.workspace = true +leptos-maybe-callback.workspace = true +leptos-node-ref.workspace = true radix-leptos-arrow.workspace = true radix-leptos-compose-refs.workspace = true +radix-leptos-primitive.workspace = true radix-leptos-use-size.workspace = true +send_wrapper.workspace = true serde.workspace = true serde_json.workspace = true web-sys.workspace = true diff --git a/packages/primitives/leptos/popper/src/popper.rs b/packages/primitives/leptos/popper/src/popper.rs index e4a5b7c3..87896f6f 100644 --- a/packages/primitives/leptos/popper/src/popper.rs +++ b/packages/primitives/leptos/popper/src/popper.rs @@ -1,18 +1,18 @@ use floating_ui_leptos::{ - use_floating, Alignment, ApplyState, Arrow, ArrowData, ArrowOptions, AutoUpdateOptions, - Boundary, DetectOverflowOptions, Flip, FlipOptions, Hide, HideData, HideOptions, HideStrategy, - IntoReference, LimitShift, LimitShiftOptions, Middleware, MiddlewareReturn, MiddlewareState, - MiddlewareVec, Offset, OffsetOptions, OffsetOptionsValues, Padding, Placement, Shift, - ShiftOptions, Side, Size, SizeOptions, Strategy, UseFloatingOptions, UseFloatingReturn, - ARROW_NAME, HIDE_NAME, -}; -use leptos::{ - html::{AnyElement, Div}, - *, + ARROW_NAME, Alignment, ApplyState, Arrow, ArrowData, ArrowOptions, AutoUpdateOptions, Boundary, + DetectOverflowOptions, Flip, FlipOptions, HIDE_NAME, Hide, HideData, HideOptions, HideStrategy, + LimitShift, LimitShiftOptions, Middleware, MiddlewareReturn, MiddlewareState, MiddlewareVec, + Offset, OffsetOptions, OffsetOptionsValues, Padding, Placement, Shift, ShiftOptions, Side, + Size, SizeOptions, Strategy, UseFloatingOptions, UseFloatingReturn, use_floating, }; +use leptos::{attribute_interceptor::AttributeInterceptor, context::Provider, html, prelude::*}; +use leptos_maybe_callback::MaybeCallback; +use leptos_node_ref::AnyNodeRef; use radix_leptos_arrow::Arrow as ArrowPrimitive; use radix_leptos_compose_refs::use_composed_refs; +use radix_leptos_primitive::Primitive; use radix_leptos_use_size::use_size; +use send_wrapper::SendWrapper; use serde::{Deserialize, Serialize}; use web_sys::wasm_bindgen::JsCast; @@ -56,12 +56,12 @@ pub enum UpdatePositionStrategy { #[derive(Clone)] struct PopperContextValue { - pub anchor_ref: NodeRef, + pub anchor_ref: AnyNodeRef, } #[component] pub fn Popper(children: ChildrenFn) -> impl IntoView { - let anchor_ref: NodeRef = NodeRef::new(); + let anchor_ref = AnyNodeRef::new(); let context_value = PopperContextValue { anchor_ref }; @@ -75,8 +75,7 @@ pub fn Popper(children: ChildrenFn) -> impl IntoView { #[component] pub fn PopperAnchor( #[prop(into, optional)] as_child: MaybeProp, - #[prop(optional)] node_ref: NodeRef, - #[prop(attrs)] attrs: Vec<(&'static str, Attribute)>, + #[prop(optional)] node_ref: AnyNodeRef, children: ChildrenFn, ) -> impl IntoView { let context: PopperContextValue = expect_context(); @@ -87,7 +86,6 @@ pub fn PopperAnchor( element=html::div as_child=as_child node_ref=composed_refs - attrs=attrs > {children()} @@ -97,7 +95,7 @@ pub fn PopperAnchor( #[derive(Clone)] struct PopperContentContextValue { pub placed_side: Signal, - pub arrow_ref: NodeRef, + pub arrow_ref: AnyNodeRef, pub arrow_x: Signal>, pub arrow_y: Signal>, pub should_hide_arrow: Signal, @@ -105,45 +103,34 @@ struct PopperContentContextValue { #[component] pub fn PopperContent( - #[prop(into, optional)] side: MaybeProp, - #[prop(into, optional)] side_offset: MaybeProp, - #[prop(into, optional)] align: MaybeProp, - #[prop(into, optional)] align_offset: MaybeProp, - #[prop(into, optional)] arrow_padding: MaybeProp, - #[prop(into, optional)] avoid_collisions: MaybeProp, - #[prop(into, optional)] collision_boundary: MaybeProp>, - #[prop(into, optional)] collision_padding: MaybeProp, - #[prop(into, optional)] sticky: MaybeProp, - #[prop(into, optional)] hide_when_detached: MaybeProp, - #[prop(into, optional)] update_position_strategy: MaybeProp, - #[prop(into, optional)] on_placed: Option>, + #[prop(into, optional, default = Side::Bottom.into())] side: Signal, + #[prop(into, optional, default = 0.0.into())] side_offset: Signal, + #[prop(into, optional, default = Align::Center.into())] align: Signal, + #[prop(into, optional, default = 0.0.into())] align_offset: Signal, + #[prop(into, optional, default = 0.0.into())] arrow_padding: Signal, + #[prop(into, optional, default = true.into())] avoid_collisions: Signal, + #[prop(into, optional, default = SendWrapper::new(vec![]).into())] collision_boundary: Signal< + SendWrapper>, + >, + #[prop(into, optional, default = Padding::All(0.0).into())] collision_padding: Signal, + #[prop(into, optional, default = Sticky::Partial.into())] sticky: Signal, + #[prop(into, optional, default = false.into())] hide_when_detached: Signal, + #[prop(into, optional, default = UpdatePositionStrategy::Optimized.into())] + update_position_strategy: Signal, + #[prop(into, optional)] on_placed: MaybeCallback<()>, + #[prop(into, optional)] dir: MaybeProp, #[prop(into, optional)] as_child: MaybeProp, - #[prop(optional)] node_ref: NodeRef, - #[prop(attrs)] attrs: Vec<(&'static str, Attribute)>, + #[prop(optional)] node_ref: AnyNodeRef, children: ChildrenFn, ) -> impl IntoView { - let side = move || side.get().unwrap_or(Side::Bottom); - let side_offset = move || side_offset.get().unwrap_or(0.0); - let align = move || align.get().unwrap_or(Align::Center); - let align_offset = move || align_offset.get().unwrap_or(0.0); - let arrow_padding = move || arrow_padding.get().unwrap_or(0.0); - let avoid_collisions = move || avoid_collisions.get().unwrap_or(true); - let collision_boundary = move || collision_boundary.get().unwrap_or_default(); - let collision_padding = move || collision_padding.get().unwrap_or(Padding::All(0.0)); - let sticky = move || sticky.get().unwrap_or(Sticky::Partial); - let hide_when_detached = move || hide_when_detached.get().unwrap_or(false); - let update_position_strategy = move || { - update_position_strategy - .get() - .unwrap_or(UpdatePositionStrategy::Optimized) - }; + let children = StoredValue::new(children); let context: PopperContextValue = expect_context(); - let content_ref: NodeRef = NodeRef::new(); + let content_ref = AnyNodeRef::new(); let composed_refs = use_composed_refs(vec![node_ref, content_ref]); - let arrow_ref: NodeRef = NodeRef::new(); + let arrow_ref = AnyNodeRef::new(); let arrow_size = use_size(arrow_ref); let arrow_width = move || { arrow_size @@ -158,9 +145,10 @@ pub fn PopperContent( .unwrap_or(0.0) }; - let desired_placement = Signal::derive(move || Placement::from((side(), align().alignment()))); + let desired_placement = + Signal::derive(move || Placement::from((side.get(), align.get().alignment()))); - let floating_ref: NodeRef
= NodeRef::new(); + let floating_ref = AnyNodeRef::new(); let UseFloatingReturn { floating_styles, @@ -169,35 +157,36 @@ pub fn PopperContent( middleware_data, .. } = use_floating( - context.anchor_ref.into_reference(), + context.anchor_ref, floating_ref, UseFloatingOptions::default() .strategy(Strategy::Fixed.into()) .placement(desired_placement.into()) - .while_elements_mounted_auto_update_with_options(MaybeSignal::derive(move || { - AutoUpdateOptions::default() - .animation_frame(update_position_strategy() == UpdatePositionStrategy::Always) + .while_elements_mounted_auto_update_with_options(Signal::derive(move || { + AutoUpdateOptions::default().animation_frame( + update_position_strategy.get() == UpdatePositionStrategy::Always, + ) })) .middleware(MaybeProp::derive(move || { let detect_overflow_options = DetectOverflowOptions::default() - .padding(collision_padding()) - .boundary(Boundary::Elements(collision_boundary())) - .alt_boundary(!collision_boundary().is_empty()); + .padding(collision_padding.get()) + .boundary(Boundary::Elements((*collision_boundary.get()).clone())) + .alt_boundary(!collision_boundary.get().is_empty()); let mut middleware: MiddlewareVec = vec![Box::new(Offset::new(OffsetOptions::Values( OffsetOptionsValues::default() - .main_axis(side_offset() + arrow_height()) - .alignment_axis(align_offset()), + .main_axis(side_offset.get() + arrow_height()) + .alignment_axis(align_offset.get()), )))]; - if avoid_collisions() { + if avoid_collisions.get() { let mut shift_options = ShiftOptions::default() .detect_overflow(detect_overflow_options.clone()) .main_axis(true) .cross_axis(false); - if sticky() == Sticky::Partial { + if sticky.get() == Sticky::Partial { shift_options = shift_options .limiter(Box::new(LimitShift::new(LimitShiftOptions::default()))); } @@ -229,13 +218,13 @@ pub fn PopperContent( content_style .set_property( "--radix-popper-available-width", - &format!("{}px", available_width), + &format!("{available_width}px"), ) .expect("Style should be updated."); content_style .set_property( "--radix-popper-available-height", - &format!("{}px", available_height), + &format!("{available_height}px"), ) .expect("Style should be updated."); content_style @@ -254,7 +243,7 @@ pub fn PopperContent( ))); middleware.push(Box::new(Arrow::new( - ArrowOptions::new(arrow_ref).padding(Padding::All(arrow_padding())), + ArrowOptions::new(arrow_ref).padding(Padding::All(arrow_padding.get())), ))); middleware.push(Box::new(TransformOrigin::new(TransformOriginOptions { @@ -262,7 +251,7 @@ pub fn PopperContent( arrow_height: arrow_height(), }))); - if hide_when_detached() { + if hide_when_detached.get() { middleware.push(Box::new(Hide::new( HideOptions::default() .detect_overflow(detect_overflow_options) @@ -270,7 +259,7 @@ pub fn PopperContent( ))); } - Some(middleware) + Some(SendWrapper::new(middleware)) })), ); @@ -279,9 +268,7 @@ pub fn PopperContent( Effect::new(move |_| { if is_positioned.get() { - if let Some(on_placed) = on_placed { - on_placed.call(()); - } + on_placed.run(()); } }); @@ -292,7 +279,7 @@ pub fn PopperContent( arrow_data().is_none_or(|arrow_data| arrow_data.center_offset != 0.0) }); - let (content_z_index, set_content_z_index) = create_signal::>(None); + let (content_z_index, set_content_z_index) = signal::>(None); Effect::new(move |_| { if let Some(content) = content_ref.get() { set_content_z_index.set(Some( @@ -321,10 +308,6 @@ pub fn PopperContent( .unwrap_or(false) }; - let dir = attrs - .iter() - .find_map(|(key, value)| (*key == "dir").then_some(value.clone())); - let content_context_value = PopperContentContextValue { placed_side, arrow_ref, @@ -333,62 +316,51 @@ pub fn PopperContent( should_hide_arrow: cannot_center_arrow, }; - let mut attrs = attrs.clone(); - attrs.extend([ - ( - "data-side", - (move || format!("{:?}", placed_side.get()).to_lowercase()).into_attribute(), - ), - ( - "data-align", - (move || format!("{:?}", placed_align()).to_lowercase()).into_attribute(), - ), - // If the PopperContent hasn't been placed yet (not all measurements done), - // we prevent animations so that users's animation don't kick in too early referring wrong sides. - ( - "style", - (move || (!is_positioned.get()).then_some("animation: none;")).into_attribute(), - ), - ]); - view! { -
floating_styles.get().style_transform(), - // Keep off the page when measuring - false => Some("translate(0, -200%)".into()) - } - style:will-change=move || floating_styles.get().style_will_change() - style:min-width="max-content" - style:z-index=content_z_index - style=("--radix-popper-transform-origin", transform_origin) - - // Hide the content if using the hide middleware and should be hidden set visibility to hidden - // and disable pointer events so the UI behaves as if the PopperContent isn't there at all. - style:visibility=move || reference_hidden().then_some("hidden") - style:pointer-events=move || reference_hidden().then_some("none") - - // Floating UI interally calculates logical alignment based the `dir` attribute on - // the reference/floating node, we must add this attribute here to ensure - // this is calculated when portalled as well as inline. - dir={dir} - > - - - {children()} - - -
+ +
floating_styles.get().style_transform(), + // Keep off the page when measuring + false => Some("translate(0, -200%)".into()) + } + style:will-change=move || floating_styles.get().style_will_change() + style:min-width="max-content" + style:z-index=content_z_index + style=("--radix-popper-transform-origin", transform_origin) + + // Hide the content if using the hide middleware and should be hidden set visibility to hidden + // and disable pointer events so the UI behaves as if the PopperContent isn't there at all. + style:visibility=move || reference_hidden().then_some("hidden") + style:pointer-events=move || reference_hidden().then_some("none") + + // Floating UI interally calculates logical alignment based the `dir` attribute on + // the reference/floating node, we must add this attribute here to ensure + // this is calculated when portalled as well as inline. + dir=move || dir.get() + > + + + {children.with_value(|children| children())} + + +
+
} } @@ -397,62 +369,66 @@ pub fn PopperArrow( #[prop(into, optional)] width: MaybeProp, #[prop(into, optional)] height: MaybeProp, #[prop(into, optional)] as_child: MaybeProp, - #[prop(optional)] node_ref: NodeRef, - #[prop(attrs)] attrs: Vec<(&'static str, Attribute)>, + #[prop(optional)] node_ref: AnyNodeRef, #[prop(optional)] children: Option, ) -> impl IntoView { let children = StoredValue::new(children); let content_context: PopperContentContextValue = expect_context(); - let arrow_ref = content_context.arrow_ref; let base_side = move || content_context.placed_side.get().opposite(); - let mut attrs = attrs.clone(); - attrs.extend([("style", "display: block".into_attribute())]); - view! { - // We have to use an extra wrapper, because `ResizeObserver` (used by `useSize`) - // doesn't report size as we'd expect on SVG elements. - // It reports their bounding box, which is effectively the largest path inside the SVG. - Some("0px".into()), - _ => content_context.arrow_x.get().map(|arrow_x| format!("{}px", arrow_x)) - } - style:top=move || match base_side() { - Side::Top => Some("0px".into()), - _ => content_context.arrow_y.get().map(|arrow_y| format!("{}px", arrow_y)) - } - style:right=move || match base_side() { - Side::Right => Some("0px"), - _ => None - } - style:bottom=move || match base_side() { - Side::Bottom => Some("0px"), - _ => None - } - style:transform-origin=move || match content_context.placed_side.get() { - Side::Top => "", - Side::Right => "0 0", - Side::Bottom => "center 0", - Side::Left => "100% 0", - } - style:transform=move || match content_context.placed_side.get() { - Side::Top => "translateY(100%)", - Side::Right => "translateY(50%) rotate(90deg) translateX(-50%)", - Side::Bottom => "rotate(180deg)", - Side::Left => "translateY(50%) rotate(-90deg) translateX(50%)", - } - style:visibility=move || content_context.should_hide_arrow.get().then_some("hidden") - > - - {children.with_value(|children| children.as_ref().map(|children| children()))} - - + + // We have to use an extra wrapper, because `ResizeObserver` (used by `useSize`) + // doesn't report size as we'd expect on SVG elements. + // It reports their bounding box, which is effectively the largest path inside the SVG. + Some("0px".into()), + _ => content_context.arrow_x.get().map(|arrow_x| format!("{arrow_x}px")) + } + style:top=move || match base_side() { + Side::Top => Some("0px".into()), + _ => content_context.arrow_y.get().map(|arrow_y| format!("{arrow_y}px")) + } + style:right=move || match base_side() { + Side::Right => Some("0px"), + _ => None + } + style:bottom=move || match base_side() { + Side::Bottom => Some("0px"), + _ => None + } + style:transform-origin=move || match content_context.placed_side.get() { + Side::Top => "", + Side::Right => "0 0", + Side::Bottom => "center 0", + Side::Left => "100% 0", + } + style:transform=move || match content_context.placed_side.get() { + Side::Top => "translateY(100%)", + Side::Right => "translateY(50%) rotate(90deg) translateX(-50%)", + Side::Bottom => "rotate(180deg)", + Side::Left => "translateY(50%) rotate(-90deg) translateX(50%)", + } + style:visibility=move || content_context.should_hide_arrow.get().then_some("hidden") + > + + {children.with_value(|children| children.as_ref().map(|children| children()))} + + + } - .into_any() - .node_ref(arrow_ref) } const TRANSFORM_ORIGIN_NAME: &str = "transformOrigin"; @@ -535,7 +511,7 @@ impl Middleware for TransformOrigin { Side::Top => ( match is_arrow_hidden { true => no_arrow_align.into(), - false => format!("{}px", arrow_x_center), + false => format!("{arrow_x_center}px"), }, format!("{}px", rects.floating.height + arrow_height), ), @@ -543,13 +519,13 @@ impl Middleware for TransformOrigin { format!("{}px", -arrow_height), match is_arrow_hidden { true => no_arrow_align.into(), - false => format!("{}px", arrow_y_center), + false => format!("{arrow_y_center}px"), }, ), Side::Bottom => ( match is_arrow_hidden { true => no_arrow_align.into(), - false => format!("{}px", arrow_x_center), + false => format!("{arrow_x_center}px"), }, format!("{}px", -arrow_height), ), @@ -557,7 +533,7 @@ impl Middleware for TransformOrigin { format!("{}px", rects.floating.width + arrow_height), match is_arrow_hidden { true => no_arrow_align.into(), - false => format!("{}px", arrow_y_center), + false => format!("{arrow_y_center}px"), }, ), }; diff --git a/packages/primitives/yew/popper/Cargo.toml b/packages/primitives/yew/popper/Cargo.toml index 7060031c..b1ccc128 100644 --- a/packages/primitives/yew/popper/Cargo.toml +++ b/packages/primitives/yew/popper/Cargo.toml @@ -9,7 +9,7 @@ repository.workspace = true version.workspace = true [dependencies] -floating-ui-yew = "0.3.0" +floating-ui-yew = "0.4.0" radix-yew-arrow.workspace = true radix-yew-primitive.workspace = true radix-yew-use-size.workspace = true diff --git a/stories/leptos/Cargo.toml b/stories/leptos/Cargo.toml index 4776a4d2..dfffeef3 100644 --- a/stories/leptos/Cargo.toml +++ b/stories/leptos/Cargo.toml @@ -25,7 +25,7 @@ radix-leptos-aspect-ratio.workspace = true # radix-leptos-focus-scope.workspace = true radix-leptos-label.workspace = true # radix-leptos-menu.workspace = true -# radix-leptos-popper.workspace = true +radix-leptos-popper.workspace = true radix-leptos-portal.workspace = true # radix-leptos-presence.workspace = true # radix-leptos-progress.workspace = true @@ -33,5 +33,6 @@ radix-leptos-separator.workspace = true # radix-leptos-switch.workspace = true # radix-leptos-toggle.workspace = true radix-leptos-visually-hidden.workspace = true +send_wrapper.workspace = true tailwind_fuse.workspace = true web-sys.workspace = true diff --git a/stories/leptos/src/app.rs b/stories/leptos/src/app.rs index d4c5cf78..b0004ccb 100644 --- a/stories/leptos/src/app.rs +++ b/stories/leptos/src/app.rs @@ -5,7 +5,7 @@ use leptos_router::{ }; use crate::primitives::{ - accessible_icon, arrow, aspect_ratio, label, portal, separator, visually_hidden, + accessible_icon, arrow, aspect_ratio, label, popper, portal, separator, visually_hidden, }; #[component] @@ -120,18 +120,18 @@ pub fn App() -> impl IntoView { //
  • Styled
  • // // - //
  • - // Popper +
  • + Popper - //
      - //
    • Styled
    • - //
    • With Custom Arrow
    • - //
    • Animated
    • - //
    • With Portal
    • - //
    • With Update Position Strategy Always
    • - //
    • Chromatic
    • - //
    - //
  • +
      +
    • Styled
    • +
    • With Custom Arrow
    • +
    • Animated
    • +
    • With Portal
    • +
    • With Update Position Strategy Always
    • +
    • Chromatic
    • +
    +
  • Portal @@ -247,12 +247,12 @@ pub fn App() -> impl IntoView { // - // - // - // - // - // - // + + + + + + diff --git a/stories/leptos/src/primitives.rs b/stories/leptos/src/primitives.rs index dedc2aae..5d7dcdb5 100644 --- a/stories/leptos/src/primitives.rs +++ b/stories/leptos/src/primitives.rs @@ -8,7 +8,7 @@ pub mod aspect_ratio; pub mod label; // pub mod menu; // pub mod playground; -// pub mod popper; +pub mod popper; pub mod portal; // pub mod presence; // pub mod progress; diff --git a/stories/leptos/src/primitives/popper.rs b/stories/leptos/src/primitives/popper.rs index 3b46cb19..a8b7b448 100644 --- a/stories/leptos/src/primitives/popper.rs +++ b/stories/leptos/src/primitives/popper.rs @@ -1,8 +1,9 @@ use std::time::Duration; -use leptos::*; +use leptos::prelude::*; use radix_leptos_popper::*; use radix_leptos_portal::Portal; +use send_wrapper::SendWrapper; use tailwind_fuse::*; #[component] @@ -11,7 +12,7 @@ pub fn Styled() -> impl IntoView { let content_class = Memo::new(move |_| ContentClass::default().to_class()); let arrow_class = Memo::new(move |_| ArrowClass::default().to_class()); - let (open, set_open) = create_signal(false); + let (open, set_open) = signal(false); view! { @@ -36,7 +37,7 @@ pub fn WithCustomArrow() -> impl IntoView { let anchor_class = Memo::new(move |_| AnchorClass::default().to_class()); let content_class = Memo::new(move |_| ContentClass::default().to_class()); - let (open, set_open) = create_signal(false); + let (open, set_open) = signal(false); view! { @@ -48,7 +49,7 @@ pub fn WithCustomArrow() -> impl IntoView { - + @@ -64,7 +65,7 @@ pub fn Animated() -> impl IntoView { let animated_content_class = Memo::new(move |_| AnimatedContentClass::default().to_class()); let arrow_class = Memo::new(move |_| ArrowClass::default().to_class()); - let (open, set_open) = create_signal(false); + let (open, set_open) = signal(false); view! { @@ -76,7 +77,7 @@ pub fn Animated() -> impl IntoView { - + @@ -90,7 +91,7 @@ pub fn WithPortal() -> impl IntoView { let content_class = Memo::new(move |_| ContentClass::default().to_class()); let arrow_class = Memo::new(move |_| ArrowClass::default().to_class()); - let (open, set_open) = create_signal(false); + let (open, set_open) = signal(false); view! { @@ -118,8 +119,8 @@ pub fn WithUpdatePositionStrategyAlways() -> impl IntoView { let content_class = Memo::new(move |_| ContentClass::default().to_class()); let arrow_class = Memo::new(move |_| ArrowClass::default().to_class()); - let (open, set_open) = create_signal(false); - let (left, set_left) = create_signal(0); + let (open, set_open) = signal(false); + let (left, set_left) = signal(0); let handle = set_interval_with_handle( move || { @@ -164,24 +165,28 @@ pub fn Chromatic() -> impl IntoView { let arrow_class = Memo::new(move |_| ArrowClass::default().to_class()); let scroll_container1_ref = NodeRef::new(); - let scroll_container1: Signal> = Signal::derive(move || { - scroll_container1_ref - .get() - .map(|scroll_container| { - let element: &web_sys::HtmlDivElement = &scroll_container; - vec![element.clone().into()] - }) - .unwrap_or(vec![]) + let scroll_container1: Signal>> = Signal::derive(move || { + SendWrapper::new( + scroll_container1_ref + .get() + .map(|scroll_container| { + let element: &web_sys::HtmlDivElement = &scroll_container; + vec![element.clone().into()] + }) + .unwrap_or(vec![]), + ) }); let scroll_container2_ref = NodeRef::new(); - let scroll_container2: Signal> = Signal::derive(move || { - scroll_container2_ref - .get() - .map(|scroll_container| { - let element: &web_sys::HtmlDivElement = &scroll_container; - vec![element.clone().into()] - }) - .unwrap_or(vec![]) + let scroll_container2: Signal>> = Signal::derive(move || { + SendWrapper::new( + scroll_container2_ref + .get() + .map(|scroll_container| { + let element: &web_sys::HtmlDivElement = &scroll_container; + vec![element.clone().into()] + }) + .unwrap_or(vec![]), + ) }); view! { @@ -479,10 +484,9 @@ fn Scrollable(children: Children) -> impl IntoView { } #[component] -fn CustomArrow(#[prop(attrs)] attrs: Vec<(&'static str, Attribute)>) -> impl IntoView { +fn CustomArrow() -> impl IntoView { view! {