floating_ui_leptos/
types.rs

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/// Options for [`use_floating`][`crate::use_floating::use_floating`].
19#[derive(Clone, Default)]
20pub struct UseFloatingOptions {
21    /// Represents the open/close state of the floating element.
22    ///
23    /// Defaults to `true`.
24    pub open: MaybeProp<bool>,
25
26    /// Where to place the floating element relative to the reference element.
27    ///
28    /// Defaults to [`Placement::Bottom`].
29    pub placement: MaybeProp<Placement>,
30
31    /// The strategy to use when positioning the floating element.
32    ///
33    /// Defaults to [`Strategy::Absolute`].
34    pub strategy: MaybeProp<Strategy>,
35
36    /// Array of middleware objects to modify the positioning or provide data for rendering.
37    ///
38    /// Defaults to an empty vector.
39    pub middleware: MaybeProp<WrappedMiddleware>,
40
41    ///  Whether to use `transform` for positioning instead of `top` and `left` in the `floatingStyles` object.
42    ///
43    /// Defaults to `true`.
44    pub transform: MaybeProp<bool>,
45
46    /// Callback to handle mounting/unmounting of the elements.
47    ///
48    /// Defaults to [`Option::None`].
49    pub while_elements_mounted: MaybeProp<SendWrapper<Rc<WhileElementsMountedFn>>>,
50}
51
52impl UseFloatingOptions {
53    /// Set `open` option.
54    pub fn open<I: Into<MaybeProp<bool>>>(mut self, value: I) -> Self {
55        self.open = value.into();
56        self
57    }
58
59    /// Set `placement` option.
60    pub fn placement<I: Into<MaybeProp<Placement>>>(mut self, value: I) -> Self {
61        self.placement = value.into();
62        self
63    }
64
65    /// Set `strategy` option.
66    pub fn strategy<I: Into<MaybeProp<Strategy>>>(mut self, value: I) -> Self {
67        self.strategy = value.into();
68        self
69    }
70
71    /// Set `middleware` option.
72    pub fn middleware<I: Into<MaybeProp<WrappedMiddleware>>>(mut self, value: I) -> Self {
73        self.middleware = value.into();
74        self
75    }
76
77    /// Set `transform` option.
78    pub fn transform<I: Into<MaybeProp<bool>>>(mut self, value: I) -> Self {
79        self.transform = value.into();
80        self
81    }
82
83    /// Set `while_elements_mounted` option.
84    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    /// Set `while_elements_mounted` option to [`auto_update`] with [`AutoUpdateOptions::default`].
93    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    /// Set `while_elements_mounted` option to [`auto_update`] with [`AutoUpdateOptions::default`] when `enabled` is `true`.
102    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    /// Set `while_elements_mounted` option to [`auto_update`] with `options`.
117    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    /// Set `while_elements_mounted` option to [`auto_update`] with `options` when `enabled` is `true`.
134    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/// CSS styles to apply to the floating element to position it.
157#[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
265/// Return of [`use_floating`][crate::use_floating::use_floating].
266pub struct UseFloatingReturn {
267    /// The x-coord of the floating element.
268    pub x: Signal<f64>,
269
270    /// The y-coord of the floating element.
271    pub y: Signal<f64>,
272
273    /// The stateful placement, which can be different from the initial `placement` passed as options.
274    pub placement: Signal<Placement>,
275
276    /// The strategy to use when positioning the floating element.
277    pub strategy: Signal<Strategy>,
278
279    /// Additional data from middleware.
280    pub middleware_data: Signal<MiddlewareData>,
281
282    /// Indicates if the floating element has been positioned.
283    pub is_positioned: Signal<bool>,
284
285    /// CSS styles to apply to the floating element to position it.
286    pub floating_styles: Signal<FloatingStyles>,
287
288    /// The function to update floating position manually.
289    pub update: SendWrapper<Rc<dyn Fn()>>,
290}