1use std::{cell::RefCell, rc::Rc};
2
3use floating_ui_utils::{
4 ClientRectObject,
5 dom::{OverflowAncestor, get_document_element, get_overflow_ancestors, get_window},
6};
7use web_sys::{
8 AddEventListenerOptions, Element, EventTarget, IntersectionObserver, IntersectionObserverEntry,
9 IntersectionObserverInit, ResizeObserver, ResizeObserverEntry,
10 wasm_bindgen::{JsCast, JsValue, closure::Closure},
11 window,
12};
13
14use crate::{
15 types::{ElementOrVirtual, OwnedElementOrVirtual},
16 utils::{get_bounding_client_rect::get_bounding_client_rect, rects_are_equal::rects_are_equal},
17};
18
19fn request_animation_frame(callback: &Closure<dyn FnMut()>) -> i32 {
20 window()
21 .expect("Window should exist.")
22 .request_animation_frame(callback.as_ref().unchecked_ref())
23 .expect("Request animation frame should be successful.")
24}
25
26fn cancel_animation_frame(handle: i32) {
27 window()
28 .expect("Window should exist.")
29 .cancel_animation_frame(handle)
30 .expect("Cancel animation frame should be successful.")
31}
32
33fn observe_move(element: Element, on_move: Rc<dyn Fn()>) -> Box<dyn Fn()> {
34 let io: Rc<RefCell<Option<IntersectionObserver>>> = Rc::new(RefCell::new(None));
35 let timeout_id: Rc<RefCell<Option<i32>>> = Rc::new(RefCell::new(None));
36
37 let window = get_window(Some(&element));
38 let root = get_document_element(Some((&element).into()));
39
40 type ObserveClosure = Closure<dyn Fn(Vec<IntersectionObserverEntry>)>;
41 let observe_closure: Rc<RefCell<Option<ObserveClosure>>> = Rc::new(RefCell::new(None));
42
43 let cleanup_io = io.clone();
44 let cleanup_timeout_id = timeout_id.clone();
45 let cleanup_window = window.clone();
46 let cleanup_observe_closure = observe_closure.clone();
47 let cleanup = move || {
48 if let Some(timeout_id) = cleanup_timeout_id.take() {
49 cleanup_window.clear_timeout_with_handle(timeout_id);
50 }
51
52 if let Some(io) = cleanup_io.take() {
53 io.disconnect();
54 }
55
56 _ = cleanup_observe_closure.take();
57 };
58 let cleanup_rc = Rc::new(cleanup);
59 type RefreshFn = Box<dyn Fn(bool, f64)>;
60 let refresh_closure: Rc<RefCell<Option<RefreshFn>>> = Rc::new(RefCell::new(None));
61 let refresh_closure_clone = refresh_closure.clone();
62
63 let refresh_cleanup = cleanup_rc.clone();
64 *refresh_closure_clone.borrow_mut() = Some(Box::new(move |skip: bool, threshold: f64| {
65 refresh_cleanup();
66
67 let element_rect_for_root_margin = element.get_bounding_client_rect();
68
69 if !skip {
70 on_move();
71 }
72
73 if element_rect_for_root_margin.width() == 0.0
74 || element_rect_for_root_margin.height() == 0.0
75 {
76 return;
77 }
78
79 let inset_top = element_rect_for_root_margin.top().floor();
80 let inset_right = (root.client_width() as f64
81 - (element_rect_for_root_margin.left() + element_rect_for_root_margin.width()))
82 .floor();
83 let inset_bottom = (root.client_height() as f64
84 - (element_rect_for_root_margin.top() + element_rect_for_root_margin.height()))
85 .floor();
86 let inset_left = element_rect_for_root_margin.left().floor();
87 let root_margin = format!(
88 "{}px {}px {}px {}px",
89 -inset_top, -inset_right, -inset_bottom, -inset_left
90 );
91
92 let is_first_update: Rc<RefCell<bool>> = Rc::new(RefCell::new(true));
93
94 let timeout_refresh = refresh_closure.clone();
95 let timeout_closure: Rc<Closure<dyn Fn()>> = Rc::new(Closure::new(move || {
96 timeout_refresh
97 .borrow()
98 .as_ref()
99 .expect("Refresh closure should exist.")(false, 1e-7)
100 }));
101
102 let observe_timeout_id = timeout_id.clone();
103 let observe_window = window.clone();
104 let observe_refresh = refresh_closure.clone();
105 let local_observe_closure = Closure::new({
106 let element = element.clone();
107
108 move |entries: Vec<IntersectionObserverEntry>| {
109 let ratio = entries[0].intersection_ratio();
110
111 if ratio != threshold {
112 if !*is_first_update.borrow() {
113 observe_refresh
114 .borrow()
115 .as_ref()
116 .expect("Refresh closure should exist.")(
117 false, 1.0
118 );
119 return;
120 }
121
122 if ratio == 0.0 {
123 observe_timeout_id.replace(Some(
125 observe_window
126 .set_timeout_with_callback_and_timeout_and_arguments_0(
127 (*timeout_closure).as_ref().unchecked_ref(),
128 1000,
129 )
130 .expect("Set timeout should be successful."),
131 ));
132 } else {
133 observe_refresh
134 .borrow()
135 .as_ref()
136 .expect("Refresh closure should exist.")(
137 false, ratio
138 );
139 }
140 }
141
142 if ratio == 1.0
143 && !rects_are_equal(
144 &element_rect_for_root_margin.clone().into(),
145 &element.get_bounding_client_rect().into(),
146 )
147 {
148 observe_refresh
156 .borrow()
157 .as_ref()
158 .expect("Refresh closure should exist.")(false, 1.0);
159 }
160
161 is_first_update.replace(false);
162 }
163 });
164
165 let options = IntersectionObserverInit::new();
166 options.set_root_margin(&root_margin);
167 options.set_threshold(&JsValue::from_f64(threshold.clamp(0.0, 1.0)));
168
169 let local_io = IntersectionObserver::new_with_options(
170 local_observe_closure.as_ref().unchecked_ref(),
171 &options,
172 )
173 .expect("Intersection observer should be created.");
174
175 observe_closure.replace(Some(local_observe_closure));
176
177 local_io.observe(&element);
178 io.replace(Some(local_io));
179 }));
180
181 refresh_closure_clone
182 .borrow()
183 .as_ref()
184 .expect("Refresh closure should exist.")(true, 1.0);
185
186 Box::new(move || {
187 cleanup_rc();
188 })
189}
190
191#[derive(Clone, Debug, Default, PartialEq)]
193pub struct AutoUpdateOptions {
194 pub ancestor_scroll: Option<bool>,
198
199 pub ancestor_resize: Option<bool>,
203
204 pub element_resize: Option<bool>,
208
209 pub layout_shift: Option<bool>,
213
214 pub animation_frame: Option<bool>,
219}
220
221impl AutoUpdateOptions {
222 pub fn ancestor_scroll(mut self, value: bool) -> Self {
224 self.ancestor_scroll = Some(value);
225 self
226 }
227
228 pub fn ancestor_resize(mut self, value: bool) -> Self {
230 self.ancestor_resize = Some(value);
231 self
232 }
233
234 pub fn element_resize(mut self, value: bool) -> Self {
236 self.element_resize = Some(value);
237 self
238 }
239
240 pub fn layout_shift(mut self, value: bool) -> Self {
242 self.layout_shift = Some(value);
243 self
244 }
245
246 pub fn animation_frame(mut self, value: bool) -> Self {
248 self.animation_frame = Some(value);
249 self
250 }
251}
252
253pub fn auto_update(
256 reference: ElementOrVirtual,
257 floating: &Element,
258 update: Rc<dyn Fn()>,
259 options: AutoUpdateOptions,
260) -> Box<dyn Fn()> {
261 let ancestor_scoll = options.ancestor_scroll.unwrap_or(true);
262 let ancestor_resize = options.ancestor_resize.unwrap_or(true);
263 let element_resize = options.element_resize.unwrap_or(true);
264 let layout_shift = options.layout_shift.unwrap_or(true);
265 let animation_frame = options.animation_frame.unwrap_or(false);
266
267 let reference_element = reference.clone().resolve();
268
269 let owned_reference = match reference.clone() {
270 ElementOrVirtual::Element(e) => OwnedElementOrVirtual::Element(e.clone()),
271 ElementOrVirtual::VirtualElement(ve) => OwnedElementOrVirtual::VirtualElement(ve.clone()),
272 };
273
274 let ancestors = if ancestor_scoll || ancestor_resize {
275 let mut ancestors = vec![];
276
277 if let Some(reference) = reference_element.as_ref() {
278 ancestors = get_overflow_ancestors(reference, ancestors, true);
279 }
280
281 ancestors.append(&mut get_overflow_ancestors(floating, vec![], true));
282
283 ancestors
284 } else {
285 vec![]
286 };
287
288 let update_closure: Closure<dyn Fn()> = Closure::new({
289 let update = update.clone();
290
291 move || {
292 update();
293 }
294 });
295
296 for ancestor in &ancestors {
297 let event_target: &EventTarget = match ancestor {
298 OverflowAncestor::Element(element) => element,
299 OverflowAncestor::Window(window) => window,
300 };
301
302 if ancestor_scoll {
303 let options = AddEventListenerOptions::new();
304 options.set_passive(true);
305
306 event_target
307 .add_event_listener_with_callback_and_add_event_listener_options(
308 "scroll",
309 update_closure.as_ref().unchecked_ref(),
310 &options,
311 )
312 .expect("Scroll event listener should be added.");
313 }
314
315 if ancestor_resize {
316 event_target
317 .add_event_listener_with_callback("resize", update_closure.as_ref().unchecked_ref())
318 .expect("Resize event listener should be added.");
319 }
320 }
321
322 let cleanup_observe_move = reference_element.as_ref().and_then(|reference_element| {
323 layout_shift.then(|| observe_move(reference_element.clone(), update.clone()))
324 });
325
326 let reobserve_frame: Rc<RefCell<Option<i32>>> = Rc::new(RefCell::new(None));
327 let resize_observer: Rc<RefCell<Option<ResizeObserver>>> = Rc::new(RefCell::new(None));
328
329 if element_resize {
330 let reobserve_floating = floating.clone();
331 let reobserve_closure: Rc<Closure<dyn FnMut()>> = Rc::new(Closure::new({
332 let resize_observer = resize_observer.clone();
333
334 move || {
335 resize_observer
336 .borrow()
337 .as_ref()
338 .expect("Resize observer should exist.")
339 .observe(&reobserve_floating);
340 }
341 }));
342
343 let resize_reference_element = reference_element.clone();
344 let resize_closure: Closure<dyn Fn(Vec<ResizeObserverEntry>)> = Closure::new({
345 let reobserve_frame = reobserve_frame.clone();
346 let update = update.clone();
347
348 move |entries: Vec<ResizeObserverEntry>| {
349 if let Some(first_entry) = entries.first()
350 && resize_reference_element
351 .as_ref()
352 .is_some_and(|reference_element| first_entry.target() == *reference_element)
353 {
354 if let Some(reobserve_frame) = reobserve_frame.take() {
355 cancel_animation_frame(reobserve_frame);
356 }
357
358 reobserve_frame
359 .replace(Some(request_animation_frame(reobserve_closure.as_ref())));
360 }
361
362 update();
363 }
364 });
365
366 resize_observer.replace(Some(
367 ResizeObserver::new(resize_closure.into_js_value().unchecked_ref())
368 .expect("Resize observer should be created."),
369 ));
370
371 if let Some(reference) = reference_element.as_ref()
372 && !animation_frame
373 {
374 resize_observer
375 .borrow()
376 .as_ref()
377 .expect("Resize observer should exist.")
378 .observe(reference);
379 }
380
381 resize_observer
382 .borrow()
383 .as_ref()
384 .expect("Resize observer should exist.")
385 .observe(floating);
386 }
387
388 let frame_id: Rc<RefCell<Option<i32>>> = Rc::new(RefCell::new(None));
389 let prev_ref_rect: Rc<RefCell<Option<ClientRectObject>>> =
390 Rc::new(RefCell::new(animation_frame.then(|| {
391 get_bounding_client_rect(reference, false, false, None)
392 })));
393
394 let frame_loop_frame_id = frame_id.clone();
395 let frame_loop_closure = Rc::new(RefCell::new(None));
396 let frame_loop_closure_clone = frame_loop_closure.clone();
397
398 *frame_loop_closure_clone.borrow_mut() = Some(Closure::new({
399 let owned_reference = owned_reference.clone();
400 let update = update.clone();
401 let prev_ref_rect = prev_ref_rect.clone();
402 let frame_loop_frame_id = frame_loop_frame_id.clone();
403
404 move || {
405 let next_ref_rect =
406 get_bounding_client_rect((&owned_reference).into(), false, false, None);
407
408 if let Some(prev_ref_rect) = prev_ref_rect.borrow().as_ref()
409 && !rects_are_equal(prev_ref_rect, &next_ref_rect)
410 {
411 update();
412 }
413
414 prev_ref_rect.replace(Some(next_ref_rect));
415 frame_loop_frame_id.replace(Some(request_animation_frame(
416 frame_loop_closure
417 .borrow()
418 .as_ref()
419 .expect("Frame loop closure should exist."),
420 )));
421 }
422 }));
423
424 if animation_frame {
425 let next_ref_rect = get_bounding_client_rect((&owned_reference).into(), false, false, None);
428
429 if let Some(prev_ref_rect) = prev_ref_rect.borrow().as_ref()
430 && (next_ref_rect.x != prev_ref_rect.x
431 || next_ref_rect.y != prev_ref_rect.y
432 || next_ref_rect.width != prev_ref_rect.width
433 || next_ref_rect.height != prev_ref_rect.height)
434 {
435 update();
436 }
437
438 prev_ref_rect.replace(Some(next_ref_rect));
439 frame_loop_frame_id.replace(Some(request_animation_frame(
440 frame_loop_closure_clone
441 .borrow()
442 .as_ref()
443 .expect("Frame loop closure should exist."),
444 )));
445 }
446
447 update();
448
449 Box::new(move || {
450 for ancestor in &ancestors {
451 let event_target: &EventTarget = match ancestor {
452 OverflowAncestor::Element(element) => element,
453 OverflowAncestor::Window(window) => window,
454 };
455
456 if ancestor_scoll {
457 event_target
458 .remove_event_listener_with_callback(
459 "scroll",
460 update_closure.as_ref().unchecked_ref(),
461 )
462 .expect("Scroll event listener should be removed.");
463 }
464
465 if ancestor_resize {
466 event_target
467 .remove_event_listener_with_callback(
468 "resize",
469 update_closure.as_ref().unchecked_ref(),
470 )
471 .expect("Resize event listener should be removed.");
472 }
473 }
474
475 if let Some(cleanup_observe_move) = &cleanup_observe_move {
476 cleanup_observe_move();
477 }
478
479 if let Some(reobserve_frame) = reobserve_frame.take() {
480 cancel_animation_frame(reobserve_frame);
481 }
482
483 if let Some(resize_observer) = resize_observer.take() {
484 resize_observer.disconnect();
485 }
486
487 if let Some(frame_id) = frame_id.take() {
488 cancel_animation_frame(frame_id);
489 }
490 })
491}