1use std::{fmt::Display, rc::Rc};
2
3use floating_ui_dom::{
4 AutoUpdateOptions, ElementOrVirtual, Middleware, MiddlewareData, Placement, Strategy,
5 auto_update,
6};
7use leptos::{prelude::*, tachys::html::style::IntoStyle};
8use send_wrapper::SendWrapper;
9use web_sys::{Element, Window};
10
11pub type WhileElementsMountedFn =
12 dyn Fn(ElementOrVirtual, &Element, Rc<dyn Fn()>) -> WhileElementsMountedCleanupFn;
13
14pub type WhileElementsMountedCleanupFn = Box<dyn Fn()>;
15
16pub type WrappedMiddleware = SendWrapper<Vec<Box<dyn Middleware<Element, Window>>>>;
17
18#[derive(Clone, Default)]
20pub struct UseFloatingOptions {
21 pub open: MaybeProp<bool>,
25
26 pub placement: MaybeProp<Placement>,
30
31 pub strategy: MaybeProp<Strategy>,
35
36 pub middleware: MaybeProp<WrappedMiddleware>,
40
41 pub transform: MaybeProp<bool>,
45
46 pub while_elements_mounted: MaybeProp<SendWrapper<Rc<WhileElementsMountedFn>>>,
50}
51
52impl UseFloatingOptions {
53 pub fn open<I: Into<MaybeProp<bool>>>(mut self, value: I) -> Self {
55 self.open = value.into();
56 self
57 }
58
59 pub fn placement<I: Into<MaybeProp<Placement>>>(mut self, value: I) -> Self {
61 self.placement = value.into();
62 self
63 }
64
65 pub fn strategy<I: Into<MaybeProp<Strategy>>>(mut self, value: I) -> Self {
67 self.strategy = value.into();
68 self
69 }
70
71 pub fn middleware<I: Into<MaybeProp<WrappedMiddleware>>>(mut self, value: I) -> Self {
73 self.middleware = value.into();
74 self
75 }
76
77 pub fn transform<I: Into<MaybeProp<bool>>>(mut self, value: I) -> Self {
79 self.transform = value.into();
80 self
81 }
82
83 pub fn while_elements_mounted<I: Into<MaybeProp<SendWrapper<Rc<WhileElementsMountedFn>>>>>(
85 mut self,
86 value: I,
87 ) -> Self {
88 self.while_elements_mounted = value.into();
89 self
90 }
91
92 pub fn while_elements_mounted_auto_update(self) -> Self {
94 let auto_update_rc: SendWrapper<Rc<WhileElementsMountedFn>> =
95 SendWrapper::new(Rc::new(|reference, floating, update| {
96 auto_update(reference, floating, update, AutoUpdateOptions::default())
97 }));
98 self.while_elements_mounted(auto_update_rc)
99 }
100
101 pub fn while_elements_mounted_auto_update_with_enabled(self, enabled: Signal<bool>) -> Self {
103 let auto_update_rc: SendWrapper<Rc<WhileElementsMountedFn>> =
104 SendWrapper::new(Rc::new(|reference, floating, update| {
105 auto_update(reference, floating, update, AutoUpdateOptions::default())
106 }));
107 self.while_elements_mounted(MaybeProp::derive(move || {
108 if enabled.get() {
109 Some(auto_update_rc.clone())
110 } else {
111 None
112 }
113 }))
114 }
115
116 pub fn while_elements_mounted_auto_update_with_options(
118 self,
119 options: Signal<AutoUpdateOptions>,
120 ) -> Self {
121 let auto_update_rc =
122 move |options: AutoUpdateOptions| -> SendWrapper<Rc<WhileElementsMountedFn>> {
123 SendWrapper::new(Rc::new(move |reference, floating, update| {
124 auto_update(reference, floating, update, options.clone())
125 }))
126 };
127
128 self.while_elements_mounted(MaybeProp::derive(move || {
129 Some(auto_update_rc(options.get()))
130 }))
131 }
132
133 pub fn while_elements_mounted_auto_update_with_enabled_and_options(
135 self,
136 enabled: Signal<bool>,
137 options: Signal<AutoUpdateOptions>,
138 ) -> Self {
139 let auto_update_rc =
140 move |options: AutoUpdateOptions| -> SendWrapper<Rc<WhileElementsMountedFn>> {
141 SendWrapper::new(Rc::new(move |reference, floating, update| {
142 auto_update(reference, floating, update, options.clone())
143 }))
144 };
145
146 self.while_elements_mounted(MaybeProp::derive(move || {
147 if enabled.get() {
148 Some(auto_update_rc(options.get()))
149 } else {
150 None
151 }
152 }))
153 }
154}
155
156#[derive(Clone, Debug, PartialEq)]
158pub struct FloatingStyles {
159 pub position: Strategy,
160 pub top: String,
161 pub left: String,
162 pub transform: Option<String>,
163 pub will_change: Option<String>,
164}
165
166impl FloatingStyles {
167 pub fn style_position(&self) -> String {
168 match self.position {
169 Strategy::Absolute => "absolute".to_owned(),
170 Strategy::Fixed => "fixed".to_owned(),
171 }
172 }
173
174 pub fn style_top(&self) -> String {
175 self.top.clone()
176 }
177
178 pub fn style_left(&self) -> String {
179 self.left.clone()
180 }
181
182 pub fn style_transform(&self) -> Option<String> {
183 self.transform.clone()
184 }
185
186 pub fn style_will_change(&self) -> Option<String> {
187 self.will_change.clone()
188 }
189}
190
191impl Display for FloatingStyles {
192 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
193 write!(
194 f,
195 "position: {}; top: {}; left: {};{}{}",
196 match self.position {
197 Strategy::Absolute => "absolute",
198 Strategy::Fixed => "fixed",
199 },
200 self.top,
201 self.left,
202 self.transform
203 .as_ref()
204 .map_or("".to_owned(), |transform| format!(
205 " transform: {transform};"
206 ),),
207 self.will_change
208 .as_ref()
209 .map_or("".to_owned(), |will_change| format!(
210 " will-change: {will_change};"
211 ))
212 )
213 }
214}
215
216impl IntoStyle for FloatingStyles {
217 type AsyncOutput = Self;
218 type State = (leptos::tachys::renderer::types::Element, Self);
219 type Cloneable = Self;
220 type CloneableOwned = Self;
221 fn to_html(self, style: &mut String) {
222 style.push_str(&self.to_string());
223 }
224
225 fn hydrate<const FROM_SERVER: bool>(
226 self,
227 el: &leptos::tachys::renderer::types::Element,
228 ) -> Self::State {
229 (el.clone(), self)
230 }
231
232 fn build(self, el: &leptos::tachys::renderer::types::Element) -> Self::State {
233 leptos::tachys::renderer::Rndr::set_attribute(el, "style", &self.to_string());
234 (el.clone(), self)
235 }
236
237 fn rebuild(self, state: &mut Self::State) {
238 let (el, prev) = state;
239 if self != *prev {
240 leptos::tachys::renderer::Rndr::set_attribute(el, "style", &self.to_string());
241 }
242 *prev = self;
243 }
244
245 fn into_cloneable(self) -> Self::Cloneable {
246 self
247 }
248
249 fn into_cloneable_owned(self) -> Self::CloneableOwned {
250 self
251 }
252
253 fn dry_resolve(&mut self) {}
254
255 async fn resolve(self) -> Self::AsyncOutput {
256 self
257 }
258
259 fn reset(state: &mut Self::State) {
260 let (el, _prev) = state;
261 leptos::tachys::renderer::Rndr::remove_attribute(el, "style");
262 }
263}
264
265pub struct UseFloatingReturn {
267 pub x: Signal<f64>,
269
270 pub y: Signal<f64>,
272
273 pub placement: Signal<Placement>,
275
276 pub strategy: Signal<Strategy>,
278
279 pub middleware_data: Signal<MiddlewareData>,
281
282 pub is_positioned: Signal<bool>,
284
285 pub floating_styles: Signal<FloatingStyles>,
287
288 pub update: SendWrapper<Rc<dyn Fn()>>,
290}