Skip to content

Commit

Permalink
Add start of Leptos implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
DanielleHuisman committed Mar 25, 2024
1 parent 5f7aac2 commit d9ec770
Show file tree
Hide file tree
Showing 13 changed files with 312 additions and 52 deletions.
3 changes: 3 additions & 0 deletions packages/leptos/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ repository.workspace = true
version.workspace = true

[dependencies]
cfg-if = "1.0.0"
# TODO: remove
log = "0.4.21"
floating-ui-dom = { path = "../dom", version = "0.0.2" }
leptos = { version = "0.6.9", features = ["nightly"] }
web-sys.workspace = true
19 changes: 19 additions & 0 deletions packages/leptos/example/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[package]
name = "floating-ui-leptos-example"
description = "Example for Floating UI Leptos."
publish = false

authors.workspace = true
edition.workspace = true
license.workspace = true
repository.workspace = true
version.workspace = true

[dependencies]
console_log = "1.0.0"
console_error_panic_hook = "0.1.7"
leptos = { version = "0.6.9", features = ["csr", "nightly"] }
log = "0.4.21"
floating-ui-leptos = { path = ".." }
wasm-bindgen.workspace = true
web-sys.workspace = true
5 changes: 5 additions & 0 deletions packages/leptos/example/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<!doctype html>
<html>
<head></head>
<body></body>
</html>
22 changes: 22 additions & 0 deletions packages/leptos/example/src/app.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
use floating_ui_leptos::{use_floating, UseFloatingOptions, UseFloatingReturn};
use leptos::{
html::{Div, Span},
*,
};

#[component]
pub fn App() -> impl IntoView {
let reference = create_node_ref::<Span>();
let floating = create_node_ref::<Div>();

let UseFloatingReturn {
floating_styles, ..
} = use_floating(reference, floating, UseFloatingOptions::default().into());

view! {
<span _ref=reference>Reference</span>
<div _ref=floating style=floating_styles>
Floating
</div>
}
}
13 changes: 13 additions & 0 deletions packages/leptos/example/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// TODO: remove after leptos is fixed
#![allow(clippy::empty_docs)]

mod app;

use crate::app::App;

pub fn main() {
_ = console_log::init_with_level(log::Level::Debug);
console_error_panic_hook::set_once();

leptos::mount_to_body(App);
}
3 changes: 3 additions & 0 deletions packages/leptos/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
mod types;
mod use_floating;
mod utils;

pub use types::*;
pub use use_floating::*;
97 changes: 97 additions & 0 deletions packages/leptos/src/types.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
use floating_ui_dom::{Middleware, MiddlewareData, Placement, Strategy};
use leptos::{Attribute, IntoAttribute, Signal};
use web_sys::{Element, Window};

/// Options for [`use_floating`].
#[derive(Clone, Default)]
pub struct UseFloatingOptions<'a> {
/// Represents the open/close state of the floating element.
///
/// Defaults to `true`.
pub open: Option<bool>,

/// Where to place the floating element relative to the reference element.
///
/// Defaults to [`Placement::Bottom`].
pub placement: Option<Placement>,

/// The strategy to use when positioning the floating element.
///
/// Defaults to [`Strategy::Absolute`].
pub strategy: Option<Strategy>,

/// Array of middleware objects to modify the positioning or provide data for rendering.
///
/// Defaults to an empty vector.
pub middleware: Option<Vec<&'a dyn Middleware<Element, Window>>>,

/// Whether to use `transform` for positioning instead of `top` and `left` in the `floatingStyles` object.
///
/// Defaults to `true`.
pub transform: Option<bool>,

/// Callback to handle mounting/unmounting of the elements.
///
///Detauls to [`Option::None`].
pub while_elements_mounted: Option<bool>, // TODO: type
}

#[derive(Clone, Debug, PartialEq)]
pub struct FloatingStyles {
pub position: Strategy,
pub top: String,
pub left: String,
pub transform: Option<String>,
pub will_change: Option<String>,
}

