use std::{fmt::Display, rc::Rc};
use floating_ui_dom::{
AutoUpdateOptions, ElementOrVirtual, Middleware, MiddlewareData, Placement, Strategy,
auto_update,
};
use leptos::{prelude::*, tachys::html::style::IntoStyle};
use send_wrapper::SendWrapper;
use web_sys::{Element, Window};
pub type WhileElementsMountedFn =
dyn Fn(ElementOrVirtual, &Element, Rc<dyn Fn()>) -> WhileElementsMountedCleanupFn;
pub type WhileElementsMountedCleanupFn = Box<dyn Fn()>;
pub type WrappedMiddleware = SendWrapper<Vec<Box<dyn Middleware<Element, Window>>>>;
#[derive(Clone, Default)]
pub struct UseFloatingOptions {
pub open: MaybeProp<bool>,
pub placement: MaybeProp<Placement>,
pub strategy: MaybeProp<Strategy>,
pub middleware: MaybeProp<WrappedMiddleware>,
pub transform: MaybeProp<bool>,
pub while_elements_mounted: MaybeProp<SendWrapper<Rc<WhileElementsMountedFn>>>,
}
impl UseFloatingOptions {
pub fn open<I: Into<MaybeProp<bool>>>(mut self, value: I) -> Self {
self.open = value.into();
self
}
pub fn placement<I: Into<MaybeProp<Placement>>>(mut self, value: I) -> Self {
self.placement = value.into();
self
}
pub fn strategy<I: Into<MaybeProp<Strategy>>>(mut self, value: I) -> Self {
self.strategy = value.into();
self
}
pub fn middleware<I: Into<MaybeProp<WrappedMiddleware>>>(mut self, value: I) -> Self {
self.middleware = value.into();
self
}
pub fn transform<I: Into<MaybeProp<bool>>>(mut self, value: I) -> Self {
self.transform = value.into();
self
}
pub fn while_elements_mounted<I: Into<MaybeProp<SendWrapper<Rc<WhileElementsMountedFn>>>>>(
mut self,
value: I,
) -> Self {
self.while_elements_mounted = value.into();
self
}
pub fn while_elements_mounted_auto_update(self) -> Self {
let auto_update_rc: SendWrapper<Rc<WhileElementsMountedFn>> =
SendWrapper::new(Rc::new(|reference, floating, update| {
auto_update(reference, floating, update, AutoUpdateOptions::default())
}));
self.while_elements_mounted(auto_update_rc)
}
pub fn while_elements_mounted_auto_update_with_enabled(self, enabled: Signal<bool>) -> Self {
let auto_update_rc: SendWrapper<Rc<WhileElementsMountedFn>> =
SendWrapper::new(Rc::new(|reference, floating, update| {
auto_update(reference, floating, update, AutoUpdateOptions::default())
}));
self.while_elements_mounted(MaybeProp::derive(move || {
if enabled.get() {
Some(auto_update_rc.clone())
} else {
None
}
}))
}
pub fn while_elements_mounted_auto_update_with_options(
self,
options: Signal<AutoUpdateOptions>,
) -> Self {
let auto_update_rc =
move |options: AutoUpdateOptions| -> SendWrapper<Rc<WhileElementsMountedFn>> {
SendWrapper::new(Rc::new(move |reference, floating, update| {
auto_update(reference, floating, update, options.clone())
}))
};
self.while_elements_mounted(MaybeProp::derive(move || {
Some(auto_update_rc(options.get()))
}))
}
pub fn while_elements_mounted_auto_update_with_enabled_and_options(
self,
enabled: Signal<bool>,
options: Signal<AutoUpdateOptions>,
) -> Self {
let auto_update_rc =
move |options: AutoUpdateOptions| -> SendWrapper<Rc<WhileElementsMountedFn>> {
SendWrapper::new(Rc::new(move |reference, floating, update| {
auto_update(reference, floating, update, options.clone())
}))
};
self.while_elements_mounted(MaybeProp::derive(move || {
if enabled.get() {
Some(auto_update_rc(options.get()))
} else {
None
}
}))
}
}
#[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 FloatingStyles {
pub fn style_position(&self) -> String {
match self.position {
Strategy::Absolute => "absolute".to_owned(),
Strategy::Fixed => "fixed".to_owned(),
}
}
pub fn style_top(&self) -> String {
self.top.clone()
}
pub fn style_left(&self) -> String {
self.left.clone()
}
pub fn style_transform(&self) -> Option<String> {
self.transform.clone()
}
pub fn style_will_change(&self) -> Option<String> {
self.will_change.clone()
}
}
impl Display for FloatingStyles {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"position: {}; top: {}; left: {};{}{}",
match self.position {
Strategy::Absolute => "absolute",
Strategy::Fixed => "fixed",
},
self.top,
self.left,
self.transform
.as_ref()
.map_or("".to_owned(), |transform| format!(
" transform: {transform};"
),),
self.will_change
.as_ref()
.map_or("".to_owned(), |will_change| format!(
" will-change: {will_change};"
))
)
}
}
impl IntoStyle for FloatingStyles {
type AsyncOutput = Self;
type State = (leptos::tachys::renderer::types::Element, Self);
type Cloneable = Self;
type CloneableOwned = Self;
fn to_html(self, style: &mut String) {
style.push_str(&self.to_string());
}
fn hydrate<const FROM_SERVER: bool>(
self,
el: &leptos::tachys::renderer::types::Element,
) -> Self::State {
(el.clone(), self)
}
fn build(self, el: &leptos::tachys::renderer::types::Element) -> Self::State {
leptos::tachys::renderer::Rndr::set_attribute(el, "style", &self.to_string());
(el.clone(), self)
}
fn rebuild(self, state: &mut Self::State) {
let (el, prev) = state;
if self != *prev {
leptos::tachys::renderer::Rndr::set_attribute(el, "style", &self.to_string());
}
*prev = self;
}
fn into_cloneable(self) -> Self::Cloneable {
self
}
fn into_cloneable_owned(self) -> Self::CloneableOwned {
self
}
fn dry_resolve(&mut self) {}
async fn resolve(self) -> Self::AsyncOutput {
self
}
fn reset(state: &mut Self::State) {
let (el, _prev) = state;
leptos::tachys::renderer::Rndr::remove_attribute(el, "style");
}
}
pub struct UseFloatingReturn {
pub x: Signal<f64>,
pub y: Signal<f64>,
pub placement: Signal<Placement>,
pub strategy: Signal<Strategy>,
pub middleware_data: Signal<MiddlewareData>,
pub is_positioned: Signal<bool>,
pub floating_styles: Signal<FloatingStyles>,
pub update: SendWrapper<Rc<dyn Fn()>>,
}