Skip to main content

logicaffeine_web/ui/hooks/
use_visual_viewport.rs

1//! How much of the window an on-screen keyboard covers.
2
3use dioxus::prelude::*;
4
5/// The height, in CSS pixels, of the window's bottom that an on-screen keyboard covers: what the
6/// visual viewport leaves of the layout viewport. Android with `interactive-widget=resizes-content`
7/// shrinks the layout viewport itself, so this stays 0 there; iOS keeps the layout viewport and
8/// shrinks only the visual one. Updated as the keyboard opens and closes, 0 off the browser.
9pub fn use_keyboard_inset() -> Signal<f64> {
10    #[cfg(target_arch = "wasm32")]
11    {
12        use std::rc::Rc;
13        use wasm_bindgen::prelude::*;
14        use wasm_bindgen::JsCast;
15
16        fn measure() -> Option<f64> {
17            let window = web_sys::window()?;
18            let layout = window.inner_height().ok()?.as_f64()?;
19            let visual = window.visual_viewport()?;
20            Some((layout - visual.height() - visual.offset_top()).max(0.0))
21        }
22
23        let mut inset = use_signal(|| measure().unwrap_or(0.0));
24        let on_resize = use_hook(move || {
25            let on_resize = Closure::<dyn FnMut()>::new(move || {
26                if let Some(now) = measure() {
27                    if (*inset.peek() - now).abs() >= 1.0 {
28                        inset.set(now);
29                    }
30                }
31            });
32            if let Some(visual) = web_sys::window().and_then(|w| w.visual_viewport()) {
33                let _ = visual.add_event_listener_with_callback("resize", on_resize.as_ref().unchecked_ref());
34                let _ = visual.add_event_listener_with_callback("scroll", on_resize.as_ref().unchecked_ref());
35            }
36            Rc::new(on_resize)
37        });
38        use_drop(move || {
39            if let Some(visual) = web_sys::window().and_then(|w| w.visual_viewport()) {
40                let _ = visual.remove_event_listener_with_callback("resize", (*on_resize).as_ref().unchecked_ref());
41                let _ = visual.remove_event_listener_with_callback("scroll", (*on_resize).as_ref().unchecked_ref());
42            }
43        });
44        inset
45    }
46    #[cfg(not(target_arch = "wasm32"))]
47    {
48        use_signal(|| 0.0)
49    }
50}