impl IntoAttribute for FloatingStyles {
fn into_attribute(self) -> Attribute {
Attribute::String(
format!(
"position: {:?}; top: {}; left: {};{}{}",
self.position,
self.top,
self.left,
self.transform
.map_or("".into(), |transform| format!(" transform: {};", transform),),
self.will_change.map_or("".into(), |will_change| format!(
" will-change: {};",
will_change
))
)
.into(),
)
}

fn into_attribute_boxed(self: Box<Self>) -> Attribute {
self.into_attribute()
}
}

#[derive(Debug)]
pub struct UseFloatingReturn {
/// The x-coord of the floating element.
pub x: Signal<f64>,

/// The y-coord of the floating element.
pub y: Signal<f64>,

/// The stateful placement, which can be different from the initial `placement` passed as options.
pub placement: Signal<Placement>,

/// The strategy to use when positioning the floating element.
pub strategy: Signal<Strategy>,

/// Additional data from middleware.
pub middleware_data: Signal<MiddlewareData>,

/// Indicates if the floating element has been positioned.
pub is_positioned: Signal<bool>,

/// CSS styles to apply to the floating element to position it.
pub floating_styles: Signal<FloatingStyles>,

/// The function to update floating position manually.
pub update: bool, // TODO: type
}
181 changes: 129 additions & 52 deletions packages/leptos/src/use_floating.rs
Original file line number Diff line number Diff line change
@@ -1,60 +1,137 @@
use floating_ui_dom::{Middleware, MiddlewareData, Placement, Strategy};
use leptos::{create_signal, MaybeSignal};
use web_sys::{Element, Window};

/// Options for [`use_floating`].
#[derive(Clone, Default)]
pub struct UseFloatingOptions<'a> {
/// Where to place the floating element relative to the reference element.
///
/// Defaults to [`Placement::Bottom`].
pub placement: Option<Placement>,

/// The strategy to use when positioning the floating element.
///
/// Defaults to [`Strategy::Absolute`].
pub strategy: Option<Strategy>,

/// Array of middleware objects to modify the positioning or provide data for rendering.
///
/// Defaults to an empty vector.
pub middleware: Option<Vec<&'a dyn Middleware<Element, Window>>>,
}
use std::ops::Deref;

/// Data stored by [`use_floating`].
pub struct UseFloatingData {
pub x: f64,
pub y: f64,
pub strategy: Strategy,
pub placement: Placement,
pub middleware_data: MiddlewareData,
pub is_positioned: bool,
}
use floating_ui_dom::{
compute_position, ComputePositionConfig, MiddlewareData, Placement, Strategy,
};
use leptos::{create_memo, create_signal, html::ElementDescriptor, MaybeSignal, NodeRef, Signal};
use log::info;

use crate::{
types::{FloatingStyles, UseFloatingOptions, UseFloatingReturn},
utils::{get_dpr::get_dpr, round_by_dpr::round_by_dpr},
};

