telegram_webapp_sdk/api/
settings_button.rs

1use js_sys::{Function, Reflect};
2use wasm_bindgen::{JsCast, prelude::*};
3use web_sys::window;
4
5/// Show the Telegram Settings Button.
6///
7/// # Errors
8/// Returns `Err` if the underlying JavaScript call fails or the button is
9/// missing.
10///
11/// # Examples
12/// ```no_run
13/// use telegram_webapp_sdk::api::settings_button::show;
14/// # fn run() -> Result<(), wasm_bindgen::JsValue> {
15/// show()?;
16/// # Ok(()) }
17/// ```
18pub fn show() -> Result<(), JsValue> {
19    let button = settings_button_object()?;
20    let func = Reflect::get(&button, &"show".into())?.dyn_into::<Function>()?;
21    func.call0(&button)?;
22    Ok(())
23}
24
25/// Hide the Telegram Settings Button.
26///
27/// # Errors
28/// Returns `Err` if the underlying JavaScript call fails or the button is
29/// missing.
30///
31/// # Examples
32/// ```no_run
33/// use telegram_webapp_sdk::api::settings_button::hide;
34/// # fn run() -> Result<(), wasm_bindgen::JsValue> {
35/// hide()?;
36/// # Ok(()) }
37/// ```
38pub fn hide() -> Result<(), JsValue> {
39    let button = settings_button_object()?;
40    let func = Reflect::get(&button, &"hide".into())?.dyn_into::<Function>()?;
41    func.call0(&button)?;
42    Ok(())
43}
44
45/// Register a callback for Settings Button clicks.
46///
47/// # Safety
48/// The closure must be kept alive for as long as it's registered.
49///
50/// # Errors
51/// Returns `Err` if the registration fails or the button is missing.
52///
53/// # Examples
54/// ```no_run
55/// use telegram_webapp_sdk::api::settings_button::on_click;
56/// use wasm_bindgen::prelude::Closure;
57/// # fn run() -> Result<(), wasm_bindgen::JsValue> {
58/// let cb = Closure::wrap(Box::new(|| {}) as Box<dyn Fn()>);
59/// on_click(&cb)?;
60/// # Ok(()) }
61/// ```
62pub fn on_click(callback: &Closure<dyn Fn()>) -> Result<(), JsValue> {
63    let button = settings_button_object()?;
64    let func = Reflect::get(&button, &"onClick".into())?.dyn_into::<Function>()?;
65    func.call1(&button, callback.as_ref())?;
66    Ok(())
67}
68
69/// Remove a previously registered click callback.
70///
71/// # Errors
72/// Returns `Err` if the deregistration fails or the button is missing.
73///
74/// # Examples
75/// ```no_run
76/// use telegram_webapp_sdk::api::settings_button::{off_click, on_click};
77/// use wasm_bindgen::prelude::Closure;
78/// # fn run() -> Result<(), wasm_bindgen::JsValue> {
79/// let cb = Closure::wrap(Box::new(|| {}) as Box<dyn Fn()>);
80/// on_click(&cb)?;
81/// off_click(&cb)?;
82/// # Ok(()) }
83/// ```
84pub fn off_click(callback: &Closure<dyn Fn()>) -> Result<(), JsValue> {
85    let button = settings_button_object()?;
86    let func = Reflect::get(&button, &"offClick".into())?.dyn_into::<Function>()?;
87    func.call1(&button, callback.as_ref())?;
88    Ok(())
89}
90
91fn settings_button_object() -> Result<JsValue, JsValue> {
92    let win = window().ok_or_else(|| JsValue::from_str("no window"))?;
93    let tg = Reflect::get(&win, &"Telegram".into())?;
94    let webapp = Reflect::get(&tg, &"WebApp".into())?;
95    Reflect::get(&webapp, &"SettingsButton".into())
96}
97
98#[cfg(test)]
99mod tests {
100    use js_sys::{Function, Object, Reflect};
101    use wasm_bindgen::closure::Closure;
102    use wasm_bindgen_test::{wasm_bindgen_test, wasm_bindgen_test_configure};
103    use web_sys::window;
104
105    use super::*;
106
107    wasm_bindgen_test_configure!(run_in_browser);
108
109    #[allow(dead_code)]
110    fn setup_button() -> Object {
111        let win = window().unwrap();
112        let telegram = Object::new();
113        let webapp = Object::new();
114        let button = Object::new();
115        let _ = Reflect::set(&win, &"Telegram".into(), &telegram);
116        let _ = Reflect::set(&telegram, &"WebApp".into(), &webapp);
117        let _ = Reflect::set(&webapp, &"SettingsButton".into(), &button);
118        button
119    }
120
121    #[wasm_bindgen_test]
122    #[allow(dead_code, clippy::unused_unit)]
123    fn show_calls_js() {
124        let button = setup_button();
125        let func = Function::new_no_args("this.called = true;");
126        let _ = Reflect::set(&button, &"show".into(), &func);
127        assert!(show().is_ok());
128        assert!(
129            Reflect::get(&button, &"called".into())
130                .unwrap()
131                .as_bool()
132                .unwrap()
133        );
134    }
135
136    #[wasm_bindgen_test]
137    #[allow(dead_code, clippy::unused_unit)]
138    fn hide_calls_js() {
139        let button = setup_button();
140        let func = Function::new_no_args("this.called = true;");
141        let _ = Reflect::set(&button, &"hide".into(), &func);
142        assert!(hide().is_ok());
143        assert!(
144            Reflect::get(&button, &"called".into())
145                .unwrap()
146                .as_bool()
147                .unwrap()
148        );
149    }
150
151    #[wasm_bindgen_test]
152    #[allow(dead_code, clippy::unused_unit)]
153    fn click_callbacks() {
154        let button = setup_button();
155        let on = Function::new_with_args("cb", "this.cb = cb;");
156        let off = Function::new_with_args("cb", "delete this.cb;");
157        let _ = Reflect::set(&button, &"onClick".into(), &on);
158        let _ = Reflect::set(&button, &"offClick".into(), &off);
159        let cb = Closure::wrap(Box::new(|| {}) as Box<dyn Fn()>);
160        on_click(&cb).expect("on");
161        assert!(Reflect::has(&button, &"cb".into()).unwrap());
162        off_click(&cb).expect("off");
163        assert!(!Reflect::has(&button, &"cb".into()).unwrap());
164    }
165
166    #[wasm_bindgen_test]
167    #[allow(dead_code, clippy::unused_unit)]
168    fn show_err() {
169        let _ = setup_button();
170        assert!(show().is_err());
171    }
172
173    #[wasm_bindgen_test]
174    #[allow(dead_code, clippy::unused_unit)]
175    fn on_click_err() {
176        let _ = setup_button();
177        let cb = Closure::wrap(Box::new(|| {}) as Box<dyn Fn()>);
178        assert!(on_click(&cb).is_err());
179    }
180}