diff --git a/packages/core/src/compute_position.rs b/packages/core/src/compute_position.rs index bd608c9..2bf1852 100644 --- a/packages/core/src/compute_position.rs +++ b/packages/core/src/compute_position.rs @@ -11,7 +11,7 @@ use crate::types::{ /// This export does not have any `platform` interface logic. You will need to write one for the platform you are using Floating UI with. /// /// See [`Platform`][`crate::types::Platform`]. -pub fn compute_position( +pub fn compute_position( reference: &Element, floating: &Element, config: ComputePositionConfig, @@ -24,7 +24,7 @@ pub fn compute_position( let rtl = platform.is_rtl(floating); let mut rects = platform.get_element_rects(GetElementRectsArgs { - reference, + reference: reference.into(), floating, strategy, }); @@ -79,7 +79,7 @@ pub fn compute_position( rects = match reset_rects { ResetRects::True => { platform.get_element_rects(GetElementRectsArgs { - reference, + reference: reference.into(), floating, strategy, }) diff --git a/packages/core/src/detect_overflow.rs b/packages/core/src/detect_overflow.rs index 0e7f0ed..3fbc964 100644 --- a/packages/core/src/detect_overflow.rs +++ b/packages/core/src/detect_overflow.rs @@ -87,7 +87,7 @@ impl Default for DetectOverflowOptions { /// - `0` = lies flush with the boundary /// /// See for the original documentation. -pub fn detect_overflow( +pub fn detect_overflow( state: MiddlewareState, options: DetectOverflowOptions, ) -> SideObject { diff --git a/packages/core/src/middleware.rs b/packages/core/src/middleware.rs index aa66d59..218b1e4 100644 --- a/packages/core/src/middleware.rs +++ b/packages/core/src/middleware.rs @@ -4,6 +4,7 @@ mod arrow; mod auto_placement; mod flip; mod hide; +mod inline; mod offset; mod shift; mod size; @@ -12,6 +13,7 @@ pub use arrow::*; pub use auto_placement::*; pub use flip::*; pub use hide::*; +pub use inline::*; pub use offset::*; pub use shift::*; pub use size::*; diff --git a/packages/core/src/middleware/auto_placement.rs b/packages/core/src/middleware/auto_placement.rs index 3747c6c..597f20c 100644 --- a/packages/core/src/middleware/auto_placement.rs +++ b/packages/core/src/middleware/auto_placement.rs @@ -313,7 +313,7 @@ impl<'a, Element: Clone, Window: Clone> Middleware }) .collect(); - placements_sorted_by_most_space.sort_by_key(|v| v.1 as i64); + placements_sorted_by_most_space.sort_by(|a, b| a.1.total_cmp(&b.1)); let placements_that_fit_on_each_side: Vec<_> = placements_sorted_by_most_space .clone() diff --git a/packages/core/src/middleware/flip.rs b/packages/core/src/middleware/flip.rs index f777cff..d19e14f 100644 --- a/packages/core/src/middleware/flip.rs +++ b/packages/core/src/middleware/flip.rs @@ -292,7 +292,7 @@ impl<'a, Element: Clone, Window: Clone> Middleware for Flip<'a, .iter() .filter(|overflow| overflow.overflows[0] <= 0.0) .collect(); - reset_placement.sort_by_key(|overflow| overflow.overflows[1] as i64); + reset_placement.sort_by(|a, b| a.overflows[1].total_cmp(&b.overflows[1])); let mut reset_placement = reset_placement.first().map(|overflow| overflow.placement); @@ -313,7 +313,7 @@ impl<'a, Element: Clone, Window: Clone> Middleware for Flip<'a, ) }) .collect(); - placement.sort_by_key(|v| v.1 as i64); + placement.sort_by(|a, b| a.1.total_cmp(&b.1)); let placement = placement.first().map(|v| v.0); if placement.is_some() { diff --git a/packages/core/src/middleware/inline.rs b/packages/core/src/middleware/inline.rs new file mode 100644 index 0000000..4cdb367 --- /dev/null +++ b/packages/core/src/middleware/inline.rs @@ -0,0 +1,314 @@ +use floating_ui_utils::{ + get_padding_object, get_side_axis, rect_to_client_rect, Axis, ClientRectObject, Coords, + DefaultVirtualElement, ElementOrVirtual, Padding, Rect, Side, +}; + +use crate::{ + types::{ + Derivable, DerivableFn, Middleware, MiddlewareReturn, MiddlewareState, + MiddlewareWithOptions, + }, + GetElementRectsArgs, Reset, ResetRects, ResetValue, +}; + +fn get_bounding_rect(rects: Vec) -> Rect { + let min_x = rects + .iter() + .map(|rect| rect.left) + .reduce(f64::min) + .unwrap_or(f64::INFINITY); + let min_y = rects + .iter() + .map(|rect| rect.top) + .reduce(f64::min) + .unwrap_or(f64::INFINITY); + let max_x = rects + .iter() + .map(|rect| rect.right) + .reduce(f64::max) + .unwrap_or(f64::NEG_INFINITY); + let max_y = rects + .iter() + .map(|rect| rect.bottom) + .reduce(f64::max) + .unwrap_or(f64::NEG_INFINITY); + Rect { + x: min_x, + y: min_y, + width: max_x - min_y, + height: max_y - min_y, + } +} + +fn get_rects_by_line(rects: Vec) -> Vec { + let mut sorted_rects = rects.clone(); + sorted_rects.sort_by(|a, b| a.y.total_cmp(&b.y)); + + let mut groups: Vec> = vec![]; + let mut prev_rect: Option = None; + for rect in sorted_rects { + if prev_rect.is_none() + || prev_rect.is_some_and(|prev_rect| rect.y - prev_rect.y > prev_rect.height / 2.0) + { + groups.push(vec![rect.clone()]); + } else { + groups + .last_mut() + .expect("Last group should exist.") + .push(rect.clone()); + } + prev_rect = Some(rect); + } + + groups + .into_iter() + .map(|rects| rect_to_client_rect(get_bounding_rect(rects))) + .collect() +} + +/// Name of the [`Inline`] middleware. +pub const INLINE_NAME: &str = "inline"; + +/// Options for [`Inline`]. +#[derive(Clone, Debug, Default)] +pub struct InlineOptions { + /// Viewport-relative `x` coordinate to choose a `ClientRect`. + /// + /// Defaults to [`None`]. + pub x: Option, + + /// Viewport-relative `y` coordinate to choose a `ClientRect`. + /// + /// Defaults to [`None`]. + pub y: Option, + + /// Represents the padding around a disjoined rect when choosing it. + /// + /// Defaults to `2` on all sides. + pub padding: Option, +} + +impl InlineOptions { + /// Set `x` option. + pub fn x(mut self, value: f64) -> Self { + self.x = Some(value); + self + } + + /// Set `y` option. + pub fn y(mut self, value: f64) -> Self { + self.y = Some(value); + self + } + + /// Set `x` and `y` options using [`Coords`]. + pub fn coords(mut self, value: Coords) -> Self { + self.x = Some(value.x); + self.y = Some(value.y); + self + } + + /// Set `padding` option. + pub fn padding(mut self, value: Padding) -> Self { + self.padding = Some(value); + self + } +} + +/// Provides improved positioning for inline reference elements that can span over multiple lines, such as hyperlinks or range selections. +/// +/// See for the original documentation. +pub struct Inline<'a, Element: Clone, Window: Clone> { + options: Derivable<'a, Element, Window, InlineOptions>, +} + +impl<'a, Element: Clone, Window: Clone> Inline<'a, Element, Window> { + /// Constructs a new instance of this middleware. + pub fn new(options: InlineOptions) -> Self { + Inline { + options: options.into(), + } + } + + /// Constructs a new instance of this middleware with derivable options. + pub fn new_derivable(options: Derivable<'a, Element, Window, InlineOptions>) -> Self { + Inline { options } + } + + /// Constructs a new instance of this middleware with derivable options function. + pub fn new_derivable_fn(options: DerivableFn<'a, Element, Window, InlineOptions>) -> Self { + Inline { + options: options.into(), + } + } +} + +impl<'a, Element: Clone, Window: Clone> Clone for Inline<'a, Element, Window> { + fn clone(&self) -> Self { + Self { + options: self.options.clone(), + } + } +} + +impl<'a, Element: Clone + 'static, Window: Clone> Middleware + for Inline<'a, Element, Window> +{ + fn name(&self) -> &'static str { + INLINE_NAME + } + + fn compute(&self, state: MiddlewareState) -> MiddlewareReturn { + let options = self.options.evaluate(state.clone()); + + let MiddlewareState { + placement, + strategy, + elements, + rects, + platform, + .. + } = state; + + let padding = options.padding.unwrap_or(Padding::All(2.0)); + + let native_client_rects = platform + .get_client_rects(elements.reference) + .unwrap_or(vec![]); + + let client_rects = get_rects_by_line(native_client_rects.clone()); + let fallback = rect_to_client_rect(get_bounding_rect(native_client_rects)); + let padding_object = get_padding_object(padding); + + let get_bounding_client_rect = move || { + // There are two rects and they are disjoined. + if client_rects.len() == 2 && client_rects[0].left > client_rects[1].right { + if let Some(x) = options.x { + if let Some(y) = options.y { + return client_rects + .clone() + .into_iter() + .find(|rect| { + x > rect.left - padding_object.left + && x < rect.right + padding_object.right + && y > rect.top - padding_object.top + && rect.y < rect.bottom + padding_object.bottom + }) + .unwrap_or(fallback.clone()); + } + } + } + + // There are 2 or more connected rects. + if client_rects.len() >= 2 { + if get_side_axis(placement) == Axis::Y { + let first_rect = client_rects.first().expect("Enough elements exist."); + let last_rect = client_rects.last().expect("Enough elements exist."); + let is_top = placement.side() == Side::Top; + + let top = first_rect.top; + let bottom = last_rect.bottom; + let left = match is_top { + true => first_rect.left, + false => last_rect.left, + }; + let right = match is_top { + true => first_rect.right, + false => last_rect.right, + }; + let width = right - left; + let height = bottom - top; + + return ClientRectObject { + x: left, + y: top, + width, + height, + top, + right, + bottom, + left, + }; + } + + let is_left_side = placement.side() == Side::Left; + let max_right = client_rects + .iter() + .map(|rect| rect.right) + .reduce(f64::max) + .expect("Enough elements exist."); + let min_left = client_rects + .iter() + .map(|rect| rect.left) + .reduce(f64::min) + .expect("Enough elements exist."); + let measure_rects: Vec<&ClientRectObject> = client_rects + .iter() + .filter(|rect| match is_left_side { + true => rect.left == min_left, + false => rect.right == max_right, + }) + .collect(); + + let top = measure_rects.first().expect("Enough elements exist.").top; + let bottom = measure_rects.last().expect("Enough elements exist.").bottom; + let left = min_left; + let right = max_right; + let width = right - left; + let height = bottom - top; + + return ClientRectObject { + x: left, + y: top, + width, + height, + top, + right, + bottom, + left, + }; + } + + fallback.clone() + }; + + let reset_rects = platform.get_element_rects(GetElementRectsArgs { + reference: ElementOrVirtual::VirtualElement(Box::new(DefaultVirtualElement::new( + Box::new(get_bounding_client_rect), + ))), + floating: elements.floating, + strategy, + }); + + if rects.reference.x != reset_rects.reference.x + || rects.reference.y != reset_rects.reference.y + || rects.reference.width != reset_rects.reference.width + || rects.reference.height != reset_rects.reference.height + { + MiddlewareReturn { + x: None, + y: None, + data: None, + reset: Some(Reset::Value(ResetValue { + placement: None, + rects: Some(ResetRects::Value(reset_rects)), + })), + } + } else { + MiddlewareReturn { + x: None, + y: None, + data: None, + reset: None, + } + } + } +} + +impl<'a, Element: Clone, Window: Clone> MiddlewareWithOptions + for Inline<'a, Element, Window> +{ + fn options(&self) -> &Derivable { + &self.options + } +} diff --git a/packages/core/src/middleware/offset.rs b/packages/core/src/middleware/offset.rs index 31b2887..899925f 100644 --- a/packages/core/src/middleware/offset.rs +++ b/packages/core/src/middleware/offset.rs @@ -11,7 +11,7 @@ use crate::{ }, }; -fn convert_value_to_coords( +fn convert_value_to_coords( state: MiddlewareState, options: &OffsetOptions, ) -> Coords { diff --git a/packages/core/src/middleware/size.rs b/packages/core/src/middleware/size.rs index 621f6f4..0d7c09f 100644 --- a/packages/core/src/middleware/size.rs +++ b/packages/core/src/middleware/size.rs @@ -14,6 +14,7 @@ use super::SHIFT_NAME; /// Name of the [`Size`] middleware. pub const SIZE_NAME: &str = "size"; +/// State passed to [`SizeOptions::apply`]. #[derive(Clone)] pub struct ApplyState<'a, Element: Clone, Window: Clone> { pub state: MiddlewareState<'a, Element, Window>, @@ -21,6 +22,8 @@ pub struct ApplyState<'a, Element: Clone, Window: Clone> { pub available_height: f64, } +pub type ApplyFn = dyn Fn(ApplyState); + /// Options for [`Size`] middleware. #[derive(Clone)] pub struct SizeOptions<'a, Element: Clone, Window: Clone> { @@ -30,14 +33,14 @@ pub struct SizeOptions<'a, Element: Clone, Window: Clone> { pub detect_overflow: Option>, /// Function that is called to perform style mutations to the floating element to change its size. - pub apply: &'a dyn Fn(ApplyState), + pub apply: Option<&'a ApplyFn>, } impl<'a, Element: Clone, Window: Clone> SizeOptions<'a, Element, Window> { - pub fn new(value: &'a dyn Fn(ApplyState)) -> Self { + pub fn new() -> Self { SizeOptions { detect_overflow: None, - apply: value, + apply: None, } } @@ -48,12 +51,21 @@ impl<'a, Element: Clone, Window: Clone> SizeOptions<'a, Element, Window> { } /// Set `apply` option. - pub fn apply(mut self, value: &'a dyn Fn(ApplyState)) -> Self { - self.apply = value; + pub fn apply(mut self, value: &'a ApplyFn) -> Self { + self.apply = Some(value); self } } +impl<'a, Element: Clone, Window: Clone> Default for SizeOptions<'a, Element, Window> { + fn default() -> Self { + Self { + detect_overflow: Default::default(), + apply: Default::default(), + } + } +} + /// Provides data that allows you to change the size of the floating element - /// for instance, prevent it from overflowing the clipping boundary or match the width of the reference element. /// @@ -195,14 +207,16 @@ impl<'a, Element: Clone, Window: Clone> Middleware for Size<'a, } } - (options.apply)(ApplyState { - state: MiddlewareState { - elements: elements.clone(), - ..state - }, - available_width, - available_height, - }); + if let Some(apply) = options.apply { + apply(ApplyState { + state: MiddlewareState { + elements: elements.clone(), + ..state + }, + available_width, + available_height, + }); + } let next_dimensions = platform.get_dimensions(elements.floating); diff --git a/packages/core/src/types.rs b/packages/core/src/types.rs index 9fa1c0b..8d317cb 100644 --- a/packages/core/src/types.rs +++ b/packages/core/src/types.rs @@ -5,7 +5,7 @@ use dyn_clone::DynClone; use serde::{de::DeserializeOwned, Serialize}; use floating_ui_utils::{ - ClientRectObject, Coords, Dimensions, ElementOrWindow, ElementRects, Length, + ClientRectObject, Coords, Dimensions, ElementOrVirtual, ElementOrWindow, ElementRects, Length, OwnedElementOrWindow, Placement, Rect, Strategy, }; @@ -49,8 +49,8 @@ impl<'a, Element, Window, T: Clone> From> } /// Arguments for [`Platform::get_element_rects`]. -pub struct GetElementRectsArgs<'a, Element> { - pub reference: &'a Element, +pub struct GetElementRectsArgs<'a, Element: Clone> { + pub reference: ElementOrVirtual<'a, Element>, pub floating: &'a Element, pub strategy: Strategy, } @@ -74,7 +74,7 @@ pub struct ConvertOffsetParentRelativeRectToViewportRelativeRectArgs<'a, Element /// Platform interface methods to work with the current platform. /// /// See for the original documentation. -pub trait Platform: Debug { +pub trait Platform: Debug { fn get_element_rects(&self, args: GetElementRectsArgs) -> ElementRects; fn get_clipping_rect(&self, args: GetClippingRectArgs) -> Rect; diff --git a/packages/dom/src/middleware.rs b/packages/dom/src/middleware.rs index 5b867ab..cee2b39 100644 --- a/packages/dom/src/middleware.rs +++ b/packages/dom/src/middleware.rs @@ -1,16 +1,16 @@ use floating_ui_core::middleware::{ Arrow as CoreArrow, AutoPlacement as CoreAutoPlacement, Flip as CoreFlip, Hide as CoreHide, - Offset as CoreOffset, Shift as CoreShift, Size as CoreSize, + Inline as CoreInline, Offset as CoreOffset, Shift as CoreShift, Size as CoreSize, }; use web_sys::{Element, Window}; pub use floating_ui_core::middleware::{ ApplyState, ArrowData, ArrowOptions, AutoPlacementData, AutoPlacementDataOverflow, AutoPlacementOptions, DefaultLimiter, FallbackStrategy, FlipData, FlipDataOverflow, - FlipOptions, HideData, HideOptions, HideStrategy, LimitShift, LimitShiftOffset, + FlipOptions, HideData, HideOptions, HideStrategy, InlineOptions, LimitShift, LimitShiftOffset, LimitShiftOffsetValues, LimitShiftOptions, OffsetData, OffsetOptions, OffsetOptionsValues, ShiftData, ShiftOptions, SizeOptions, ARROW_NAME, AUTO_PLACEMENT_NAME, FLIP_NAME, HIDE_NAME, - OFFSET_NAME, SHIFT_NAME, SIZE_NAME, + INLINE_NAME, OFFSET_NAME, SHIFT_NAME, SIZE_NAME, }; /// Provides data to position an inner element of the floating element so that it appears centered to the reference element. @@ -36,6 +36,11 @@ pub type Flip<'a> = CoreFlip<'a, Element, Window>; /// See for the original documentation. pub type Hide<'a> = CoreHide<'a, Element, Window>; +/// Provides improved positioning for inline reference elements that can span over multiple lines, such as hyperlinks or range selections. +/// +/// See for the original documentation. +pub type Inline<'a> = CoreInline<'a, Element, Window>; + /// Modifies the placement by translating the floating element along the specified axes. /// /// See for the original documentation. diff --git a/packages/dom/src/platform/get_element_rects.rs b/packages/dom/src/platform/get_element_rects.rs index 6ed9a7a..066de38 100644 --- a/packages/dom/src/platform/get_element_rects.rs +++ b/packages/dom/src/platform/get_element_rects.rs @@ -17,7 +17,7 @@ pub fn get_element_rects(platform: &Platform, args: GetElementRectsArgs ElementRects { reference: get_rect_relative_to_offset_parent( - args.reference.into(), + args.reference, offset_parent_ref.into(), args.strategy, ), diff --git a/packages/leptos/src/lib.rs b/packages/leptos/src/lib.rs index 6e974de..e992827 100644 --- a/packages/leptos/src/lib.rs +++ b/packages/leptos/src/lib.rs @@ -14,10 +14,10 @@ pub use floating_ui_dom::{ AutoUpdateOptions, Axis, ClientRectObject, ComputePositionConfig, ComputePositionReturn, Coords, DefaultLimiter, Derivable, DerivableFn, DetectOverflowOptions, Dimensions, ElementOrVirtual, ElementRects, FallbackStrategy, Flip, FlipData, FlipDataOverflow, - FlipOptions, Hide, HideData, HideOptions, HideStrategy, Length, LimitShift, LimitShiftOffset, - LimitShiftOffsetValues, LimitShiftOptions, Middleware, MiddlewareData, MiddlewareReturn, - MiddlewareState, MiddlewareVec, MiddlewareWithOptions, Offset, OffsetData, OffsetOptions, - OffsetOptionsValues, Padding, Placement, Rect, Shift, ShiftData, ShiftOptions, Side, Size, - SizeOptions, Strategy, VirtualElement, ARROW_NAME, AUTO_PLACEMENT_NAME, FLIP_NAME, HIDE_NAME, - OFFSET_NAME, SHIFT_NAME, SIZE_NAME, + FlipOptions, Hide, HideData, HideOptions, HideStrategy, Inline, InlineOptions, Length, + LimitShift, LimitShiftOffset, LimitShiftOffsetValues, LimitShiftOptions, Middleware, + MiddlewareData, MiddlewareReturn, MiddlewareState, MiddlewareVec, MiddlewareWithOptions, + Offset, OffsetData, OffsetOptions, OffsetOptionsValues, Padding, Placement, Rect, Shift, + ShiftData, ShiftOptions, Side, Size, SizeOptions, Strategy, VirtualElement, ARROW_NAME, + AUTO_PLACEMENT_NAME, FLIP_NAME, HIDE_NAME, INLINE_NAME, OFFSET_NAME, SHIFT_NAME, SIZE_NAME, }; diff --git a/packages/leptos/tests/visual/src/app.rs b/packages/leptos/tests/visual/src/app.rs index 180b418..b708f1d 100644 --- a/packages/leptos/tests/visual/src/app.rs +++ b/packages/leptos/tests/visual/src/app.rs @@ -9,6 +9,7 @@ use crate::spec::containing_block::ContainingBlock; use crate::spec::decimal_size::DecimalSize; use crate::spec::flip::Flip; use crate::spec::hide::Hide; +use crate::spec::inline::Inline; use crate::spec::offset::Offset; use crate::spec::placement::Placement; use crate::spec::relative::Relative; @@ -117,7 +118,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 d815e78..dde84a4 100644 --- a/packages/leptos/tests/visual/src/spec.rs +++ b/packages/leptos/tests/visual/src/spec.rs @@ -6,6 +6,7 @@ pub mod containing_block; pub mod decimal_size; pub mod flip; pub mod hide; +pub mod inline; pub mod offset; pub mod placement; pub mod relative; diff --git a/packages/leptos/tests/visual/src/spec/decimal_size.rs b/packages/leptos/tests/visual/src/spec/decimal_size.rs index e695f0b..b435aa4 100644 --- a/packages/leptos/tests/visual/src/spec/decimal_size.rs +++ b/packages/leptos/tests/visual/src/spec/decimal_size.rs @@ -16,7 +16,7 @@ pub fn DecimalSize() -> impl IntoView { let (size, set_size) = create_signal(INTEGER); let (truncate, set_truncate) = create_signal(false); - let middleware: MiddlewareVec = vec![Box::new(Size::new(SizeOptions::new( + let middleware: MiddlewareVec = vec![Box::new(Size::new(SizeOptions::default().apply( &|ApplyState { state, .. }| { let MiddlewareState { elements, rects, .. diff --git a/packages/leptos/tests/visual/src/spec/hide.rs b/packages/leptos/tests/visual/src/spec/hide.rs index f19be6b..a5687b1 100644 --- a/packages/leptos/tests/visual/src/spec/hide.rs +++ b/packages/leptos/tests/visual/src/spec/hide.rs @@ -54,7 +54,7 @@ pub fn Hide() -> impl IntoView { middleware.push(Box::new(Shift::new(ShiftOptions::default()))); } - middleware.push(Box::new(Size::new(SizeOptions::new( + middleware.push(Box::new(Size::new(SizeOptions::default().apply( match is_fixed_strategy() { true => &|ApplyState { state, diff --git a/packages/leptos/tests/visual/src/spec/inline.rs b/packages/leptos/tests/visual/src/spec/inline.rs new file mode 100644 index 0000000..9cad99e --- /dev/null +++ b/packages/leptos/tests/visual/src/spec/inline.rs @@ -0,0 +1,200 @@ +use convert_case::{Case, Casing}; +use floating_ui_leptos::{ + use_floating, Coords, Flip, FlipOptions, Inline, InlineOptions, MiddlewareVec, Placement, Size, + SizeOptions, UseFloatingOptions, UseFloatingReturn, +}; +use leptos::{ + ev::MouseEvent, + html::{Div, Span}, + *, +}; + +use crate::utils::all_placements::ALL_PLACEMENTS; + +#[derive(Copy, Clone, Debug, PartialEq)] +enum ConnectedStatus { + One, + TwoDisjoined, + TwoJoined, + Three, +} + +#[component] +pub fn Inline() -> impl IntoView { + let reference_ref = create_node_ref::(); + let floating_ref = create_node_ref::
(); + + let (placement, set_placement) = create_signal(Placement::Bottom); + let (status, set_status) = create_signal(ConnectedStatus::TwoDisjoined); + let (open, set_open) = create_signal(false); + let (mouse_coords, set_mouse_coords) = create_signal::>(None); + + let UseFloatingReturn { x, y, strategy, .. } = use_floating( + reference_ref, + floating_ref, + UseFloatingOptions::default() + .placement(placement.into()) + .while_elements_mounted_auto_update() + .middleware(MaybeProp::derive(move || { + let mut options = InlineOptions::default(); + if let Some(mouse_coords) = mouse_coords() { + options = options.coords(mouse_coords); + } + + let middleware: MiddlewareVec = vec![ + Box::new(Inline::new(options)), + Box::new(Flip::new(FlipOptions::default())), + Box::new(Size::new(SizeOptions::default())), + ]; + + Some(middleware) + })), + ); + + let text = move || { + match status() { + ConnectedStatus::One => "test", + ConnectedStatus::TwoDisjoined => "Nulla rutrum dapibus turpis eu volutpat", + ConnectedStatus::TwoJoined => "Nulla rutrum dapibus turpis eu volutpat. Duis cursus nisi massa, non dictum", + ConnectedStatus::Three => "Nulla rutrum dapibus turpis eu volutpat. Duis cursus nisi massa, non dictum turpis interdum at. Nulla rutrum dapibus turpis eu volutpat", + } + }; + + let handle_mouse_enter = move |event: MouseEvent| { + set_mouse_coords(Some(Coords { + x: event.client_x() as f64, + y: event.client_y() as f64, + })); + set_open(true); + }; + + let handle_mouse_leave = move |_: MouseEvent| { + set_mouse_coords(None); + set_open(false); + }; + + // TODO: effect + + view! { +

Inline

+

The floating element should choose the most appropriate rect.

+
+

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit.{' '} + + {text} + . 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{' '} + nunc quis faucibus egestas. + Duis cursus nisi massa, non dictum turpis interdum at. +

+ + // TODO: use this when refs can be re-mounted + // + //
+ // Floating + //
+ //
+ +
"block", + false => "none" + } + style:position=move || format!("{:?}", strategy()).to_lowercase() + style:top=move || format!("{}px", y()) + style:left=move || format!("{}px", x()) + style:pointer-events="none" + > + Floating +
+
+ +

