Skip to content

Commit

Permalink
Add size middleware
Browse files Browse the repository at this point in the history
  • Loading branch information
DanielleHuisman committed Apr 6, 2024
1 parent db81054 commit fc5651d
Show file tree
Hide file tree
Showing 6 changed files with 284 additions and 20 deletions.
2 changes: 2 additions & 0 deletions packages/core/src/middleware.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@ mod flip;
mod hide;
mod offset;
mod shift;
mod size;

pub use arrow::*;
pub use auto_placement::*;
pub use flip::*;
pub use hide::*;
pub use offset::*;
pub use shift::*;
pub use size::*;
237 changes: 237 additions & 0 deletions packages/core/src/middleware/size.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
use floating_ui_utils::{get_side_axis, Alignment, Axis, Rect, Side};

use crate::{
detect_overflow::{detect_overflow, DetectOverflowOptions},
types::{
Derivable, DerivableFn, Middleware, MiddlewareReturn, MiddlewareState,
MiddlewareWithOptions,
},
ResetRects, ResetValue,
};

use super::SHIFT_NAME;

/// Name of the [`Size`] middleware.
pub const SIZE_NAME: &str = "size";

#[derive(Clone)]
pub struct ApplyState<'a, Element: Clone, Window: Clone> {
pub state: MiddlewareState<'a, Element, Window>,
pub available_width: f64,
pub available_height: f64,
}

/// Options for [`Size`] middleware.
#[derive(Clone)]
pub struct SizeOptions<'a, Element: Clone, Window: Clone> {
/// Options for [`detect_overflow`].
///
/// Defaults to [`DetectOverflowOptions::default`].
pub detect_overflow: Option<DetectOverflowOptions<Element>>,

/// Function that is called to perform style mutations to the floating element to change its size.
pub apply: &'a dyn Fn(ApplyState<Element, Window>),
}

impl<'a, Element: Clone, Window: Clone> SizeOptions<'a, Element, Window> {
pub fn new(value: &'a dyn Fn(ApplyState<Element, Window>)) -> Self {
SizeOptions {
detect_overflow: None,
apply: value,
}
}

/// Set `detect_overflow` option.
pub fn detect_overflow(mut self, value: DetectOverflowOptions<Element>) -> Self {
self.detect_overflow = Some(value);
self
}

/// Set `apply` option.
pub fn apply(mut self, value: &'a dyn Fn(ApplyState<Element, Window>)) -> Self {
self.apply = value;
self
}
}

/// 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.
///
/// See <https://floating-ui.com/docs/size> for the original documentation.
pub struct Size<'a, Element: Clone, Window: Clone> {
options: Derivable<'a, Element, Window, SizeOptions<'a, Element, Window>>,
}

impl<'a, Element: Clone, Window: Clone> Size<'a, Element, Window> {
/// Constructs a new instance of this middleware.
pub fn new(options: SizeOptions<'a, Element, Window>) -> Self {
Size {
options: options.into(),
}
}

/// Constructs a new instance of this middleware with derivable options.
pub fn new_derivable(
options: Derivable<'a, Element, Window, SizeOptions<'a, Element, Window>>,
) -> Self {
Size { options }
}

/// Constructs a new instance of this middleware with derivable options function.
pub fn new_derivable_fn(
options: DerivableFn<'a, Element, Window, SizeOptions<'a, Element, Window>>,
) -> Self {
Size {
options: options.into(),
}
}
}

impl<'a, Element: Clone, Window: Clone> Clone for Size<'a, Element, Window> {
fn clone(&self) -> Self {
Self {
options: self.options.clone(),
}
}
}

impl<'a, Element: Clone, Window: Clone> Middleware<Element, Window> for Size<'a, Element, Window> {
fn name(&self) -> &'static str {
SIZE_NAME
}

