floating_ui_dioxus/
use_floating.rs

1use std::{cell::RefCell, rc::Rc};
2
3use dioxus::{core::use_drop, prelude::*, web::WebEventExt};
4use floating_ui_dom::{
5    ComputePositionConfig, MiddlewareData, Placement, Strategy, compute_position,
6};
7
8use crate::{
9    FloatingStyles, UseFloatingOptions, UseFloatingReturn, WhileElementsMountedCleanupFn,
10    utils::{get_dpr::get_dpr, round_by_dpr::round_by_dpr},
11};
12
13/// Computes the `x` and `y` coordinates that will place the floating element next to a reference element.
14pub fn use_floating(
15    reference: Signal<Option<Rc<MountedData>>>,
16    floating: Signal<Option<Rc<MountedData>>>,
17    options: UseFloatingOptions,
18) -> UseFloatingReturn {
19    let open_option = use_memo(move || options.open.unwrap_or(true));
20    let placement_option = use_memo(move || options.placement.unwrap_or(Placement::Bottom));
21    let strategy_option = use_memo(move || options.strategy.unwrap_or(Strategy::Absolute));
22    let middleware_option = use_memo(move || options.middleware.clone().unwrap_or_default());
23    let transform_option = use_memo(move || options.transform.unwrap_or(true));
24    let while_elements_mounted_option = options.while_elements_mounted;
25
26    let mut x = use_signal(|| 0.0);
27    let mut y = use_signal(|| 0.0);
28    #[expect(clippy::redundant_closure)]
29    let mut strategy = use_signal(|| strategy_option());
30    #[expect(clippy::redundant_closure)]
31    let mut placement = use_signal(|| placement_option());
32    let mut middleware_data = use_signal(MiddlewareData::default);
33    let mut is_positioned = use_signal(|| false);
34    let floating_styles = use_memo(move || {
35        let initial_styles = FloatingStyles {
36            position: strategy(),
37            top: "0".to_owned(),
38            left: "0".to_owned(),
39            transform: None,
40            will_change: None,
41        };
42
43        match floating().map(|floating| floating.as_web_event()) {
44            Some(floating_element) => {
45                let x_val = round_by_dpr(&floating_element, x());
46                let y_val = round_by_dpr(&floating_element, y());
47
48                if transform_option() {
49                    FloatingStyles {
50                        transform: Some(format!("translate({x_val}px, {y_val}px)")),
51                        will_change: (get_dpr(&floating_element) >= 1.5)
52                            .then_some("transform".to_owned()),
53                        ..initial_styles
54                    }
55                } else {
56                    FloatingStyles {
57                        left: format!("{x_val}px"),
58                        top: format!("{y_val}px"),
59                        ..initial_styles
60                    }
61                }
62            }
63            _ => initial_styles,
64        }
65    });
66
67    let update = use_callback(move |_| {
68        if let Some(reference_element) = reference().map(|reference| reference.as_web_event())
69            && let Some(floating_element) = floating().map(|floating| floating.as_web_event())
70        {
71            let config = ComputePositionConfig {
72                placement: Some(placement_option()),
73                strategy: Some(strategy_option()),
74                middleware: Some(middleware_option()),
75            };
76
77            let open = open_option();
78
79            let position = compute_position((&reference_element).into(), &floating_element, config);
80            x.set(position.x);
81            y.set(position.y);
82            strategy.set(position.strategy);
83            placement.set(position.placement);
84            middleware_data.set(position.middleware_data);
85            // The floating element's position may be recomputed while it's closed
86            // but still mounted (such as when transitioning out). To ensure
87            // `is_positioned` will be `false` initially on the next open,
88            // avoid setting it to `true` when `open === false` (must be specified).
89            is_positioned.set(open);
90        }
91    });
92
93    let while_elements_mounted_cleanup = use_hook::<
94        Rc<RefCell<Option<Rc<WhileElementsMountedCleanupFn>>>>,
95    >(|| Rc::new(RefCell::new(None)));
96
97    let cleanup = use_callback({
98        let while_elements_mounted_cleanup = while_elements_mounted_cleanup.clone();
99
100        move |_| {
101            if let Some(while_elements_mounted_cleanup) = while_elements_mounted_cleanup.take() {
102                while_elements_mounted_cleanup();
103            }
104        }
105    });
106
107    let attach = use_callback(move |_| {
108        cleanup.call(());
109
110        if let Some(while_elements_mounted) = &while_elements_mounted_option {
111            if let Some(reference_element) = reference().map(|reference| reference.as_web_event())
112                && let Some(floating_element) = floating().map(|floating| floating.as_web_event())
113            {
114                while_elements_mounted_cleanup.replace(Some(Rc::new((*while_elements_mounted)(
115                    (&reference_element).into(),
116                    &floating_element,
117                    Rc::new(move || {
118                        update.call(());
119                    }),
120                ))));
121            }
122        } else {
123            update.call(());
124        }
125    });
126
127    let reset = use_callback(move |_| {
128        if open_option() {
129            is_positioned.set(false);
130        }
131    });
132
133    use_effect(move || {
134        _ = open_option();
135        _ = placement_option();
136        _ = strategy_option();
137        _ = middleware_option();
138
139        update.call(());
140    });
141
142    use_effect(move || {
143        _ = reference();
144        _ = floating();
145
146        attach(());
147    });
148
149    use_effect(move || {
150        _ = open_option();
151
152        reset.call(());
153    });
154
155    use_drop(move || {
156        cleanup.call(());
157    });
158
159    UseFloatingReturn {
160        x,
161        y,
162        placement,
163        strategy,
164        middleware_data,
165        is_positioned,
166        floating_styles,
167        update,
168    }
169}