Placement

+
+ "black", + false => "" + } + on:click=move |_| set_placement(local_placement) + > + {format!("{:?}", local_placement).to_case(Case::Kebab)} + + } + /> +
+ +

Open

+
+ "black", + false => "" + } + on:click=move |_| set_open(value) + > + {format!("{}", value)} + + } + /> +
+ +

Connected

+
+ "1", + ConnectedStatus::TwoDisjoined => "2-disjoined", + ConnectedStatus::TwoJoined => "2-joined", + ConnectedStatus::Three => "3", + }) + style:background-color=move || match status() == value { + true => "black", + false => "" + } + on:click=move |_| set_status(value) + > + {match value { + ConnectedStatus::One => "1", + ConnectedStatus::TwoDisjoined => "2-disjoined", + ConnectedStatus::TwoJoined => "2-joined", + ConnectedStatus::Three => "3", + }} + + } + /> +
+ } +} diff --git a/packages/leptos/tests/visual/src/spec/size.rs b/packages/leptos/tests/visual/src/spec/size.rs index b6b31b2..cc090e8 100644 --- a/packages/leptos/tests/visual/src/spec/size.rs +++ b/packages/leptos/tests/visual/src/spec/size.rs @@ -55,30 +55,31 @@ pub fn Size() -> impl IntoView { } middleware.push(Box::new(Size::new( - SizeOptions::new(&|ApplyState { - state, - available_width, - available_height, - }: ApplyState< - web_sys::Element, - web_sys::Window, - >| { - let MiddlewareState { elements, .. } = state; - - let floating = (*elements.floating) - .clone() - .unchecked_into::(); - - floating - .style() - .set_property("max-width", &format!("{}px", available_width)) - .expect("Style should be updated."); - floating - .style() - .set_property("max-height", &format!("{}px", available_height)) - .expect("Style should be updated."); - }) - .detect_overflow(detect_overflow_options.clone()), + SizeOptions::default() + .apply(&|ApplyState { + state, + available_width, + available_height, + }: ApplyState< + web_sys::Element, + web_sys::Window, + >| { + let MiddlewareState { elements, .. } = state; + + let floating = (*elements.floating) + .clone() + .unchecked_into::(); + + floating + .style() + .set_property("max-width", &format!("{}px", available_width)) + .expect("Style should be updated."); + floating + .style() + .set_property("max-height", &format!("{}px", available_height)) + .expect("Style should be updated."); + }) + .detect_overflow(detect_overflow_options.clone()), ))); if add_flip_shift() && has_edge_alignment() { diff --git a/packages/utils/src/lib.rs b/packages/utils/src/lib.rs index 3b43307..1705c0c 100644 --- a/packages/utils/src/lib.rs +++ b/packages/utils/src/lib.rs @@ -260,6 +260,68 @@ pub trait VirtualElement: DynClone { dyn_clone::clone_trait_object!( VirtualElement); +pub trait GetBoundingClientRectCloneable: DynClone { + fn call(&self) -> ClientRectObject; +} + +impl GetBoundingClientRectCloneable for F +where + F: Fn() -> ClientRectObject + Clone, +{ + fn call(&self) -> ClientRectObject { + self() + } +} + +dyn_clone::clone_trait_object!(GetBoundingClientRectCloneable); + +#[derive(Clone)] +pub struct DefaultVirtualElement { + pub get_bounding_client_rect: Box, + pub context_element: Option, +} + +impl DefaultVirtualElement { + pub fn new(get_bounding_client_rect: Box) -> Self { + DefaultVirtualElement { + get_bounding_client_rect, + context_element: None, + } + } + + pub fn get_bounding_client_rect( + mut self, + get_bounding_client_rect: Box, + ) -> Self { + self.get_bounding_client_rect = get_bounding_client_rect; + self + } + + pub fn context_element(mut self, context_element: Element) -> Self { + self.context_element = Some(context_element); + self + } +} + +// impl Clone for DefaultVirtualElement { +// fn clone(&self) -> Self { +// Self { +// get_bounding_client_rect: dyn_clone::clone_box(&*self.get_bounding_client_rect), +// context_element: self.context_element.clone(), +// } +// } +// } + +impl VirtualElement for DefaultVirtualElement { + fn get_bounding_client_rect(&self) -> ClientRectObject { + (self.get_bounding_client_rect).call() + } + + fn context_element(&self) -> Option { + self.context_element.clone() + } +} + #[derive(Clone)] pub enum ElementOrVirtual<'a, Element: Clone> { Element(&'a Element), @@ -281,6 +343,12 @@ impl<'a, Element: Clone> From<&'a Element> for ElementOrVirtual<'a, Element> { } } +impl<'a, Element: Clone> From>> for ElementOrVirtual<'a, Element> { + fn from(value: Box>) -> Self { + ElementOrVirtual::VirtualElement(value) + } +} + impl<'a, Element: Clone> From<&'a OwnedElementOrVirtual> for ElementOrVirtual<'a, Element> {