fn compute(&self, state: MiddlewareState<Element, Window>) -> MiddlewareReturn {
let options = self.options.evaluate(state.clone());

let MiddlewareState {
placement,
elements,
rects,
platform,
..
} = state;

let overflow = detect_overflow(
MiddlewareState {
elements: elements.clone(),
..state
},
options.detect_overflow.unwrap_or_default(),
);
let side = placement.side();
let alignment = placement.alignment();
let is_y_axis = get_side_axis(placement) == Axis::Y;
let Rect { width, height, .. } = rects.floating;

let height_side;
let width_side;

match side {
Side::Top | Side::Bottom => {
height_side = side;
width_side = match alignment {
Some(alignment) => match alignment
== match platform.is_rtl(elements.floating) {
Some(true) => Alignment::Start,
_ => Alignment::End,
} {
true => Side::Left,
false => Side::Right,
},
None => Side::Right,
};
}
Side::Right | Side::Left => {
width_side = side;
height_side = match alignment {
Some(Alignment::End) => Side::Top,
_ => Side::Bottom,
};
}
}

let overflow_available_height = height - overflow.side(height_side);
let overflow_available_width = width - overflow.side(width_side);

let no_shift = state.middleware_data.get(SHIFT_NAME).is_none();

let mut available_height = overflow_available_height;
let mut available_width = overflow_available_width;

if is_y_axis {
let maximum_clipping_width = width - overflow.left - overflow.right;
available_width = match alignment.is_some() || no_shift {
true => overflow_available_width.min(maximum_clipping_width),
false => maximum_clipping_width,
};
} else {
let maximum_clipping_height = height - overflow.top - overflow.bottom;
available_height = match alignment.is_some() || no_shift {
true => overflow_available_height.min(maximum_clipping_height),
false => maximum_clipping_height,
}
}

if no_shift && alignment.is_none() {
let x_min = overflow.left.max(0.0);
let x_max = overflow.right.max(0.0);
let y_min = overflow.top.max(0.0);
let y_max = overflow.bottom.max(0.0);

if is_y_axis {
available_width = width
- 2.0
* (match x_min != 0.0 || x_max != 0.0 {
true => x_min + x_max,
false => overflow.left.max(overflow.right),
});
} else {
available_height = height
- 2.0
* (match y_min != 0.0 || y_max != 0.0 {
true => y_min + y_max,
false => overflow.top.max(overflow.bottom),
});
}
}

(options.apply)(ApplyState {
state: MiddlewareState {
elements: elements.clone(),
..state
},
available_width,
available_height,
});

let next_dimensions = platform.get_dimensions(elements.floating);

if width != next_dimensions.width || height != next_dimensions.height {
MiddlewareReturn {
x: None,
y: None,
data: None,
reset: Some(crate::Reset::Value(ResetValue {
placement: None,
rects: Some(ResetRects::True),
})),
}
} else {
MiddlewareReturn {
x: None,
y: None,
data: None,
reset: None,
}
}
}
}

impl<'a, Element: Clone, Window: Clone>
MiddlewareWithOptions<Element, Window, SizeOptions<'a, Element, Window>>
for Size<'a, Element, Window>
{
fn options(&self) -> &Derivable<Element, Window, SizeOptions<'a, Element, Window>> {
&self.options
}
}
18 changes: 12 additions & 6 deletions packages/dom/src/middleware.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
use floating_ui_core::middleware::{
Arrow as CoreArrow, AutoPlacement as CoreAutoPlacement, Flip as CoreFlip, Hide as CoreHide,
Offset as CoreOffset, Shift as CoreShift,
Offset as CoreOffset, Shift as CoreShift, Size as CoreSize,
};
use web_sys::{Element, Window};

pub use floating_ui_core::middleware::{
ArrowData, ArrowOptions, AutoPlacementData, AutoPlacementDataOverflow, AutoPlacementOptions,
DefaultLimiter, FlipData, FlipDataOverflow, FlipOptions, LimitShift, LimitShiftOffset,
LimitShiftOffsetValues, LimitShiftOptions, OffsetData, OffsetOptions, OffsetOptionsValues,
ShiftData, ShiftOptions, ARROW_NAME, AUTO_PLACEMENT_NAME, FLIP_NAME, HIDE_NAME, OFFSET_NAME,
SHIFT_NAME,
ApplyState, ArrowData, ArrowOptions, AutoPlacementData, AutoPlacementDataOverflow,
AutoPlacementOptions, DefaultLimiter, FlipData, FlipDataOverflow, FlipOptions, 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,
};

