telegram_webapp_sdk/webapp/
buttons.rs

1// SPDX-FileCopyrightText: 2025 RAprogramm <andrey.rozanov.vl@gmail.com>
2// SPDX-License-Identifier: MIT
3
4use js_sys::{Function, Object, Reflect};
5use serde_wasm_bindgen::to_value;
6use wasm_bindgen::{JsCast, JsValue, prelude::Closure};
7
8use crate::{
9    logger,
10    webapp::{
11        TelegramWebApp,
12        types::{
13            BottomButton, BottomButtonParams, EventHandle, SecondaryButtonParams,
14            SecondaryButtonPosition
15        }
16    }
17};
18
19impl TelegramWebApp {
20    // === Internal bottom button helpers ===
21
22    pub(super) fn bottom_button_object(&self, button: BottomButton) -> Result<Object, JsValue> {
23        let name = button.js_name();
24        Reflect::get(&self.inner, &name.into())
25            .inspect_err(|_| logger::error(&format!("{name} not available")))?
26            .dyn_into::<Object>()
27            .inspect_err(|_| logger::error(&format!("{name} is not an object")))
28    }
29
30    pub(super) fn bottom_button_method(
31        &self,
32        button: BottomButton,
33        method: &str,
34        arg: Option<&JsValue>
35    ) -> Result<(), JsValue> {
36        let name = button.js_name();
37        let btn = self.bottom_button_object(button)?;
38        let f = Reflect::get(&btn, &method.into())
39            .inspect_err(|_| logger::error(&format!("{name}.{method} not available")))?;
40        let func = f.dyn_ref::<Function>().ok_or_else(|| {
41            logger::error(&format!("{name}.{method} is not a function"));
42            JsValue::from_str("not a function")
43        })?;
44        let result = match arg {
45            Some(v) => func.call1(&btn, v),
46            None => func.call0(&btn)
47        };
48        result.inspect_err(|_| logger::error(&format!("{name}.{method} call failed")))?;
49        Ok(())
50    }
51
52    pub(super) fn bottom_button_property(
53        &self,
54        button: BottomButton,
55        property: &str
56    ) -> Option<JsValue> {
57        self.bottom_button_object(button)
58            .ok()
59            .and_then(|object| Reflect::get(&object, &property.into()).ok())
60    }
61
62    // === Bottom button operations ===
63
64    /// Call `WebApp.MainButton.show()` or `WebApp.SecondaryButton.show()`.
65    ///
66    /// # Errors
67    /// Returns [`JsValue`] if the underlying JS call fails.
68    pub fn show_bottom_button(&self, button: BottomButton) -> Result<(), JsValue> {
69        self.bottom_button_method(button, "show", None)
70    }
71
72    /// Hide a bottom button.
73    ///
74    /// # Errors
75    /// Returns [`JsValue`] if the underlying JS call fails.
76    pub fn hide_bottom_button(&self, button: BottomButton) -> Result<(), JsValue> {
77        self.bottom_button_method(button, "hide", None)
78    }
79
80    /// Set bottom button text.
81    ///
82    /// # Errors
83    /// Returns [`JsValue`] if the underlying JS call fails.
84    pub fn set_bottom_button_text(&self, button: BottomButton, text: &str) -> Result<(), JsValue> {
85        self.bottom_button_method(button, "setText", Some(&text.into()))
86    }
87
88    /// Set bottom button color (`setColor(color)`).
89    ///
90    /// # Errors
91    /// Returns [`JsValue`] if the underlying JS call fails.
92    ///
93    /// # Examples
94    /// ```no_run
95    /// # use telegram_webapp_sdk::webapp::{TelegramWebApp, BottomButton};
96    /// # let app = TelegramWebApp::instance().unwrap();
97    /// let _ = app.set_bottom_button_color(BottomButton::Main, "#ff0000");
98    /// ```
99    pub fn set_bottom_button_color(
100        &self,
101        button: BottomButton,
102        color: &str
103    ) -> Result<(), JsValue> {
104        self.bottom_button_method(button, "setColor", Some(&color.into()))
105    }
106
107    /// Set bottom button text color (`setTextColor(color)`).
108    ///
109    /// # Errors
110    /// Returns [`JsValue`] if the underlying JS call fails.
111    ///
112    /// # Examples
113    /// ```no_run
114    /// # use telegram_webapp_sdk::webapp::{TelegramWebApp, BottomButton};
115    /// # let app = TelegramWebApp::instance().unwrap();
116    /// let _ = app.set_bottom_button_text_color(BottomButton::Main, "#ffffff");
117    /// ```
118    pub fn set_bottom_button_text_color(
119        &self,
120        button: BottomButton,
121        color: &str
122    ) -> Result<(), JsValue> {
123        self.bottom_button_method(button, "setTextColor", Some(&color.into()))
124    }
125
126    /// Enable a bottom button, allowing user interaction.
127    ///
128    /// # Examples
129    /// ```no_run
130    /// use telegram_webapp_sdk::webapp::{BottomButton, TelegramWebApp};
131    ///
132    /// if let Some(app) = TelegramWebApp::instance() {
133    ///     let _ = app.enable_bottom_button(BottomButton::Main);
134    /// }
135    /// ```
136    pub fn enable_bottom_button(&self, button: BottomButton) -> Result<(), JsValue> {
137        self.bottom_button_method(button, "enable", None)
138    }
139
140    /// Disable a bottom button, preventing user interaction.
141    ///
142    /// # Examples
143    /// ```no_run
144    /// use telegram_webapp_sdk::webapp::{BottomButton, TelegramWebApp};
145    ///
146    /// if let Some(app) = TelegramWebApp::instance() {
147    ///     let _ = app.disable_bottom_button(BottomButton::Main);
148    /// }
149    /// ```
150    pub fn disable_bottom_button(&self, button: BottomButton) -> Result<(), JsValue> {
151        self.bottom_button_method(button, "disable", None)
152    }
153
154    /// Show the circular loading indicator on a bottom button.
155    ///
156    /// # Examples
157    /// ```no_run
158    /// use telegram_webapp_sdk::webapp::{BottomButton, TelegramWebApp};
159    ///
160    /// if let Some(app) = TelegramWebApp::instance() {
161    ///     let _ = app.show_bottom_button_progress(BottomButton::Main, false);
162    /// }
163    /// ```
164    pub fn show_bottom_button_progress(
165        &self,
166        button: BottomButton,
167        leave_active: bool
168    ) -> Result<(), JsValue> {
169        let leave_active = JsValue::from_bool(leave_active);
170        self.bottom_button_method(button, "showProgress", Some(&leave_active))
171    }
172
173    /// Hide the loading indicator on a bottom button.
174    ///
175    /// # Examples
176    /// ```no_run
177    /// use telegram_webapp_sdk::webapp::{BottomButton, TelegramWebApp};
178    ///
179    /// if let Some(app) = TelegramWebApp::instance() {
180    ///     let _ = app.hide_bottom_button_progress(BottomButton::Main);
181    /// }
182    /// ```
183    pub fn hide_bottom_button_progress(&self, button: BottomButton) -> Result<(), JsValue> {
184        self.bottom_button_method(button, "hideProgress", None)
185    }
186
187    /// Returns whether the specified bottom button is currently visible.
188    ///
189    /// # Examples
190    /// ```no_run
191    /// use telegram_webapp_sdk::webapp::{BottomButton, TelegramWebApp};
192    ///
193    /// if let Some(app) = TelegramWebApp::instance() {
194    ///     let _ = app.is_bottom_button_visible(BottomButton::Main);
195    /// }
196    /// ```
197    pub fn is_bottom_button_visible(&self, button: BottomButton) -> bool {
198        self.bottom_button_property(button, "isVisible")
199            .and_then(|v| v.as_bool())
200            .unwrap_or(false)
201    }
202
203    /// Returns whether the specified bottom button is active (enabled).
204    ///
205    /// # Examples
206    /// ```no_run
207    /// use telegram_webapp_sdk::webapp::{BottomButton, TelegramWebApp};
208    ///
209    /// if let Some(app) = TelegramWebApp::instance() {
210    ///     let _ = app.is_bottom_button_active(BottomButton::Main);
211    /// }
212    /// ```
213    pub fn is_bottom_button_active(&self, button: BottomButton) -> bool {
214        self.bottom_button_property(button, "isActive")
215            .and_then(|v| v.as_bool())
216            .unwrap_or(false)
217    }
218
219    /// Returns whether the progress indicator is visible on the button.
220    ///
221    /// # Examples
222    /// ```no_run
223    /// use telegram_webapp_sdk::webapp::{BottomButton, TelegramWebApp};
224    ///
225    /// if let Some(app) = TelegramWebApp::instance() {
226    ///     let _ = app.is_bottom_button_progress_visible(BottomButton::Main);
227    /// }
228    /// ```
229    pub fn is_bottom_button_progress_visible(&self, button: BottomButton) -> bool {
230        self.bottom_button_property(button, "isProgressVisible")
231            .and_then(|v| v.as_bool())
232            .unwrap_or(false)
233    }
234
235    /// Returns the current text displayed on the button.
236    ///
237    /// # Examples
238    /// ```no_run
239    /// use telegram_webapp_sdk::webapp::{BottomButton, TelegramWebApp};
240    ///
241    /// if let Some(app) = TelegramWebApp::instance() {
242    ///     let _ = app.bottom_button_text(BottomButton::Main);
243    /// }
244    /// ```
245    pub fn bottom_button_text(&self, button: BottomButton) -> Option<String> {
246        self.bottom_button_property(button, "text")?.as_string()
247    }
248
249    /// Returns the current text color of the button.
250    ///
251    /// # Examples
252    /// ```no_run
253    /// use telegram_webapp_sdk::webapp::{BottomButton, TelegramWebApp};
254    ///
255    /// if let Some(app) = TelegramWebApp::instance() {
256    ///     let _ = app.bottom_button_text_color(BottomButton::Main);
257    /// }
258    /// ```
259    pub fn bottom_button_text_color(&self, button: BottomButton) -> Option<String> {
260        self.bottom_button_property(button, "textColor")?
261            .as_string()
262    }
263
264    /// Returns the current background color of the button.
265    ///
266    /// # Examples
267    /// ```no_run
268    /// use telegram_webapp_sdk::webapp::{BottomButton, TelegramWebApp};
269    ///
270    /// if let Some(app) = TelegramWebApp::instance() {
271    ///     let _ = app.bottom_button_color(BottomButton::Main);
272    /// }
273    /// ```
274    pub fn bottom_button_color(&self, button: BottomButton) -> Option<String> {
275        self.bottom_button_property(button, "color")?.as_string()
276    }
277
278    /// Returns whether the shine effect is enabled on the button.
279    ///
280    /// # Examples
281    /// ```no_run
282    /// use telegram_webapp_sdk::webapp::{BottomButton, TelegramWebApp};
283    ///
284    /// if let Some(app) = TelegramWebApp::instance() {
285    ///     let _ = app.bottom_button_has_shine_effect(BottomButton::Main);
286    /// }
287    /// ```
288    pub fn bottom_button_has_shine_effect(&self, button: BottomButton) -> bool {
289        self.bottom_button_property(button, "hasShineEffect")
290            .and_then(|v| v.as_bool())
291            .unwrap_or(false)
292    }
293
294    /// Update bottom button state via `setParams`.
295    ///
296    /// # Examples
297    /// ```no_run
298    /// use telegram_webapp_sdk::webapp::{BottomButton, BottomButtonParams, TelegramWebApp};
299    ///
300    /// if let Some(app) = TelegramWebApp::instance() {
301    ///     let params = BottomButtonParams {
302    ///         text: Some("Send"),
303    ///         ..Default::default()
304    ///     };
305    ///     let _ = app.set_bottom_button_params(BottomButton::Main, &params);
306    /// }
307    /// ```
308    pub fn set_bottom_button_params(
309        &self,
310        button: BottomButton,
311        params: &BottomButtonParams<'_>
312    ) -> Result<(), JsValue> {
313        let value = to_value(params).map_err(|err| JsValue::from_str(&err.to_string()))?;
314        self.bottom_button_method(button, "setParams", Some(&value))
315    }
316
317    /// Update secondary button state via `setParams`, including position.
318    ///
319    /// # Examples
320    /// ```no_run
321    /// use telegram_webapp_sdk::webapp::{
322    ///     SecondaryButtonParams, SecondaryButtonPosition, TelegramWebApp
323    /// };
324    ///
325    /// if let Some(app) = TelegramWebApp::instance() {
326    ///     let params = SecondaryButtonParams {
327    ///         position: Some(SecondaryButtonPosition::Left),
328    ///         ..Default::default()
329    ///     };
330    ///     let _ = app.set_secondary_button_params(&params);
331    /// }
332    /// ```
333    pub fn set_secondary_button_params(
334        &self,
335        params: &SecondaryButtonParams<'_>
336    ) -> Result<(), JsValue> {
337        let value = to_value(params).map_err(|err| JsValue::from_str(&err.to_string()))?;
338        self.bottom_button_method(BottomButton::Secondary, "setParams", Some(&value))
339    }
340
341    /// Returns the configured position of the secondary button, if available.
342    ///
343    /// # Examples
344    /// ```no_run
345    /// use telegram_webapp_sdk::webapp::{SecondaryButtonPosition, TelegramWebApp};
346    ///
347    /// if let Some(app) = TelegramWebApp::instance() {
348    ///     let _ = app.secondary_button_position();
349    /// }
350    /// ```
351    pub fn secondary_button_position(&self) -> Option<SecondaryButtonPosition> {
352        self.bottom_button_property(BottomButton::Secondary, "position")
353            .and_then(SecondaryButtonPosition::from_js_value)
354    }
355
356    /// Set callback for `onClick()` on a bottom button.
357    ///
358    /// Returns an [`EventHandle`] that can be used to remove the callback.
359    ///
360    /// # Errors
361    /// Returns [`JsValue`] if the underlying JS call fails.
362    pub fn set_bottom_button_callback<F>(
363        &self,
364        button: BottomButton,
365        callback: F
366    ) -> Result<EventHandle<dyn FnMut()>, JsValue>
367    where
368        F: 'static + Fn()
369    {
370        let btn_val = Reflect::get(&self.inner, &button.js_name().into())?;
371        let btn = btn_val.dyn_into::<Object>()?;
372        let cb = Closure::<dyn FnMut()>::new(callback);
373        let f = Reflect::get(&btn, &"onClick".into())?;
374        let func = f
375            .dyn_ref::<Function>()
376            .ok_or_else(|| JsValue::from_str("onClick is not a function"))?;
377        func.call1(&btn, cb.as_ref().unchecked_ref())?;
378        Ok(EventHandle::new(btn, "offClick", None, cb))
379    }
380
381    /// Remove previously set bottom button callback.
382    ///
383    /// # Errors
384    /// Returns [`JsValue`] if the underlying JS call fails.
385    pub fn remove_bottom_button_callback(
386        &self,
387        handle: EventHandle<dyn FnMut()>
388    ) -> Result<(), JsValue> {
389        handle.unregister()
390    }
391
392    // === Back button operations ===
393
394    /// Show back button.
395    ///
396    /// # Errors
397    /// Returns [`JsValue`] if the underlying JS call fails.
398    pub fn show_back_button(&self) -> Result<(), JsValue> {
399        self.call_nested0("BackButton", "show")
400    }
401
402    /// Hide back button.
403    ///
404    /// # Errors
405    /// Returns [`JsValue`] if the underlying JS call fails.
406    pub fn hide_back_button(&self) -> Result<(), JsValue> {
407        self.call_nested0("BackButton", "hide")
408    }
409
410    /// Registers a callback for the native back button.
411    ///
412    /// Returns an [`EventHandle`] that can be passed to
413    /// [`remove_back_button_callback`](Self::remove_back_button_callback).
414    ///
415    /// # Examples
416    /// ```no_run
417    /// # use telegram_webapp_sdk::webapp::TelegramWebApp;
418    /// # let app = TelegramWebApp::instance().unwrap();
419    /// let handle = app.set_back_button_callback(|| {}).expect("callback");
420    /// app.remove_back_button_callback(handle).unwrap();
421    /// ```
422    ///
423    /// # Errors
424    /// Returns [`JsValue`] if the underlying JS call fails.
425    pub fn set_back_button_callback<F>(
426        &self,
427        callback: F
428    ) -> Result<EventHandle<dyn FnMut()>, JsValue>
429    where
430        F: 'static + Fn()
431    {
432        let back_button_val = Reflect::get(&self.inner, &"BackButton".into())?;
433        let back_button = back_button_val.dyn_into::<Object>()?;
434        let cb = Closure::<dyn FnMut()>::new(callback);
435        let f = Reflect::get(&back_button, &"onClick".into())?;
436        let func = f
437            .dyn_ref::<Function>()
438            .ok_or_else(|| JsValue::from_str("onClick is not a function"))?;
439        func.call1(&back_button, cb.as_ref().unchecked_ref())?;
440        Ok(EventHandle::new(back_button, "offClick", None, cb))
441    }
442
443    /// Remove previously set back button callback.
444    ///
445    /// # Errors
446    /// Returns [`JsValue`] if the underlying JS call fails.
447    pub fn remove_back_button_callback(
448        &self,
449        handle: EventHandle<dyn FnMut()>
450    ) -> Result<(), JsValue> {
451        handle.unregister()
452    }
453
454    /// Returns whether the native back button is visible.
455    ///
456    /// # Examples
457    /// ```no_run
458    /// # use telegram_webapp_sdk::webapp::TelegramWebApp;
459    /// # let app = TelegramWebApp::instance().unwrap();
460    /// let _ = app.is_back_button_visible();
461    /// ```
462    pub fn is_back_button_visible(&self) -> bool {
463        Reflect::get(&self.inner, &"BackButton".into())
464            .ok()
465            .and_then(|bb| Reflect::get(&bb, &"isVisible".into()).ok())
466            .and_then(|v| v.as_bool())
467            .unwrap_or(false)
468    }
469
470    // === Legacy aliases for main button ===
471
472    /// Legacy alias for [`Self::show_bottom_button`] with
473    /// [`BottomButton::Main`].
474    pub fn show_main_button(&self) -> Result<(), JsValue> {
475        self.show_bottom_button(BottomButton::Main)
476    }
477
478    /// Legacy alias for [`Self::hide_bottom_button`] with
479    /// [`BottomButton::Main`].
480    pub fn hide_main_button(&self) -> Result<(), JsValue> {
481        self.hide_bottom_button(BottomButton::Main)
482    }
483
484    /// Legacy alias for [`Self::set_bottom_button_text`] with
485    /// [`BottomButton::Main`].
486    pub fn set_main_button_text(&self, text: &str) -> Result<(), JsValue> {
487        self.set_bottom_button_text(BottomButton::Main, text)
488    }
489
490    /// Legacy alias for [`Self::set_bottom_button_color`] with
491    /// [`BottomButton::Main`].
492    pub fn set_main_button_color(&self, color: &str) -> Result<(), JsValue> {
493        self.set_bottom_button_color(BottomButton::Main, color)
494    }
495
496    /// Legacy alias for [`Self::set_bottom_button_text_color`] with
497    /// [`BottomButton::Main`].
498    pub fn set_main_button_text_color(&self, color: &str) -> Result<(), JsValue> {
499        self.set_bottom_button_text_color(BottomButton::Main, color)
500    }
501
502    /// Enable the main bottom button.
503    ///
504    /// # Examples
505    /// ```no_run
506    /// use telegram_webapp_sdk::webapp::TelegramWebApp;
507    ///
508    /// if let Some(app) = TelegramWebApp::instance() {
509    ///     let _ = app.enable_main_button();
510    /// }
511    /// ```
512    pub fn enable_main_button(&self) -> Result<(), JsValue> {
513        self.enable_bottom_button(BottomButton::Main)
514    }
515
516    /// Disable the main bottom button.
517    ///
518    /// # Examples
519    /// ```no_run
520    /// use telegram_webapp_sdk::webapp::TelegramWebApp;
521    ///
522    /// if let Some(app) = TelegramWebApp::instance() {
523    ///     let _ = app.disable_main_button();
524    /// }
525    /// ```
526    pub fn disable_main_button(&self) -> Result<(), JsValue> {
527        self.disable_bottom_button(BottomButton::Main)
528    }
529
530    /// Show progress on the main bottom button.
531    ///
532    /// # Examples
533    /// ```no_run
534    /// use telegram_webapp_sdk::webapp::TelegramWebApp;
535    ///
536    /// if let Some(app) = TelegramWebApp::instance() {
537    ///     let _ = app.show_main_button_progress(false);
538    /// }
539    /// ```
540    pub fn show_main_button_progress(&self, leave_active: bool) -> Result<(), JsValue> {
541        self.show_bottom_button_progress(BottomButton::Main, leave_active)
542    }
543
544    /// Hide progress indicator from the main bottom button.
545    ///
546    /// # Examples
547    /// ```no_run
548    /// use telegram_webapp_sdk::webapp::TelegramWebApp;
549    ///
550    /// if let Some(app) = TelegramWebApp::instance() {
551    ///     let _ = app.hide_main_button_progress();
552    /// }
553    /// ```
554    pub fn hide_main_button_progress(&self) -> Result<(), JsValue> {
555        self.hide_bottom_button_progress(BottomButton::Main)
556    }
557
558    /// Update the main button state via
559    /// [`set_bottom_button_params`](Self::set_bottom_button_params).
560    pub fn set_main_button_params(&self, params: &BottomButtonParams<'_>) -> Result<(), JsValue> {
561        self.set_bottom_button_params(BottomButton::Main, params)
562    }
563
564    /// Legacy alias for [`Self::set_bottom_button_callback`] with
565    /// [`BottomButton::Main`].
566    pub fn set_main_button_callback<F>(
567        &self,
568        callback: F
569    ) -> Result<EventHandle<dyn FnMut()>, JsValue>
570    where
571        F: 'static + Fn()
572    {
573        self.set_bottom_button_callback(BottomButton::Main, callback)
574    }
575
576    /// Legacy alias for [`Self::remove_bottom_button_callback`].
577    pub fn remove_main_button_callback(
578        &self,
579        handle: EventHandle<dyn FnMut()>
580    ) -> Result<(), JsValue> {
581        self.remove_bottom_button_callback(handle)
582    }
583
584    // === Secondary button convenience methods ===
585
586    /// Show the secondary bottom button.
587    pub fn show_secondary_button(&self) -> Result<(), JsValue> {
588        self.show_bottom_button(BottomButton::Secondary)
589    }
590
591    /// Hide the secondary bottom button.
592    pub fn hide_secondary_button(&self) -> Result<(), JsValue> {
593        self.hide_bottom_button(BottomButton::Secondary)
594    }
595
596    /// Set text for the secondary bottom button.
597    pub fn set_secondary_button_text(&self, text: &str) -> Result<(), JsValue> {
598        self.set_bottom_button_text(BottomButton::Secondary, text)
599    }
600
601    /// Set color for the secondary bottom button.
602    pub fn set_secondary_button_color(&self, color: &str) -> Result<(), JsValue> {
603        self.set_bottom_button_color(BottomButton::Secondary, color)
604    }
605
606    /// Set text color for the secondary bottom button.
607    pub fn set_secondary_button_text_color(&self, color: &str) -> Result<(), JsValue> {
608        self.set_bottom_button_text_color(BottomButton::Secondary, color)
609    }
610
611    /// Enable the secondary bottom button.
612    ///
613    /// # Examples
614    /// ```no_run
615    /// use telegram_webapp_sdk::webapp::TelegramWebApp;
616    ///
617    /// if let Some(app) = TelegramWebApp::instance() {
618    ///     let _ = app.enable_secondary_button();
619    /// }
620    /// ```
621    pub fn enable_secondary_button(&self) -> Result<(), JsValue> {
622        self.enable_bottom_button(BottomButton::Secondary)
623    }
624
625    /// Disable the secondary bottom button.
626    ///
627    /// # Examples
628    /// ```no_run
629    /// use telegram_webapp_sdk::webapp::TelegramWebApp;
630    ///
631    /// if let Some(app) = TelegramWebApp::instance() {
632    ///     let _ = app.disable_secondary_button();
633    /// }
634    /// ```
635    pub fn disable_secondary_button(&self) -> Result<(), JsValue> {
636        self.disable_bottom_button(BottomButton::Secondary)
637    }
638
639    /// Show progress on the secondary bottom button.
640    ///
641    /// # Examples
642    /// ```no_run
643    /// use telegram_webapp_sdk::webapp::TelegramWebApp;
644    ///
645    /// if let Some(app) = TelegramWebApp::instance() {
646    ///     let _ = app.show_secondary_button_progress(false);
647    /// }
648    /// ```
649    pub fn show_secondary_button_progress(&self, leave_active: bool) -> Result<(), JsValue> {
650        self.show_bottom_button_progress(BottomButton::Secondary, leave_active)
651    }
652
653    /// Hide progress indicator from the secondary bottom button.
654    ///
655    /// # Examples
656    /// ```no_run
657    /// use telegram_webapp_sdk::webapp::TelegramWebApp;
658    ///
659    /// if let Some(app) = TelegramWebApp::instance() {
660    ///     let _ = app.hide_secondary_button_progress();
661    /// }
662    /// ```
663    pub fn hide_secondary_button_progress(&self) -> Result<(), JsValue> {
664        self.hide_bottom_button_progress(BottomButton::Secondary)
665    }
666
667    /// Set callback for the secondary bottom button.
668    pub fn set_secondary_button_callback<F>(
669        &self,
670        callback: F
671    ) -> Result<EventHandle<dyn FnMut()>, JsValue>
672    where
673        F: 'static + Fn()
674    {
675        self.set_bottom_button_callback(BottomButton::Secondary, callback)
676    }
677
678    /// Remove callback for the secondary bottom button.
679    pub fn remove_secondary_button_callback(
680        &self,
681        handle: EventHandle<dyn FnMut()>
682    ) -> Result<(), JsValue> {
683        self.remove_bottom_button_callback(handle)
684    }
685
686    /// Hide the on-screen keyboard.
687    /// Call `WebApp.hideKeyboard()`.
688    ///
689    /// # Examples
690    /// ```no_run
691    /// # use telegram_webapp_sdk::webapp::TelegramWebApp;
692    /// # let app = TelegramWebApp::instance().unwrap();
693    /// app.hide_keyboard().unwrap();
694    /// ```
695    ///
696    /// # Errors
697    /// Returns [`JsValue`] if the underlying JS call fails.
698    pub fn hide_keyboard(&self) -> Result<(), JsValue> {
699        self.call0("hideKeyboard")
700    }
701}