telegram_webapp_sdk/api/
settings_button.rs

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