diff --git a/Cargo.toml b/Cargo.toml index 4090134..0b6e248 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,7 @@ repository = "https://github.com/NixySoftware/floating-ui" version = "0.0.4" [workspace.dependencies] +cfg-if = "1.0.0" dyn-clone = "1.0.17" serde = { version = "1.0.197", features = ["derive"] } serde_json = "1.0.114" diff --git a/packages/core/src/compute_position.rs b/packages/core/src/compute_position.rs index 2bf1852..ad378c7 100644 --- a/packages/core/src/compute_position.rs +++ b/packages/core/src/compute_position.rs @@ -1,4 +1,4 @@ -use floating_ui_utils::{Coords, Placement, Strategy}; +use floating_ui_utils::{Coords, ElementOrVirtual, Placement, Strategy}; use crate::compute_coords_from_placement::compute_coords_from_placement; use crate::types::{ @@ -12,7 +12,7 @@ use crate::types::{ /// /// See [`Platform`][`crate::types::Platform`]. pub fn compute_position( - reference: &Element, + reference: ElementOrVirtual, floating: &Element, config: ComputePositionConfig, ) -> ComputePositionReturn { @@ -24,7 +24,7 @@ pub fn compute_position( let rtl = platform.is_rtl(floating); let mut rects = platform.get_element_rects(GetElementRectsArgs { - reference: reference.into(), + reference: reference.clone(), floating, strategy, }); @@ -52,8 +52,8 @@ pub fn compute_position( rects: &rects, platform, elements: Elements { - reference: &reference, - floating: &floating, + reference: reference.clone(), + floating, }, }); @@ -79,7 +79,7 @@ pub fn compute_position( rects = match reset_rects { ResetRects::True => { platform.get_element_rects(GetElementRectsArgs { - reference: reference.into(), + reference: reference.clone(), floating, strategy, }) @@ -128,7 +128,7 @@ mod tests { #[derive(Clone)] struct CustomMiddleware {} - impl Middleware for CustomMiddleware { + impl Middleware for CustomMiddleware { fn name(&self) -> &'static str { "custom" } @@ -150,7 +150,7 @@ mod tests { strategy, middleware_data, } = compute_position( - &REFERENCE, + (&REFERENCE).into(), &FLOATING, ComputePositionConfig { platform: &PLATFORM, @@ -175,7 +175,7 @@ mod tests { #[derive(Clone)] struct TestMiddleware {} - impl Middleware for TestMiddleware { + impl Middleware for TestMiddleware { fn name(&self) -> &'static str { "test" } @@ -194,7 +194,7 @@ mod tests { } let ComputePositionReturn { x, y, .. } = compute_position( - &REFERENCE, + (&REFERENCE).into(), &FLOATING, ComputePositionConfig { platform: &PLATFORM, @@ -205,7 +205,7 @@ mod tests { ); let ComputePositionReturn { x: x2, y: y2, .. } = compute_position( - &REFERENCE, + (&REFERENCE).into(), &FLOATING, ComputePositionConfig { platform: &PLATFORM, @@ -223,7 +223,7 @@ mod tests { #[derive(Clone)] struct TestMiddleware {} - impl Middleware for TestMiddleware { + impl Middleware for TestMiddleware { fn name(&self) -> &'static str { "test" } @@ -241,7 +241,7 @@ mod tests { let ComputePositionReturn { middleware_data, .. } = compute_position( - &REFERENCE, + (&REFERENCE).into(), &FLOATING, ComputePositionConfig { platform: &PLATFORM, diff --git a/packages/core/src/detect_overflow.rs b/packages/core/src/detect_overflow.rs index 3fbc964..205ebb3 100644 --- a/packages/core/src/detect_overflow.rs +++ b/packages/core/src/detect_overflow.rs @@ -1,6 +1,6 @@ use floating_ui_utils::{ - get_padding_object, rect_to_client_rect, Coords, OwnedElementOrWindow, Padding, Rect, - SideObject, + get_padding_object, rect_to_client_rect, Coords, ElementOrVirtual, OwnedElementOrWindow, + Padding, Rect, SideObject, }; use crate::types::{ @@ -113,20 +113,28 @@ pub fn detect_overflow( ElementContext::Floating => ElementContext::Reference, }; let element = match alt_boundary { - true => *elements.get_element_context(alt_context), - false => *elements.get_element_context(element_context), + true => elements.get_element_context(alt_context), + false => elements.get_element_context(element_context), }; - // let document_element = platform.get_document_element(elements.floating); + let document_element = platform.get_document_element(elements.floating); + let context_element: Option; + + let element = match element { + ElementOrVirtual::Element(element) => element, + ElementOrVirtual::VirtualElement(virtual_element) => { + context_element = virtual_element.context_element(); + + context_element + .as_ref() + .or(document_element.as_ref()) + .expect("Element should exist.") + } + }; let clipping_client_rect = rect_to_client_rect(platform.get_clipping_rect(GetClippingRectArgs { element, - // TODO: virtual element - // match platform.is_element(element).unwrap_or(true) { - // true => element, - // false => document_element.as_ref().unwrap_or(element), - // }, boundary, root_boundary, strategy, diff --git a/packages/core/src/middleware/shift.rs b/packages/core/src/middleware/shift.rs index 7218e3b..a6fa83f 100644 --- a/packages/core/src/middleware/shift.rs +++ b/packages/core/src/middleware/shift.rs @@ -17,7 +17,7 @@ use crate::{ pub const SHIFT_NAME: &str = "shift"; /// Limiter used by [`Shift`] middleware. Limits the shifting done in order to prevent detachment. -pub trait Limiter: DynClone { +pub trait Limiter: DynClone { fn compute(&self, state: MiddlewareState) -> Coords; } @@ -232,7 +232,7 @@ impl<'a, Element: Clone, Window: Clone> #[derive(Clone, Debug, Default)] pub struct DefaultLimiter; -impl Limiter for DefaultLimiter { +impl Limiter for DefaultLimiter { fn compute(&self, state: MiddlewareState) -> Coords { Coords { x: state.x, @@ -278,7 +278,7 @@ impl Default for LimitShiftOffset { /// Options for [`LimitShift`] limiter. #[derive(Clone)] -pub struct LimitShiftOptions<'a, Element, Window> { +pub struct LimitShiftOptions<'a, Element: Clone, Window: Clone> { pub offset: Option>, pub main_axis: Option, diff --git a/packages/core/src/types.rs b/packages/core/src/types.rs index 8d317cb..f43c56a 100644 --- a/packages/core/src/types.rs +++ b/packages/core/src/types.rs @@ -11,12 +11,12 @@ use floating_ui_utils::{ pub type DerivableFn<'a, Element, Window, T> = &'a dyn Fn(MiddlewareState) -> T; -pub enum Derivable<'a, Element, Window, T: Clone> { +pub enum Derivable<'a, Element: Clone, Window: Clone, T: Clone> { Value(T), Fn(DerivableFn<'a, Element, Window, T>), } -impl<'a, Element, Window, T: Clone> Clone for Derivable<'a, Element, Window, T> { +impl<'a, Element: Clone, Window: Clone, T: Clone> Clone for Derivable<'a, Element, Window, T> { fn clone(&self) -> Self { match self { Self::Value(value) => Self::Value(value.clone()), @@ -25,7 +25,7 @@ impl<'a, Element, Window, T: Clone> Clone for Derivable<'a, Element, Window, T> } } -impl<'a, Element, Window, T: Clone> Derivable<'a, Element, Window, T> { +impl<'a, Element: Clone, Window: Clone, T: Clone> Derivable<'a, Element, Window, T> { pub fn evaluate(&self, state: MiddlewareState) -> T { match self { Derivable::Value(value) => value.clone(), @@ -34,13 +34,13 @@ impl<'a, Element, Window, T: Clone> Derivable<'a, Element, Window, T> { } } -impl<'a, Element, Window, T: Clone> From for Derivable<'a, Element, Window, T> { +impl<'a, Element: Clone, Window: Clone, T: Clone> From for Derivable<'a, Element, Window, T> { fn from(value: T) -> Self { Derivable::Value(value) } } -impl<'a, Element, Window, T: Clone> From> +impl<'a, Element: Clone, Window: Clone, T: Clone> From> for Derivable<'a, Element, Window, T> { fn from(value: DerivableFn<'a, Element, Window, T>) -> Self { @@ -64,7 +64,11 @@ pub struct GetClippingRectArgs<'a, Element> { } /// Arguments for [`Platform::convert_offset_parent_relative_rect_to_viewport_relative_rect`]. -pub struct ConvertOffsetParentRelativeRectToViewportRelativeRectArgs<'a, Element, Window> { +pub struct ConvertOffsetParentRelativeRectToViewportRelativeRectArgs< + 'a, + Element: Clone, + Window: Clone, +> { pub elements: Option>, pub rect: Rect, pub offset_parent: Option>, @@ -103,7 +107,10 @@ pub trait Platform: Debug { None } - fn get_client_rects(&self, _element: &Element) -> Option> { + fn get_client_rects( + &self, + _element: ElementOrVirtual, + ) -> Option> { None } @@ -250,7 +257,7 @@ pub struct MiddlewareReturn { } /// Middleware used by [`compute_position`][`crate::compute_position::compute_position`]. -pub trait Middleware: DynClone { +pub trait Middleware: DynClone { /// The name of this middleware. fn name(&self) -> &'static str; @@ -261,41 +268,51 @@ pub trait Middleware: DynClone { dyn_clone::clone_trait_object!( Middleware); /// Middleware with options. -pub trait MiddlewareWithOptions { +pub trait MiddlewareWithOptions { /// The options passed to this middleware. fn options(&self) -> &Derivable; } -#[derive(Clone, Debug)] -pub struct Elements<'a, Element> { - pub reference: &'a Element, +pub struct Elements<'a, Element: Clone> { + pub reference: ElementOrVirtual<'a, Element>, pub floating: &'a Element, } -impl<'a, Element> Elements<'a, Element> { - pub fn get_element_context(&self, element_context: ElementContext) -> &Element { +impl<'a, Element: Clone> Elements<'a, Element> { + pub fn get_element_context( + &self, + element_context: ElementContext, + ) -> ElementOrVirtual<'a, Element> { match element_context { - ElementContext::Reference => self.reference, - ElementContext::Floating => self.floating, + ElementContext::Reference => self.reference.clone(), + ElementContext::Floating => self.floating.into(), + } + } +} + +impl<'a, Element: Clone> Clone for Elements<'a, Element> { + fn clone(&self) -> Self { + Self { + reference: self.reference.clone(), + floating: self.floating, } } } /// State passed to [`Middleware::compute`]. -#[derive(Debug)] -pub struct MiddlewareState<'a, Element, Window> { +pub struct MiddlewareState<'a, Element: Clone, Window: Clone> { pub x: f64, pub y: f64, pub initial_placement: Placement, pub placement: Placement, pub strategy: Strategy, pub middleware_data: &'a MiddlewareData, - pub elements: Elements<'a, &'a Element>, + pub elements: Elements<'a, Element>, pub rects: &'a ElementRects, pub platform: &'a dyn Platform, } -impl<'a, Element, Window> Clone for MiddlewareState<'a, Element, Window> { +impl<'a, Element: Clone, Window: Clone> Clone for MiddlewareState<'a, Element, Window> { fn clone(&self) -> Self { Self { x: self.x, diff --git a/packages/dom/example/src/lib.rs b/packages/dom/example/src/lib.rs index aeed616..aa9f0d9 100644 --- a/packages/dom/example/src/lib.rs +++ b/packages/dom/example/src/lib.rs @@ -7,7 +7,7 @@ use floating_ui_dom::{ }; use log::Level; use wasm_bindgen::prelude::*; -use web_sys::HtmlElement; +use web_sys::{Element, HtmlElement}; #[wasm_bindgen(start)] fn run() -> Result<(), JsValue> { @@ -40,6 +40,8 @@ fn run() -> Result<(), JsValue> { tooltip: &HtmlElement, arrow: &HtmlElement, ) -> Result<(), JsValue> { + let button_element: &Element = button; + let ComputePositionReturn { x, y, @@ -47,7 +49,7 @@ fn run() -> Result<(), JsValue> { middleware_data, .. } = compute_position( - button, + button_element.into(), tooltip, Some( ComputePositionConfig::default() diff --git a/packages/dom/src/lib.rs b/packages/dom/src/lib.rs index 46da076..a4e3ba0 100644 --- a/packages/dom/src/lib.rs +++ b/packages/dom/src/lib.rs @@ -74,7 +74,7 @@ impl ComputePositionConfig { /// Computes the `x` and `y` coordinates that will place the floating element next to a given reference element. pub fn compute_position( - reference: &Element, + reference: ElementOrVirtual, floating: &Element, config: Option, ) -> ComputePositionReturn { diff --git a/packages/dom/src/platform.rs b/packages/dom/src/platform.rs index 49a2253..37066d2 100644 --- a/packages/dom/src/platform.rs +++ b/packages/dom/src/platform.rs @@ -18,6 +18,8 @@ use floating_ui_utils::{ }; use web_sys::{Element, Window}; +use crate::types::ElementOrVirtual; + use self::convert_offset_parent_relative_rect_to_viewport_relative_rect::convert_offset_parent_relative_rect_to_viewport_relative_rect; use self::get_client_length::get_client_length; use self::get_client_rects::get_client_rects; @@ -59,7 +61,6 @@ impl CorePlatform for Platform { } fn is_element(&self, _value: &ElementOrWindow) -> Option { - // TODO: value should probably be expanded in CorePlatform Some(true) } @@ -67,7 +68,7 @@ impl CorePlatform for Platform { Some(get_document_element(Some(element.into()))) } - fn get_client_rects(&self, element: &Element) -> Option> { + fn get_client_rects(&self, element: ElementOrVirtual) -> Option> { Some(get_client_rects(element)) } diff --git a/packages/dom/src/platform/get_client_rects.rs b/packages/dom/src/platform/get_client_rects.rs index 1f35542..3db55ef 100644 --- a/packages/dom/src/platform/get_client_rects.rs +++ b/packages/dom/src/platform/get_client_rects.rs @@ -1,12 +1,14 @@ use floating_ui_utils::ClientRectObject; -use web_sys::Element; -use crate::utils::get_bounding_client_rect::dom_rect_to_client_rect_object; +use crate::types::ElementOrVirtual; -pub fn get_client_rects(element: &Element) -> Vec { - let dom_rect_list = element.get_client_rects(); - - (0..dom_rect_list.length()) - .filter_map(|i| dom_rect_list.item(i).map(dom_rect_to_client_rect_object)) - .collect() +pub fn get_client_rects(element: ElementOrVirtual) -> Vec { + match element { + ElementOrVirtual::Element(element) => { + ClientRectObject::from_dom_rect_list(element.get_client_rects()) + } + ElementOrVirtual::VirtualElement(virtual_element) => virtual_element + .get_client_rects() + .expect("Virtual element must implement `get_client_rects`."), + } } diff --git a/packages/dom/src/types.rs b/packages/dom/src/types.rs index e382d28..b9ae0ad 100644 --- a/packages/dom/src/types.rs +++ b/packages/dom/src/types.rs @@ -1,11 +1,13 @@ use floating_ui_core::{Boundary as CoreBoundary, Middleware}; use floating_ui_utils::{ - ElementOrVirtual as CoreElementOrVirtual, OwnedElementOrVirtual as CoreOwnedElementOrVirtual, + DefaultVirtualElement as CoreDefaultVirtualElement, ElementOrVirtual as CoreElementOrVirtual, + OwnedElementOrVirtual as CoreOwnedElementOrVirtual, }; use web_sys::{Element, Window}; pub type Boundary = CoreBoundary; +pub type DefaultVirtualElement = CoreDefaultVirtualElement; pub type ElementOrVirtual<'a> = CoreElementOrVirtual<'a, Element>; pub type OwnedElementOrVirtual = CoreOwnedElementOrVirtual; diff --git a/packages/dom/src/utils/get_bounding_client_rect.rs b/packages/dom/src/utils/get_bounding_client_rect.rs index 5baa8da..634013e 100644 --- a/packages/dom/src/utils/get_bounding_client_rect.rs +++ b/packages/dom/src/utils/get_bounding_client_rect.rs @@ -2,7 +2,6 @@ use floating_ui_utils::{ dom::{get_computed_style, get_window, DomElementOrWindow}, rect_to_client_rect, ClientRectObject, Coords, Rect, }; -use web_sys::DomRect; use crate::{ platform::get_scale::get_scale, @@ -10,19 +9,6 @@ use crate::{ utils::get_visual_offsets::{get_visual_offsets, should_add_visual_offsets}, }; -pub fn dom_rect_to_client_rect_object(dom_rect: DomRect) -> ClientRectObject { - ClientRectObject { - x: dom_rect.x(), - y: dom_rect.y(), - width: dom_rect.width(), - height: dom_rect.height(), - top: dom_rect.top(), - right: dom_rect.right(), - bottom: dom_rect.bottom(), - left: dom_rect.left(), - } -} - pub fn get_bounding_client_rect( element_or_virtual: ElementOrVirtual, include_scale: bool, @@ -30,9 +16,7 @@ pub fn get_bounding_client_rect( offset_parent: Option, ) -> ClientRectObject { let client_rect = match &element_or_virtual { - ElementOrVirtual::Element(element) => { - dom_rect_to_client_rect_object(element.get_bounding_client_rect()) - } + ElementOrVirtual::Element(element) => element.get_bounding_client_rect().into(), ElementOrVirtual::VirtualElement(virtual_element) => { virtual_element.get_bounding_client_rect() } diff --git a/packages/leptos/example/src/app.rs b/packages/leptos/example/src/app.rs index 48c4a3f..f054779 100644 --- a/packages/leptos/example/src/app.rs +++ b/packages/leptos/example/src/app.rs @@ -1,6 +1,6 @@ use floating_ui_leptos::{ - use_floating, Arrow, ArrowOptions, MiddlewareVec, Offset, OffsetOptions, Placement, - UseFloatingOptions, UseFloatingReturn, + use_floating, Arrow, ArrowOptions, IntoReference, MiddlewareVec, Offset, OffsetOptions, + Placement, UseFloatingOptions, UseFloatingReturn, }; use leptos::{ html::{Div, Span}, @@ -24,7 +24,7 @@ pub fn App() -> impl IntoView { let UseFloatingReturn { floating_styles, .. } = use_floating( - reference, + reference.into_reference(), floating, UseFloatingOptions::default() .open(open.into()) diff --git a/packages/leptos/src/lib.rs b/packages/leptos/src/lib.rs index 8631370..eb806c2 100644 --- a/packages/leptos/src/lib.rs +++ b/packages/leptos/src/lib.rs @@ -13,8 +13,8 @@ pub use floating_ui_dom::{ auto_update, compute_position, dom, AlignedPlacement, Alignment, ApplyState, ArrowData, AutoPlacement, AutoPlacementData, AutoPlacementDataOverflow, AutoPlacementOptions, AutoUpdateOptions, Axis, ClientRectObject, ComputePositionConfig, ComputePositionReturn, - Coords, DefaultLimiter, Derivable, DerivableFn, DetectOverflowOptions, Dimensions, - ElementOrVirtual, ElementRects, FallbackStrategy, Flip, FlipData, FlipDataOverflow, + Coords, DefaultLimiter, DefaultVirtualElement, Derivable, DerivableFn, DetectOverflowOptions, + Dimensions, ElementOrVirtual, ElementRects, FallbackStrategy, Flip, FlipData, FlipDataOverflow, FlipOptions, Hide, HideData, HideOptions, HideStrategy, Inline, InlineOptions, Length, LimitShift, LimitShiftOffset, LimitShiftOffsetValues, LimitShiftOptions, Middleware, MiddlewareData, MiddlewareReturn, MiddlewareState, MiddlewareVec, MiddlewareWithOptions, diff --git a/packages/leptos/src/types.rs b/packages/leptos/src/types.rs index 2e592e8..ce8b98b 100644 --- a/packages/leptos/src/types.rs +++ b/packages/leptos/src/types.rs @@ -1,13 +1,14 @@ use std::rc::Rc; use floating_ui_dom::{ - auto_update, AutoUpdateOptions, Middleware, MiddlewareData, Placement, Strategy, + auto_update, AutoUpdateOptions, ElementOrVirtual, Middleware, MiddlewareData, Placement, + Strategy, }; use leptos::{Attribute, IntoAttribute, MaybeProp, MaybeSignal, Signal, SignalGet}; use web_sys::{Element, Window}; pub type WhileElementsMountedFn = - dyn Fn(&Element, &Element, Rc) -> WhileElementsMountedCleanupFn; + dyn Fn(ElementOrVirtual, &Element, Rc) -> WhileElementsMountedCleanupFn; pub type WhileElementsMountedCleanupFn = Box; @@ -88,12 +89,7 @@ impl UseFloatingOptions { /// Set `while_elements_mounted` option to [`auto_update`] with [`AutoUpdateOptions::default`]. pub fn while_elements_mounted_auto_update(self) -> Self { let auto_update_rc: Rc = Rc::new(|reference, floating, update| { - auto_update( - reference.into(), - floating, - update, - AutoUpdateOptions::default(), - ) + auto_update(reference, floating, update, AutoUpdateOptions::default()) }); self.while_elements_mounted(auto_update_rc.into()) } @@ -101,12 +97,7 @@ impl UseFloatingOptions { /// Set `while_elements_mounted` option to [`auto_update`] with [`AutoUpdateOptions::default`]. pub fn while_elements_mounted_auto_update_enabled(self, enabled: MaybeSignal) -> Self { let auto_update_rc: Rc = Rc::new(|reference, floating, update| { - auto_update( - reference.into(), - floating, - update, - AutoUpdateOptions::default(), - ) + auto_update(reference, floating, update, AutoUpdateOptions::default()) }); self.while_elements_mounted(MaybeProp::derive(move || { if enabled.get() { diff --git a/packages/leptos/src/use_floating.rs b/packages/leptos/src/use_floating.rs index 6518bb5..508b5e9 100644 --- a/packages/leptos/src/use_floating.rs +++ b/packages/leptos/src/use_floating.rs @@ -1,20 +1,113 @@ -use std::{cell::RefCell, rc::Rc}; +use std::{cell::RefCell, marker::PhantomData, rc::Rc}; use floating_ui_dom::{ - compute_position, ComputePositionConfig, MiddlewareData, Placement, Strategy, + compute_position, ComputePositionConfig, MiddlewareData, OwnedElementOrVirtual, Placement, + Strategy, VirtualElement, }; use leptos::{ - create_effect, create_memo, create_signal, html::ElementDescriptor, on_cleanup, watch, - SignalGet, SignalGetUntracked, + create_effect, create_memo, create_signal, + html::{AnyElement, ElementDescriptor}, + on_cleanup, watch, MaybeProp, NodeRef, SignalGet, SignalGetUntracked, }; use crate::{ node_ref::NodeRefAsElement, - types::{FloatingStyles, UseFloatingOptions, UseFloatingReturn}, + types::{FloatingStyles, UseFloatingOptions, UseFloatingReturn, WhileElementsMountedCleanupFn}, utils::{get_dpr::get_dpr, round_by_dpr::round_by_dpr}, - WhileElementsMountedCleanupFn, }; +pub enum VirtualElementOrNodeRef +where + NodeRef: NodeRefAsElement + Copy + 'static, + El: ElementDescriptor + Clone + 'static, +{ + VirtualElement(Box>), + NodeRef(NodeRef, PhantomData), +} + +impl VirtualElementOrNodeRef +where + NodeRef: NodeRefAsElement + Copy + 'static, + El: ElementDescriptor + Clone + 'static, +{ + pub fn get(&self) -> Option { + match self { + VirtualElementOrNodeRef::VirtualElement(virtual_element) => { + Some(virtual_element.clone().into()) + } + VirtualElementOrNodeRef::NodeRef(node_ref, _) => { + node_ref.get_as_element().map(|element| element.into()) + } + } + } + + pub fn get_untracked(&self) -> Option { + match self { + VirtualElementOrNodeRef::VirtualElement(virtual_element) => { + Some(virtual_element.clone().into()) + } + VirtualElementOrNodeRef::NodeRef(node_ref, _) => node_ref + .get_untracked_as_element() + .map(|element| element.into()), + } + } +} + +impl Clone for VirtualElementOrNodeRef +where + NodeRef: NodeRefAsElement + Copy + 'static, + El: ElementDescriptor + Clone + 'static, +{ + fn clone(&self) -> Self { + match self { + Self::VirtualElement(virtual_element) => Self::VirtualElement(virtual_element.clone()), + Self::NodeRef(node_ref, phantom) => Self::NodeRef(*node_ref, *phantom), + } + } +} + +impl From>> + for VirtualElementOrNodeRef, AnyElement> +{ + fn from(value: Box>) -> Self { + VirtualElementOrNodeRef::VirtualElement(value) + } +} + +impl From for VirtualElementOrNodeRef +where + NodeRef: NodeRefAsElement + Copy, + El: ElementDescriptor + Clone + 'static, +{ + fn from(value: NodeRef) -> Self { + VirtualElementOrNodeRef::NodeRef(value, PhantomData) + } +} + +pub trait IntoReference +where + NodeRef: NodeRefAsElement + Copy, + El: ElementDescriptor + Clone + 'static, +{ + fn into_reference(self) -> MaybeProp>; +} + +impl IntoReference, AnyElement> for Box> { + fn into_reference(self) -> MaybeProp, AnyElement>> { + VirtualElementOrNodeRef::VirtualElement(self).into() + } +} + +impl IntoReference for NodeRef +where + NodeRef: NodeRefAsElement + Copy, + El: ElementDescriptor + Clone + 'static, +{ + fn into_reference(self) -> MaybeProp> { + VirtualElementOrNodeRef::NodeRef(self, PhantomData).into() + } +} + /// Computes the `x` and `y` coordinates that will place the floating element next to a reference element. pub fn use_floating< Reference: NodeRefAsElement + Copy + 'static, @@ -22,7 +115,7 @@ pub fn use_floating< Floating: NodeRefAsElement + Copy + 'static, FloatingEl: ElementDescriptor + Clone + 'static, >( - reference: Reference, + reference: MaybeProp>, floating: Floating, options: UseFloatingOptions, ) -> UseFloatingReturn { @@ -83,23 +176,29 @@ pub fn use_floating< } }); + let update_reference = reference.clone(); let update = move || { - if let Some(reference_element) = reference.get_untracked_as_element() { - if let Some(floating_element) = floating.get_untracked_as_element() { - let config = ComputePositionConfig { - placement: Some(placement_option_untracked()), - strategy: Some(strategy_option_untracked()), - middleware: middleware_option_untracked(), - }; - - let position = - compute_position(&reference_element, &floating_element, Some(config)); - set_x(position.x); - set_y(position.y); - set_strategy(position.strategy); - set_placement(position.placement); - set_middleware_data(position.middleware_data); - set_is_positioned(true); + if let Some(reference) = update_reference.get_untracked() { + if let Some(reference_element) = reference.get_untracked() { + if let Some(floating_element) = floating.get_untracked_as_element() { + let config = ComputePositionConfig { + placement: Some(placement_option_untracked()), + strategy: Some(strategy_option_untracked()), + middleware: middleware_option_untracked(), + }; + + let position = compute_position( + (&reference_element).into(), + &floating_element, + Some(config), + ); + set_x(position.x); + set_y(position.y); + set_strategy(position.strategy); + set_placement(position.placement); + set_middleware_data(position.middleware_data); + set_is_positioned(true); + } } } }; @@ -117,6 +216,7 @@ pub fn use_floating< }; let cleanup_rc = Rc::new(cleanup); + let attach_reference = reference.clone(); let attach_update_rc = update_rc.clone(); let attach_cleanup_rc = cleanup_rc.clone(); let attach_while_elements_mounted_cleanup = while_elements_mounted_cleanup.clone(); @@ -124,13 +224,17 @@ pub fn use_floating< attach_cleanup_rc(); if let Some(while_elements_mounted) = options.while_elements_mounted.get() { - if let Some(reference_element) = reference.get_untracked_as_element() { - if let Some(floating_element) = floating.get_untracked_as_element() { - attach_while_elements_mounted_cleanup.replace(Some(while_elements_mounted( - &reference_element, - &floating_element, - attach_update_rc.clone(), - ))); + if let Some(reference) = attach_reference.get_untracked() { + if let Some(reference_element) = reference.get_untracked() { + if let Some(floating_element) = floating.get_untracked_as_element() { + attach_while_elements_mounted_cleanup.replace(Some( + while_elements_mounted( + (&reference_element).into(), + &floating_element, + attach_update_rc.clone(), + ), + )); + } } } } else { @@ -148,10 +252,19 @@ pub fn use_floating< let reference_attach = attach_rc.clone(); create_effect(move |_| { if let Some(reference) = reference.get() { - let reference_attach = reference_attach.clone(); - _ = reference.on_mount(move |_| { - reference_attach(); - }); + match reference { + VirtualElementOrNodeRef::VirtualElement(_) => { + reference_attach(); + } + VirtualElementOrNodeRef::NodeRef(reference, _) => { + if let Some(reference) = reference.get() { + let reference_attach = reference_attach.clone(); + _ = reference.on_mount(move |_| { + reference_attach(); + }); + } + } + } } }); @@ -225,8 +338,11 @@ mod tests { fn Component() -> impl IntoView { let reference = create_node_ref::
(); let floating = create_node_ref::
(); - let UseFloatingReturn { is_positioned, .. } = - use_floating(reference, floating, UseFloatingOptions::default()); + let UseFloatingReturn { is_positioned, .. } = use_floating( + reference.into_reference(), + floating, + UseFloatingOptions::default(), + ); view! {
diff --git a/packages/leptos/tests/playwright.rs b/packages/leptos/tests/playwright.rs index 6caa458..506325b 100644 --- a/packages/leptos/tests/playwright.rs +++ b/packages/leptos/tests/playwright.rs @@ -1,6 +1,6 @@ use std::{env, fs, path::Path, process::Command}; -const IMPLEMENTED_TESTS: [&str; 18] = [ +const IMPLEMENTED_TESTS: [&str; 19] = [ "arrow", "autoPlacement", "autoUpdate", @@ -19,6 +19,7 @@ const IMPLEMENTED_TESTS: [&str; 18] = [ "size", "table", "transform", + "virtual-element", ]; #[test] diff --git a/packages/leptos/tests/visual/src/app.rs b/packages/leptos/tests/visual/src/app.rs index b708f1d..5803f4b 100644 --- a/packages/leptos/tests/visual/src/app.rs +++ b/packages/leptos/tests/visual/src/app.rs @@ -19,6 +19,7 @@ use crate::spec::shift::Shift; use crate::spec::size::Size; use crate::spec::table::Table; use crate::spec::transform::Transform; +use crate::spec::virtual_element::VirtualElement; use crate::utils::new::New; const ROUTES: [&str; 23] = [ @@ -122,7 +123,7 @@ pub fn App() -> impl IntoView { // - // + // // // diff --git a/packages/leptos/tests/visual/src/spec.rs b/packages/leptos/tests/visual/src/spec.rs index dde84a4..4a849ef 100644 --- a/packages/leptos/tests/visual/src/spec.rs +++ b/packages/leptos/tests/visual/src/spec.rs @@ -16,3 +16,4 @@ pub mod shift; pub mod size; pub mod table; pub mod transform; +pub mod virtual_element; diff --git a/packages/leptos/tests/visual/src/spec/arrow.rs b/packages/leptos/tests/visual/src/spec/arrow.rs index aaa8aaa..159cb5b 100644 --- a/packages/leptos/tests/visual/src/spec/arrow.rs +++ b/packages/leptos/tests/visual/src/spec/arrow.rs @@ -1,8 +1,8 @@ use convert_case::{Case, Casing}; use floating_ui_leptos::{ - use_floating, Arrow, ArrowData, ArrowOptions, DetectOverflowOptions, MiddlewareVec, Offset, - OffsetOptions, Padding, Placement, Shift, ShiftOptions, Side, UseFloatingOptions, - UseFloatingReturn, ARROW_NAME, + use_floating, Arrow, ArrowData, ArrowOptions, DetectOverflowOptions, IntoReference, + MiddlewareVec, Offset, OffsetOptions, Padding, Placement, Shift, ShiftOptions, Side, + UseFloatingOptions, UseFloatingReturn, ARROW_NAME, }; use leptos::{ html::{AnyElement, Div}, @@ -37,7 +37,7 @@ pub fn Arrow() -> impl IntoView { middleware_data, .. } = use_floating( - reference_ref, + reference_ref.into_reference(), floating_ref, UseFloatingOptions::default() .placement(placement.into()) diff --git a/packages/leptos/tests/visual/src/spec/auto_placement.rs b/packages/leptos/tests/visual/src/spec/auto_placement.rs index c98b846..ad962b3 100644 --- a/packages/leptos/tests/visual/src/spec/auto_placement.rs +++ b/packages/leptos/tests/visual/src/spec/auto_placement.rs @@ -1,7 +1,7 @@ use convert_case::{Case, Casing}; use floating_ui_leptos::{ - use_floating, Alignment, AutoPlacement, AutoPlacementOptions, MiddlewareVec, Placement, Shift, - ShiftOptions, UseFloatingOptions, UseFloatingReturn, + use_floating, Alignment, AutoPlacement, AutoPlacementOptions, IntoReference, MiddlewareVec, + Placement, Shift, ShiftOptions, UseFloatingOptions, UseFloatingReturn, }; use leptos::{html::Div, *}; @@ -59,7 +59,7 @@ pub fn AutoPlacement() -> impl IntoView { update, .. } = use_floating( - reference_ref, + reference_ref.into_reference(), floating_ref, UseFloatingOptions::default() .while_elements_mounted_auto_update() diff --git a/packages/leptos/tests/visual/src/spec/auto_update.rs b/packages/leptos/tests/visual/src/spec/auto_update.rs index a884b2b..617f8f9 100644 --- a/packages/leptos/tests/visual/src/spec/auto_update.rs +++ b/packages/leptos/tests/visual/src/spec/auto_update.rs @@ -2,7 +2,8 @@ use std::{cell::RefCell, rc::Rc}; use convert_case::{Case, Casing}; use floating_ui_leptos::{ - auto_update, use_floating, AutoUpdateOptions, Strategy, UseFloatingOptions, UseFloatingReturn, + auto_update, use_floating, AutoUpdateOptions, IntoReference, Strategy, UseFloatingOptions, + UseFloatingReturn, }; use leptos::{html::Div, *}; use web_sys::Element; @@ -48,7 +49,7 @@ pub fn AutoUpdate() -> impl IntoView { update, .. } = use_floating( - reference_ref, + reference_ref.into_reference(), floating_ref, UseFloatingOptions::default() .strategy(Strategy::Fixed.into()) diff --git a/packages/leptos/tests/visual/src/spec/border.rs b/packages/leptos/tests/visual/src/spec/border.rs index 71ac7db..96d1c98 100644 --- a/packages/leptos/tests/visual/src/spec/border.rs +++ b/packages/leptos/tests/visual/src/spec/border.rs @@ -1,5 +1,5 @@ use convert_case::{Case, Casing}; -use floating_ui_leptos::{use_floating, UseFloatingOptions, UseFloatingReturn}; +use floating_ui_leptos::{use_floating, IntoReference, UseFloatingOptions, UseFloatingReturn}; use leptos::{html::Div, *}; use wasm_bindgen::JsCast; @@ -37,7 +37,11 @@ pub fn Border() -> impl IntoView { strategy, update, .. - } = use_floating(reference_ref, floating_ref, UseFloatingOptions::default()); + } = use_floating( + reference_ref.into_reference(), + floating_ref, + UseFloatingOptions::default(), + ); create_effect(move |_| { let element = match node() { diff --git a/packages/leptos/tests/visual/src/spec/containing_block.rs b/packages/leptos/tests/visual/src/spec/containing_block.rs index 4543c6a..55c7fd5 100644 --- a/packages/leptos/tests/visual/src/spec/containing_block.rs +++ b/packages/leptos/tests/visual/src/spec/containing_block.rs @@ -1,4 +1,6 @@ -use floating_ui_leptos::{use_floating, Strategy, UseFloatingOptions, UseFloatingReturn}; +use floating_ui_leptos::{ + use_floating, IntoReference, Strategy, UseFloatingOptions, UseFloatingReturn, +}; use leptos::{html::Div, *}; #[component] @@ -15,7 +17,7 @@ pub fn ContainingBlock() -> impl IntoView { update, .. } = use_floating( - reference_ref, + reference_ref.into_reference(), floating_ref, UseFloatingOptions::default() .strategy(Strategy::Absolute.into()) diff --git a/packages/leptos/tests/visual/src/spec/decimal_size.rs b/packages/leptos/tests/visual/src/spec/decimal_size.rs index b435aa4..5147d7c 100644 --- a/packages/leptos/tests/visual/src/spec/decimal_size.rs +++ b/packages/leptos/tests/visual/src/spec/decimal_size.rs @@ -1,5 +1,5 @@ use floating_ui_leptos::{ - use_floating, ApplyState, MiddlewareState, MiddlewareVec, Size, SizeOptions, + use_floating, ApplyState, IntoReference, MiddlewareState, MiddlewareVec, Size, SizeOptions, UseFloatingOptions, UseFloatingReturn, }; use leptos::{html::Div, *}; @@ -40,7 +40,7 @@ pub fn DecimalSize() -> impl IntoView { update, .. } = use_floating( - reference_ref, + reference_ref.into_reference(), floating_ref, UseFloatingOptions::default().middleware(middleware.into()), ); diff --git a/packages/leptos/tests/visual/src/spec/flip.rs b/packages/leptos/tests/visual/src/spec/flip.rs index e6ca992..3ecc93f 100644 --- a/packages/leptos/tests/visual/src/spec/flip.rs +++ b/packages/leptos/tests/visual/src/spec/flip.rs @@ -1,7 +1,7 @@ use convert_case::{Case, Casing}; use floating_ui_leptos::{ - use_floating, FallbackStrategy, Flip, FlipOptions, MiddlewareVec, Placement, Shift, - ShiftOptions, UseFloatingOptions, UseFloatingReturn, + use_floating, FallbackStrategy, Flip, FlipOptions, IntoReference, MiddlewareVec, Placement, + Shift, ShiftOptions, UseFloatingOptions, UseFloatingReturn, }; use leptos::{html::Div, *}; @@ -37,7 +37,7 @@ pub fn Flip() -> impl IntoView { update, .. } = use_floating( - reference_ref, + reference_ref.into_reference(), floating_ref, UseFloatingOptions::default() .placement(placement.into()) diff --git a/packages/leptos/tests/visual/src/spec/hide.rs b/packages/leptos/tests/visual/src/spec/hide.rs index c341921..eaf53b3 100644 --- a/packages/leptos/tests/visual/src/spec/hide.rs +++ b/packages/leptos/tests/visual/src/spec/hide.rs @@ -1,8 +1,8 @@ use convert_case::{Case, Casing}; use floating_ui_leptos::{ - use_floating, ApplyState, Hide, HideData, HideOptions, HideStrategy, MiddlewareState, - MiddlewareVec, Placement, Shift, ShiftOptions, Size, SizeOptions, Strategy, UseFloatingOptions, - UseFloatingReturn, HIDE_NAME, + use_floating, ApplyState, Hide, HideData, HideOptions, HideStrategy, IntoReference, + MiddlewareState, MiddlewareVec, Placement, Shift, ShiftOptions, Size, SizeOptions, Strategy, + UseFloatingOptions, UseFloatingReturn, HIDE_NAME, }; use leptos::{html::Div, *}; use wasm_bindgen::JsCast; @@ -29,7 +29,7 @@ pub fn Hide() -> impl IntoView { update, .. } = use_floating( - reference_ref, + reference_ref.into_reference(), floating_ref, UseFloatingOptions::default() .placement(placement.into()) diff --git a/packages/leptos/tests/visual/src/spec/inline.rs b/packages/leptos/tests/visual/src/spec/inline.rs index 8712612..4c9d89a 100644 --- a/packages/leptos/tests/visual/src/spec/inline.rs +++ b/packages/leptos/tests/visual/src/spec/inline.rs @@ -2,12 +2,13 @@ use std::time::Duration; use convert_case::{Case, Casing}; use floating_ui_leptos::{ - use_floating, Coords, Flip, FlipOptions, Inline, InlineOptions, MiddlewareVec, Placement, Size, - SizeOptions, UseFloatingOptions, UseFloatingReturn, + use_floating, ClientRectObject, Coords, DefaultVirtualElement, Flip, FlipOptions, Inline, + InlineOptions, MiddlewareVec, Placement, Size, SizeOptions, UseFloatingOptions, + UseFloatingReturn, VirtualElement, VirtualElementOrNodeRef, }; use leptos::{ ev::MouseEvent, - html::{Div, Span}, + html::{AnyElement, Div}, *, }; @@ -23,7 +24,7 @@ enum ConnectedStatus { #[component] pub fn Inline() -> impl IntoView { - let reference_ref = create_node_ref::(); + let reference_ref = create_node_ref::(); let floating_ref = create_node_ref::
(); let (placement, set_placement) = create_signal(Placement::Bottom); @@ -31,8 +32,12 @@ pub fn Inline() -> impl IntoView { let (open, set_open) = create_signal(false); let (mouse_coords, set_mouse_coords) = create_signal::>(None); + let reference_signal = create_rw_signal::< + VirtualElementOrNodeRef, AnyElement>, + >(reference_ref.into()); + let UseFloatingReturn { x, y, strategy, .. } = use_floating( - reference_ref, + reference_signal.into(), floating_ref, UseFloatingOptions::default() .placement(placement.into()) @@ -102,8 +107,24 @@ pub fn Inline() -> impl IntoView { return; } - if let Some(_range) = range { - // TODO: virtual reference + if let Some(range) = range { + let range_clone = range.clone(); + + reference_signal.set( + (Box::new( + DefaultVirtualElement::new(Box::new(move || { + range.get_bounding_client_rect().into() + })) + .get_client_rects(Box::new(move || { + ClientRectObject::from_dom_rect_list( + range_clone + .get_client_rects() + .expect("Range should have client rects."), + ) + })), + ) as Box>) + .into(), + ); set_open(true); } }, @@ -142,15 +163,16 @@ pub fn Inline() -> impl IntoView {

Lorem ipsum dolor sit amet, consectetur adipiscing elit.{' '} - - {text} - . Ut eu magna eu augue efficitur bibendum id commodo tellus. Nullam + {move || view! { + + {text} + + }.into_any().node_ref(reference_ref)}. Ut eu magna eu augue efficitur bibendum id commodo tellus. Nullam gravida, mi nec sodales tincidunt, lorem orci aliquam ex, id commodo erat libero ut risus. Nam molestie non lectus sit amet tempus. Vivamus accumsan{' '} diff --git a/packages/leptos/tests/visual/src/spec/offset.rs b/packages/leptos/tests/visual/src/spec/offset.rs index 6b2f7bc..76bf464 100644 --- a/packages/leptos/tests/visual/src/spec/offset.rs +++ b/packages/leptos/tests/visual/src/spec/offset.rs @@ -1,7 +1,7 @@ use convert_case::{Case, Casing}; use floating_ui_leptos::{ - use_floating, Derivable, DerivableFn, MiddlewareState, MiddlewareVec, Offset, OffsetOptions, - OffsetOptionsValues, Placement, UseFloatingOptions, UseFloatingReturn, + use_floating, Derivable, DerivableFn, IntoReference, MiddlewareState, MiddlewareVec, Offset, + OffsetOptions, OffsetOptionsValues, Placement, UseFloatingOptions, UseFloatingReturn, }; use leptos::{html::Div, *}; @@ -67,7 +67,7 @@ pub fn Offset() -> impl IntoView { update, .. } = use_floating( - reference_ref, + reference_ref.into_reference(), floating_ref, UseFloatingOptions::default() .placement(placement.into()) diff --git a/packages/leptos/tests/visual/src/spec/placement.rs b/packages/leptos/tests/visual/src/spec/placement.rs index 6ba6226..644e6e9 100644 --- a/packages/leptos/tests/visual/src/spec/placement.rs +++ b/packages/leptos/tests/visual/src/spec/placement.rs @@ -1,5 +1,7 @@ use convert_case::{Case, Casing}; -use floating_ui_leptos::{use_floating, Placement, UseFloatingOptions, UseFloatingReturn}; +use floating_ui_leptos::{ + use_floating, IntoReference, Placement, UseFloatingOptions, UseFloatingReturn, +}; use leptos::{html::Div, *}; use crate::utils::{all_placements::ALL_PLACEMENTS, use_size::use_size}; @@ -17,7 +19,7 @@ pub fn Placement() -> impl IntoView { update, .. } = use_floating( - reference_ref, + reference_ref.into_reference(), floating_ref, UseFloatingOptions::default() .placement(placement.into()) diff --git a/packages/leptos/tests/visual/src/spec/relative.rs b/packages/leptos/tests/visual/src/spec/relative.rs index 8926d91..3df1dc7 100644 --- a/packages/leptos/tests/visual/src/spec/relative.rs +++ b/packages/leptos/tests/visual/src/spec/relative.rs @@ -1,5 +1,5 @@ use convert_case::{Case, Casing}; -use floating_ui_leptos::{use_floating, UseFloatingOptions, UseFloatingReturn}; +use floating_ui_leptos::{use_floating, IntoReference, UseFloatingOptions, UseFloatingReturn}; use leptos::{html::Div, *}; use wasm_bindgen::JsCast; @@ -26,7 +26,11 @@ pub fn Relative() -> impl IntoView { strategy, update, .. - } = use_floating(reference_ref, floating_ref, UseFloatingOptions::default()); + } = use_floating( + reference_ref.into_reference(), + floating_ref, + UseFloatingOptions::default(), + ); create_effect(move |_| { let element = match node() { diff --git a/packages/leptos/tests/visual/src/spec/scroll.rs b/packages/leptos/tests/visual/src/spec/scroll.rs index 6915cf0..4a202ee 100644 --- a/packages/leptos/tests/visual/src/spec/scroll.rs +++ b/packages/leptos/tests/visual/src/spec/scroll.rs @@ -1,5 +1,7 @@ use convert_case::{Case, Casing}; -use floating_ui_leptos::{use_floating, Strategy, UseFloatingOptions, UseFloatingReturn}; +use floating_ui_leptos::{ + use_floating, IntoReference, Strategy, UseFloatingOptions, UseFloatingReturn, +}; use leptos::{html::Div, *}; use crate::utils::use_scroll::{use_scroll, UseScrollOptions, UseScrollReturn}; @@ -29,7 +31,7 @@ pub fn Scroll() -> impl IntoView { let (node, set_node) = create_signal(Node::ReferenceScrollParent); let UseFloatingReturn { x, y, update, .. } = use_floating( - reference_ref, + reference_ref.into_reference(), floating_ref, UseFloatingOptions::default().strategy(strategy.into()), ); diff --git a/packages/leptos/tests/visual/src/spec/scrollbars.rs b/packages/leptos/tests/visual/src/spec/scrollbars.rs index 5603452..02c6637 100644 --- a/packages/leptos/tests/visual/src/spec/scrollbars.rs +++ b/packages/leptos/tests/visual/src/spec/scrollbars.rs @@ -1,7 +1,7 @@ use convert_case::{Case, Casing}; use floating_ui_leptos::{ - use_floating, DetectOverflowOptions, MiddlewareVec, Placement, Shift, ShiftOptions, - UseFloatingOptions, UseFloatingReturn, + use_floating, DetectOverflowOptions, IntoReference, MiddlewareVec, Placement, Shift, + ShiftOptions, UseFloatingOptions, UseFloatingReturn, }; use leptos::{html::Div, *}; @@ -26,7 +26,7 @@ pub fn Scrollbars() -> impl IntoView { update, .. } = use_floating( - reference_ref, + reference_ref.into_reference(), floating_ref, UseFloatingOptions::default() .placement(placement.into()) diff --git a/packages/leptos/tests/visual/src/spec/shift.rs b/packages/leptos/tests/visual/src/spec/shift.rs index 56d43af..d1382f4 100644 --- a/packages/leptos/tests/visual/src/spec/shift.rs +++ b/packages/leptos/tests/visual/src/spec/shift.rs @@ -1,8 +1,8 @@ use convert_case::{Case, Casing}; use floating_ui_leptos::{ - use_floating, Derivable, DerivableFn, LimitShift, LimitShiftOffset, LimitShiftOffsetValues, - LimitShiftOptions, MiddlewareState, MiddlewareVec, Offset, OffsetOptions, Placement, Shift, - ShiftOptions, UseFloatingOptions, UseFloatingReturn, + use_floating, Derivable, DerivableFn, IntoReference, LimitShift, LimitShiftOffset, + LimitShiftOffsetValues, LimitShiftOptions, MiddlewareState, MiddlewareVec, Offset, + OffsetOptions, Placement, Shift, ShiftOptions, UseFloatingOptions, UseFloatingReturn, }; use leptos::{html::Div, *}; @@ -66,7 +66,7 @@ pub fn Shift() -> impl IntoView { update, .. } = use_floating( - reference_ref, + reference_ref.into_reference(), floating_ref, UseFloatingOptions::default() .placement(placement.into()) diff --git a/packages/leptos/tests/visual/src/spec/size.rs b/packages/leptos/tests/visual/src/spec/size.rs index cc090e8..2b49b76 100644 --- a/packages/leptos/tests/visual/src/spec/size.rs +++ b/packages/leptos/tests/visual/src/spec/size.rs @@ -1,8 +1,8 @@ use convert_case::{Case, Casing}; use floating_ui_leptos::{ - use_floating, ApplyState, DetectOverflowOptions, Flip, FlipOptions, MiddlewareState, - MiddlewareVec, Placement, Shift, ShiftOptions, Size, SizeOptions, UseFloatingOptions, - UseFloatingReturn, + use_floating, ApplyState, DetectOverflowOptions, Flip, FlipOptions, IntoReference, + MiddlewareState, MiddlewareVec, Placement, Shift, ShiftOptions, Size, SizeOptions, + UseFloatingOptions, UseFloatingReturn, }; use leptos::{html::Div, *}; use wasm_bindgen::JsCast; @@ -33,7 +33,7 @@ pub fn Size() -> impl IntoView { update, .. } = use_floating( - reference_ref, + reference_ref.into_reference(), floating_ref, UseFloatingOptions::default() .placement(placement.into()) diff --git a/packages/leptos/tests/visual/src/spec/table.rs b/packages/leptos/tests/visual/src/spec/table.rs index 14bbdcd..e40c5d4 100644 --- a/packages/leptos/tests/visual/src/spec/table.rs +++ b/packages/leptos/tests/visual/src/spec/table.rs @@ -1,5 +1,5 @@ use convert_case::{Case, Casing}; -use floating_ui_leptos::{use_floating, UseFloatingOptions, UseFloatingReturn}; +use floating_ui_leptos::{use_floating, IntoReference, UseFloatingOptions, UseFloatingReturn}; use leptos::{ html::{Div, Table, Td, Tr}, *, @@ -32,7 +32,7 @@ pub fn Table() -> impl IntoView { update, .. } = use_floating( - reference_table_ref, + reference_table_ref.into_reference(), floating_ref, UseFloatingOptions::default(), ); diff --git a/packages/leptos/tests/visual/src/spec/transform.rs b/packages/leptos/tests/visual/src/spec/transform.rs index 37dae27..797c613 100644 --- a/packages/leptos/tests/visual/src/spec/transform.rs +++ b/packages/leptos/tests/visual/src/spec/transform.rs @@ -2,9 +2,13 @@ use std::ops::Deref; use convert_case::{Case, Casing}; use floating_ui_leptos::{ - use_floating, MiddlewareVec, Shift, ShiftOptions, UseFloatingOptions, UseFloatingReturn, + use_floating, DefaultVirtualElement, MiddlewareVec, Shift, ShiftOptions, UseFloatingOptions, + UseFloatingReturn, VirtualElement, VirtualElementOrNodeRef, +}; +use leptos::{ + html::{AnyElement, Div}, + *, }; -use leptos::{html::Div, *}; use wasm_bindgen::JsCast; #[derive(Copy, Clone, Debug, PartialEq)] @@ -38,12 +42,33 @@ const ALL_NODES: [Node; 11] = [ #[component] pub fn Transform() -> impl IntoView { - let reference_ref = create_node_ref::

(); + let reference_ref = create_node_ref::(); let floating_ref = create_node_ref::
(); let offset_parent_ref = create_node_ref::
(); let (node, set_node) = create_signal(Node::None); + let reference_signal: MaybeProp, AnyElement>> = + MaybeProp::derive(move || match node() { + Node::Virtual => { + let context_element = document() + .get_element_by_id("virtual-context") + .expect("Element should exist."); + let virtual_context_clone = context_element.clone(); + + Some( + (Box::new( + DefaultVirtualElement::new(Box::new(move || { + context_element.get_bounding_client_rect().into() + })) + .context_element(virtual_context_clone), + ) as Box>) + .into(), + ) + } + _ => Some(reference_ref.into()), + }); + let middleware: MiddlewareVec = vec![Box::new(Shift::new( ShiftOptions::default().cross_axis(true), ))]; @@ -55,7 +80,7 @@ pub fn Transform() -> impl IntoView { update, .. } = use_floating( - reference_ref, + reference_signal, floating_ref, UseFloatingOptions::default() .middleware(middleware.into()) @@ -91,10 +116,7 @@ pub fn Transform() -> impl IntoView { .set_property("transform", transform) .expect("Style should be updated."); - if node() == Node::Virtual { - let _virtual_context = document().get_element_by_id("virtual-context"); - // TODO: change reference ref to virtual element - } + if node() == Node::Virtual {} } update(); @@ -151,16 +173,17 @@ pub fn Transform() -> impl IntoView { style:background="black" /> -
"scale(1.25) translate(2rem, -2rem)", - _ => "" - } - > - Reference -
+ {move || view!{ +
"scale(1.25) translate(2rem, -2rem)", + _ => "" + } + > + Reference +
+ }.into_any().node_ref(reference_ref)}
impl IntoView { } on:click=move |_| set_node(local_node) > - {format!("{:?}", local_node).to_case(Case::Camel)} + {match local_node { + Node::OffsetParent3d => "offsetParent-3d".into(), + Node::OffsetParentInverse => "offsetParent-inverse".into(), + Node::OffsetParentReference => "offsetParent-reference".into(), + _ => format!("{:?}", local_node).to_case(Case::Camel) + }} } /> diff --git a/packages/leptos/tests/visual/src/spec/virtual_element.rs b/packages/leptos/tests/visual/src/spec/virtual_element.rs new file mode 100644 index 0000000..9a6b1c7 --- /dev/null +++ b/packages/leptos/tests/visual/src/spec/virtual_element.rs @@ -0,0 +1,70 @@ +use floating_ui_leptos::{ + use_floating, DefaultVirtualElement, Strategy, UseFloatingOptions, UseFloatingReturn, + VirtualElement, +}; +use leptos::{html::Div, *}; + +use crate::utils::use_scroll::{use_scroll, UseScrollOptions, UseScrollReturn}; + +#[component] +pub fn VirtualElement() -> impl IntoView { + let reference_ref = create_node_ref::
(); + let floating_ref = create_node_ref::
(); + let virtual_element = MaybeProp::derive(move || { + let context_element = reference_ref.get(); + context_element.map(|context_element| { + let context_element_clone = context_element.clone(); + let element: &web_sys::Element = context_element.as_ref(); + (Box::new( + DefaultVirtualElement::new(Box::new(move || { + context_element_clone.get_bounding_client_rect().into() + })) + .context_element(element.clone()), + ) as Box>) + .into() + }) + }); + + let UseFloatingReturn { + x, + y, + strategy, + update, + .. + } = use_floating( + virtual_element, + floating_ref, + UseFloatingOptions::default() + .strategy(Strategy::Fixed.into()) + .while_elements_mounted_auto_update(), + ); + + let UseScrollReturn { scroll_ref, .. } = use_scroll(UseScrollOptions { + reference_ref, + floating_ref, + update, + rtl: None::.into(), + }); + + view! { +

Virtual Element

+

+
+
+
+ Reference +
+
+
+ +
+ Floating +
+ } +} diff --git a/packages/leptos/tests/visual/src/utils/use_scroll.rs b/packages/leptos/tests/visual/src/utils/use_scroll.rs index 2cc129c..5e2643d 100644 --- a/packages/leptos/tests/visual/src/utils/use_scroll.rs +++ b/packages/leptos/tests/visual/src/utils/use_scroll.rs @@ -2,8 +2,8 @@ use std::rc::Rc; use floating_ui_leptos::{ dom::{get_overflow_ancestors, OverflowAncestor}, - use_floating, DetectOverflowOptions, MiddlewareVec, Padding, Placement, Shift, ShiftOptions, - Strategy, UseFloatingOptions, UseFloatingReturn, + use_floating, DetectOverflowOptions, IntoReference, MiddlewareVec, Padding, Placement, Shift, + ShiftOptions, Strategy, UseFloatingOptions, UseFloatingReturn, }; use leptos::{html::Div, *}; use wasm_bindgen::{closure::Closure, JsCast}; @@ -50,7 +50,7 @@ pub fn use_scroll( update: indicator_update, .. } = use_floating( - reference_ref, + reference_ref.into_reference(), indicator_floating_ref, UseFloatingOptions::default() .strategy(Strategy::Fixed.into()) diff --git a/packages/utils/Cargo.toml b/packages/utils/Cargo.toml index 5a969af..a719e7c 100644 --- a/packages/utils/Cargo.toml +++ b/packages/utils/Cargo.toml @@ -9,6 +9,7 @@ repository.workspace = true version.workspace = true [dependencies] +cfg-if.workspace = true dyn-clone.workspace = true serde.workspace = true web-sys = { workspace = true, optional = true } diff --git a/packages/utils/src/lib.rs b/packages/utils/src/lib.rs index 1705c0c..d2c047c 100644 --- a/packages/utils/src/lib.rs +++ b/packages/utils/src/lib.rs @@ -243,6 +243,33 @@ pub struct ClientRectObject { pub left: f64, } +cfg_if::cfg_if! { + if #[cfg(feature = "dom")] { + impl ClientRectObject { + pub fn from_dom_rect_list(value: web_sys::DomRectList) -> Vec { + (0..value.length()) + .filter_map(|i| value.item(i).map(ClientRectObject::from)) + .collect() + } + } + + impl From for ClientRectObject { + fn from(value: web_sys::DomRect) -> Self { + Self { + x: value.x(), + y: value.y(), + width: value.width(), + height: value.height(), + top: value.top(), + right: value.right(), + bottom: value.bottom(), + left: value.left(), + } + } + } + } +} + #[derive(Clone, Debug)] pub struct ElementRects { pub reference: Rect, @@ -255,6 +282,8 @@ pub struct ElementRects { pub trait VirtualElement: DynClone { fn get_bounding_client_rect(&self) -> ClientRectObject; + fn get_client_rects(&self) -> Option>; + fn context_element(&self) -> Option; } @@ -275,9 +304,25 @@ where dyn_clone::clone_trait_object!(GetBoundingClientRectCloneable); +pub trait GetClientRectsCloneable: DynClone { + fn call(&self) -> Vec; +} + +impl GetClientRectsCloneable for F +where + F: Fn() -> Vec + Clone, +{ + fn call(&self) -> Vec { + self() + } +} + +dyn_clone::clone_trait_object!(GetClientRectsCloneable); + #[derive(Clone)] pub struct DefaultVirtualElement { pub get_bounding_client_rect: Box, + pub get_client_rects: Option>, pub context_element: Option, } @@ -285,6 +330,7 @@ impl DefaultVirtualElement { pub fn new(get_bounding_client_rect: Box) -> Self { DefaultVirtualElement { get_bounding_client_rect, + get_client_rects: None, context_element: None, } } @@ -297,6 +343,11 @@ impl DefaultVirtualElement { self } + pub fn get_client_rects(mut self, get_client_rects: Box) -> Self { + self.get_client_rects = Some(get_client_rects); + self + } + pub fn context_element(mut self, context_element: Element) -> Self { self.context_element = Some(context_element); self @@ -317,6 +368,12 @@ impl VirtualElement for DefaultVirtualElement (self.get_bounding_client_rect).call() } + fn get_client_rects(&self) -> Option> { + self.get_client_rects + .as_ref() + .map(|get_client_rects| get_client_rects.call()) + } + fn context_element(&self) -> Option { self.context_element.clone() } @@ -379,6 +436,18 @@ impl OwnedElementOrVirtual { } } +impl From for OwnedElementOrVirtual { + fn from(value: Element) -> Self { + OwnedElementOrVirtual::Element(value) + } +} + +impl From>> for OwnedElementOrVirtual { + fn from(value: Box>) -> Self { + OwnedElementOrVirtual::VirtualElement(value) + } +} + #[derive(Clone, Debug)] pub enum ElementOrWindow<'a, Element, Window> { Element(&'a Element),