pub fn use_floating<Reference, Floating, ReferenceEl, FloatingEl>(
reference: NodeRef<Reference>,
floating: NodeRef<Floating>,
options: MaybeSignal<UseFloatingOptions>,
) -> UseFloatingReturn
where
Reference: ElementDescriptor + Deref<Target = ReferenceEl> + Clone + 'static,
ReferenceEl: Deref<Target = web_sys::HtmlElement>,
Floating: ElementDescriptor + Deref<Target = FloatingEl> + Clone + 'static,
FloatingEl: Deref<Target = web_sys::HtmlElement>,
{
let options = Signal::derive(options);

let open_option = move || options().open.unwrap_or(true);
let placement_option = move || options().placement.unwrap_or(Placement::Bottom);
let strategy_option = move || options().strategy.unwrap_or(Strategy::Absolute);
let middleware_option = move || options().middleware;
let transform_option = move || options().transform.unwrap_or(true);

let (x, set_x) = create_signal(0.0);
let (y, set_y) = create_signal(0.0);
let (strategy, set_strategy) = create_signal(strategy_option());
let (placement, set_placement) = create_signal(placement_option());
let (middleware_data, set_middleware_data) = create_signal(MiddlewareData::default());
let (is_positioned, set_is_positioned) = create_signal(false);
let floating_styles = create_memo(move |_| {
let initial_styles = FloatingStyles {
position: strategy(),
top: "0".into(),
left: "0".into(),
transform: None,
will_change: None,
};

info!("floating styles memo");

if let Some(floating_element) = floating.get() {
info!("floating element exists");

pub fn use_floating(options: MaybeSignal<UseFloatingOptions>) {
// let placement = create_memo(move |_| {
// options.with(|options| options.placement.unwrap_or(Placement::Bottom))
// });

// let strategy = create_memo(move |_| {
// options.with(|options| options.strategy.unwrap_or(Strategy::Absolute))
// });

let (_data, _set_data) = create_signal(UseFloatingData {
x: 0.0,
y: 0.0,
strategy: options().strategy.unwrap_or(Strategy::Absolute),
placement: options().placement.unwrap_or(Placement::Bottom),
middleware_data: MiddlewareData::default(),
is_positioned: false,
let x_val = round_by_dpr(&floating_element, x());
let y_val = round_by_dpr(&floating_element, y());

if transform_option() {
FloatingStyles {
transform: Some(format!("translate({x_val}px, {y_val}px)")),
will_change: match get_dpr(&floating_element) >= 1.5 {
true => Some("transform".into()),
false => None,
},
..initial_styles
}
} else {
FloatingStyles {
left: format!("{x_val}px"),
top: format!("{y_val}px"),
..initial_styles
}
}
} else {
initial_styles
}
});

// let (_latest_middleware, _set_latest_middleware) = create_signal(options.middleware);
let update = move || {
info!("update");
if let Some(reference_element) = reference.get() {
info!("ref");
if let Some(floating_element) = floating.get() {
info!("float");
let config = ComputePositionConfig {
placement: Some(placement_option()),
strategy: Some(strategy_option()),
middleware: middleware_option(),
};

let position =
compute_position(&reference_element, &floating_element, Some(config));
set_x(position.x);
set_y(position.x);
set_strategy(position.strategy);
set_placement(position.placement);
set_middleware_data(position.middleware_data);
set_is_positioned(true);
}
}
};

let cleanup = move || {
// TODO
};

let attach = move || {
cleanup();

// TODO: the rest of the function
update();
};

let reset = move || {
if !open_option() {
set_is_positioned(false);
}
};

// TODO: call attach/reset

// // TODO: compare latest_middleware and options.middleware and update it
create_memo(move |_| {
info!("{} {}", reference.get().is_some(), floating.get().is_some());

// let (_reference, _set_reference) = create_signal::<Option<Element>>(None);
// let (_floating, _set_floating) = create_signal::<Option<Element>>(None);
info!("memo");
attach()
})();

// // TODO: setReference and setFloating
UseFloatingReturn {
x: x.into(),
y: y.into(),
placement: placement.into(),
strategy: strategy.into(),
middleware_data: middleware_data.into(),
is_positioned: is_positioned.into(),
floating_styles: floating_styles.into(),
update: false, // TODO
}
}
2 changes: 2 additions & 0 deletions packages/leptos/src/utils.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pub mod get_dpr;
pub mod round_by_dpr;
6 changes: 6 additions & 0 deletions packages/leptos/src/utils/get_dpr.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
use floating_ui_dom::dom::get_window;
use web_sys::Element;

pub fn get_dpr(element: &Element) -> f64 {
get_window(Some(element)).device_pixel_ratio()
}
8 changes: 8 additions & 0 deletions packages/leptos/src/utils/round_by_dpr.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
use web_sys::Element;

use crate::utils::get_dpr::get_dpr;

pub fn round_by_dpr(element: &Element, value: f64) -> f64 {
let dpr = get_dpr(element);
(value * dpr).round() / dpr
}
3 changes: 3 additions & 0 deletions packages/utils/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,6 @@ web-sys = { workspace = true, optional = true }
[features]
default = []
dom = ["dep:web-sys"]

[package.metadata.docs.rs]
all-features = true
2 changes: 2 additions & 0 deletions packages/utils/src/dom.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
//! Utility functions for the DOM. Requires `dom` feature.
use web_sys::{
css, wasm_bindgen::JsCast, window, CssStyleDeclaration, Document, Element, HtmlElement, Node,
ShadowRoot, Window,
Expand Down

0 comments on commit d9ec770

Please sign in to comment.