floating_ui_core/middleware/
hide.rs

1use floating_ui_utils::{ALL_SIDES, Rect, SideObject};
2use serde::{Deserialize, Serialize};
3
4use crate::{
5    detect_overflow::{DetectOverflowOptions, detect_overflow},
6    types::{
7        Derivable, DerivableFn, ElementContext, Middleware, MiddlewareReturn, MiddlewareState,
8        MiddlewareWithOptions,
9    },
10};
11
12fn get_side_offsets(overflow: SideObject, rect: &Rect) -> SideObject {
13    SideObject {
14        top: overflow.top - rect.height,
15        right: overflow.right - rect.width,
16        bottom: overflow.bottom - rect.height,
17        left: overflow.left - rect.width,
18    }
19}
20
21fn is_any_side_fully_clipped(overflow: &SideObject) -> bool {
22    ALL_SIDES.into_iter().any(|side| overflow.side(side) >= 0.0)
23}
24
25/// Name of the [`Hide`] middleware.
26pub const HIDE_NAME: &str = "hide";
27
28/// Fallback strategy used by [`Hide`] middleware.
29#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
30pub enum HideStrategy {
31    #[default]
32    ReferenceHidden,
33    Escaped,
34}
35
36/// Options for [`Hide`] middleware.
37#[derive(Clone, Debug, PartialEq)]
38pub struct HideOptions<Element: Clone> {
39    /// Options for [`detect_overflow`].
40    ///
41    /// Defaults to [`DetectOverflowOptions::default`].
42    pub detect_overflow: Option<DetectOverflowOptions<Element>>,
43
44    /// The strategy used to determine when to hide the floating element.
45    ///
46    /// Defaults to [`HideStrategy::ReferenceHidden`].
47    pub strategy: Option<HideStrategy>,
48}
49
50impl<Element: Clone> HideOptions<Element> {
51    /// Set `detect_overflow` option.
52    pub fn detect_overflow(mut self, value: DetectOverflowOptions<Element>) -> Self {
53        self.detect_overflow = Some(value);
54        self
55    }
56
57    /// Set `strategy` option.
58    pub fn strategy(mut self, value: HideStrategy) -> Self {
59        self.strategy = Some(value);
60        self
61    }
62}
63
64impl<Element: Clone> Default for HideOptions<Element> {
65    fn default() -> Self {
66        Self {
67            detect_overflow: Default::default(),
68            strategy: Default::default(),
69        }
70    }
71}
72
73/// Data stored by [`Hide`] middleware.
74#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
75pub struct HideData {
76    pub reference_hidden: Option<bool>,
77    pub reference_hidden_offsets: Option<SideObject>,
78    pub escaped: Option<bool>,
79    pub escaped_offsets: Option<SideObject>,
80}
81
82/// Hide middleware.
83///
84/// Provides data to hide the floating element in applicable situations,
85/// such as when it is not in the same clipping context as the reference element.
86///
87/// See [the Rust Floating UI book](https://floating-ui.rustforweb.org/middleware/hide.html) for more documentation.
88#[derive(PartialEq)]
89pub struct Hide<'a, Element: Clone + 'static, Window: Clone> {
90    options: Derivable<'a, Element, Window, HideOptions<Element>>,
91}
92
93impl<'a, Element: Clone, Window: Clone> Hide<'a, Element, Window> {
94    /// Constructs a new instance of this middleware.
95    pub fn new(options: HideOptions<Element>) -> Self {
96        Hide {
97            options: options.into(),
98        }
99    }
100
101    /// Constructs a new instance of this middleware with derivable options.
102    pub fn new_derivable(options: Derivable<'a, Element, Window, HideOptions<Element>>) -> Self {
103        Hide { options }
104    }
105
106    /// Constructs a new instance of this middleware with derivable options function.
107    pub fn new_derivable_fn(
108        options: DerivableFn<'a, Element, Window, HideOptions<Element>>,
109    ) -> Self {
110        Hide {
111            options: options.into(),
112        }
113    }
114}
115
116impl<Element: Clone + 'static, Window: Clone> Clone for Hide<'_, Element, Window> {
117    fn clone(&self) -> Self {
118        Self {
119            options: self.options.clone(),
120        }
121    }
122}
123
124impl<Element: Clone + PartialEq, Window: Clone + PartialEq> Middleware<Element, Window>
125    for Hide<'static, Element, Window>
126{
127    fn name(&self) -> &'static str {
128        HIDE_NAME
129    }
130
131    fn compute(&self, state: MiddlewareState<Element, Window>) -> MiddlewareReturn {
132        let options = self.options.evaluate(state.clone());
133
134        let MiddlewareState {
135            elements, rects, ..
136        } = state;
137
138        let strategy = options.strategy.unwrap_or_default();
139
140        match strategy {
141            HideStrategy::ReferenceHidden => {
142                let overflow = detect_overflow(
143                    MiddlewareState {
144                        elements: elements.clone(),
145                        ..state
146                    },
147                    options
148                        .detect_overflow
149                        .unwrap_or_default()
150                        .element_context(ElementContext::Reference),
151                );
152
153                let offsets = get_side_offsets(overflow, &rects.reference);
154
155                MiddlewareReturn {
156                    x: None,
157                    y: None,
158                    data: Some(
159                        serde_json::to_value(HideData {
160                            reference_hidden: Some(is_any_side_fully_clipped(&offsets)),
161                            reference_hidden_offsets: Some(offsets),
162                            escaped: None,
163                            escaped_offsets: None,
164                        })
165                        .expect("Data should be valid JSON."),
166                    ),
167                    reset: None,
168                }
169            }
170            HideStrategy::Escaped => {
171                let overflow = detect_overflow(
172                    MiddlewareState {
173                        elements: elements.clone(),
174                        ..state
175                    },
176                    options
177                        .detect_overflow
178                        .unwrap_or_default()
179                        .alt_boundary(true),
180                );
181
182                let offsets = get_side_offsets(overflow, &rects.floating);
183
184                MiddlewareReturn {
185                    x: None,
186                    y: None,
187                    data: Some(
188                        serde_json::to_value(HideData {
189                            reference_hidden: None,
190                            reference_hidden_offsets: None,
191                            escaped: Some(is_any_side_fully_clipped(&offsets)),
192                            escaped_offsets: Some(offsets),
193                        })
194                        .expect("Data should be valid JSON."),
195                    ),
196                    reset: None,
197                }
198            }
199        }
200    }
201}
202
203impl<Element: Clone, Window: Clone> MiddlewareWithOptions<Element, Window, HideOptions<Element>>
204    for Hide<'_, Element, Window>
205{
206    fn options(&self) -> &Derivable<'_, Element, Window, HideOptions<Element>> {
207        &self.options
208    }
209}