/// Provides data to position an inner element of the floating element so that it appears centered to the reference element.
Expand Down Expand Up @@ -44,3 +44,9 @@ pub type Offset<'a> = CoreOffset<'a, Element, Window>;
///
/// See <https://floating-ui.com/docs/shift> for the original documentation.
pub type Shift<'a> = CoreShift<'a, Element, Window>;

/// 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.
///
/// See <https://floating-ui.com/docs/size> for the original documentation.
pub type Size<'a> = CoreSize<'a, Element, Window>;
2 changes: 1 addition & 1 deletion packages/dom/src/utils/get_css_dimensions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ pub fn get_css_dimensions(element: &Element) -> CssDimensions {
.parse::<f64>()
.unwrap_or(0.0);
let height = css
.get_property_value("width")
.get_property_value("height")
.expect("Computed style should have height.")
.replace("px", "")
.parse::<f64>()
Expand Down
19 changes: 10 additions & 9 deletions packages/leptos/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,14 @@ pub use use_floating::*;

#[doc(no_inline)]
pub use floating_ui_dom::{
auto_update, compute_position, dom, AlignedPlacement, Alignment, ArrowData, AutoPlacement,
AutoPlacementData, AutoPlacementDataOverflow, AutoPlacementOptions, AutoUpdateOptions, Axis,
ClientRectObject, ComputePositionConfig, ComputePositionReturn, Coords, DefaultLimiter,
Derivable, DerivableFn, DetectOverflowOptions, Dimensions, ElementOrVirtual, ElementRects,
Flip, FlipData, FlipDataOverflow, FlipOptions, Length, LimitShift, LimitShiftOffset,
LimitShiftOffsetValues, LimitShiftOptions, Middleware, MiddlewareData, MiddlewareReturn,
MiddlewareState, MiddlewareVec, MiddlewareWithOptions, Offset, OffsetData, OffsetOptions,
OffsetOptionsValues, Padding, Placement, Rect, Shift, ShiftData, ShiftOptions, Side, Strategy,
VirtualElement, ARROW_NAME, AUTO_PLACEMENT_NAME, FLIP_NAME, HIDE_NAME, OFFSET_NAME, SHIFT_NAME,
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, Flip, FlipData, FlipDataOverflow, FlipOptions, 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,
};
26 changes: 22 additions & 4 deletions packages/leptos/tests/visual/src/spec/decimal_size.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
use floating_ui_leptos::{use_floating, MiddlewareVec, UseFloatingOptions, UseFloatingReturn};
use floating_ui_leptos::{
use_floating, ApplyState, MiddlewareState, MiddlewareVec, Size, SizeOptions,
UseFloatingOptions, UseFloatingReturn,
};
use leptos::{html::Div, *};
use wasm_bindgen::JsCast;

const SIZES: [f64; 4] = [0.0, 0.25, 0.5, 0.75];
const INTEGER: f64 = 80.0;
Expand All @@ -12,8 +16,22 @@ pub fn DecimalSize() -> impl IntoView {
let (size, set_size) = create_signal(INTEGER);
let (truncate, set_truncate) = create_signal(false);

// TODO: add size middleware
let middleware: MiddlewareVec = vec![];
let middleware: MiddlewareVec = vec![Box::new(Size::new(SizeOptions::new(
&|ApplyState { state, .. }| {
let MiddlewareState {
elements, rects, ..
} = state;

let floating = (*elements.floating)
.clone()
.unchecked_into::<web_sys::HtmlElement>();

floating
.style()
.set_property("width", &format!("{}px", rects.floating.width))
.expect("Style should be updated.");
},
)))];

let UseFloatingReturn {
x,
Expand All @@ -31,7 +49,7 @@ pub fn DecimalSize() -> impl IntoView {
let truncate_update = update.clone();

view! {
<h1>Decimal size</h1>
<h1>Decimal Size</h1>
<p>
The floating element should be positioned correctly on the bottom when
the reference and floating elements have a non-integer size (width/height).
Expand Down

0 comments on commit fc5651d

Please sign in to comment.