Skip to main content

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    /// Set custom emoji icon for a bottom button (Bot API 9.5+).
127    ///
128    /// Sets the custom emoji ID to be displayed as an icon on the button.
129    /// Use an empty string to remove the icon.
130    ///
131    /// # Arguments
132    /// * `button` - The button to update (Main or Secondary)
133    /// * `icon_id` - The custom emoji ID (e.g., "123456789")
134    ///
135    /// # Errors
136    /// Returns [`JsValue`] if the underlying JS call fails.
137    ///
138    /// # Examples
139    /// ```no_run
140    /// use telegram_webapp_sdk::webapp::{BottomButton, TelegramWebApp};
141    ///
142    /// if let Some(app) = TelegramWebApp::instance() {
143    ///     let _ = app.set_bottom_button_icon_custom_emoji_id(BottomButton::Main, "123456789");
144    /// }
145    /// ```
146    pub fn set_bottom_button_icon_custom_emoji_id(
147        &self,
148        button: BottomButton,
149        icon_id: &str
150    ) -> Result<(), JsValue> {
151        self.bottom_button_method(button, "setIconCustomEmojiId", Some(&icon_id.into()))
152    }
153
154    /// Enable a bottom button, allowing user interaction.
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.enable_bottom_button(BottomButton::Main);
162    /// }
163    /// ```
164    pub fn enable_bottom_button(&self, button: BottomButton) -> Result<(), JsValue> {
165        self.bottom_button_method(button, "enable", None)
166    }
167
168    /// Disable a bottom button, preventing user interaction.
169    ///
170    /// # Examples
171    /// ```no_run
172    /// use telegram_webapp_sdk::webapp::{BottomButton, TelegramWebApp};
173    ///
174    /// if let Some(app) = TelegramWebApp::instance() {
175    ///     let _ = app.disable_bottom_button(BottomButton::Main);
176    /// }
177    /// ```
178    pub fn disable_bottom_button(&self, button: BottomButton) -> Result<(), JsValue> {
179        self.bottom_button_method(button, "disable", None)
180    }
181
182    /// Show the circular loading indicator on a bottom button.
183    ///
184    /// # Examples
185    /// ```no_run
186    /// use telegram_webapp_sdk::webapp::{BottomButton, TelegramWebApp};
187    ///
188    /// if let Some(app) = TelegramWebApp::instance() {
189    ///     let _ = app.show_bottom_button_progress(BottomButton::Main, false);
190    /// }
191    /// ```
192    pub fn show_bottom_button_progress(
193        &self,
194        button: BottomButton,
195        leave_active: bool
196    ) -> Result<(), JsValue> {
197        let leave_active = JsValue::from_bool(leave_active);
198        self.bottom_button_method(button, "showProgress", Some(&leave_active))
199    }
200
201    /// Hide the loading indicator on a bottom button.
202    ///
203    /// # Examples
204    /// ```no_run
205    /// use telegram_webapp_sdk::webapp::{BottomButton, TelegramWebApp};
206    ///
207    /// if let Some(app) = TelegramWebApp::instance() {
208    ///     let _ = app.hide_bottom_button_progress(BottomButton::Main);
209    /// }
210    /// ```
211    pub fn hide_bottom_button_progress(&self, button: BottomButton) -> Result<(), JsValue> {
212        self.bottom_button_method(button, "hideProgress", None)
213    }
214
215    /// Returns whether the specified bottom button is currently visible.
216    ///
217    /// # Examples
218    /// ```no_run
219    /// use telegram_webapp_sdk::webapp::{BottomButton, TelegramWebApp};
220    ///
221    /// if let Some(app) = TelegramWebApp::instance() {
222    ///     let _ = app.is_bottom_button_visible(BottomButton::Main);
223    /// }
224    /// ```
225    pub fn is_bottom_button_visible(&self, button: BottomButton) -> bool {
226        self.bottom_button_property(button, "isVisible")
227            .and_then(|v| v.as_bool())
228            .unwrap_or(false)
229    }
230
231    /// Returns whether the specified bottom button is active (enabled).
232    ///
233    /// # Examples
234    /// ```no_run
235    /// use telegram_webapp_sdk::webapp::{BottomButton, TelegramWebApp};
236    ///
237    /// if let Some(app) = TelegramWebApp::instance() {
238    ///     let _ = app.is_bottom_button_active(BottomButton::Main);
239    /// }
240    /// ```
241    pub fn is_bottom_button_active(&self, button: BottomButton) -> bool {
242        self.bottom_button_property(button, "isActive")
243            .and_then(|v| v.as_bool())
244            .unwrap_or(false)
245    }
246
247    /// Returns whether the progress indicator is visible on the button.
248    ///
249    /// # Examples
250    /// ```no_run
251    /// use telegram_webapp_sdk::webapp::{BottomButton, TelegramWebApp};
252    ///
253    /// if let Some(app) = TelegramWebApp::instance() {
254    ///     let _ = app.is_bottom_button_progress_visible(BottomButton::Main);
255    /// }
256    /// ```
257    pub fn is_bottom_button_progress_visible(&self, button: BottomButton) -> bool {
258        self.bottom_button_property(button, "isProgressVisible")
259            .and_then(|v| v.as_bool())
260            .unwrap_or(false)
261    }
262
263    /// Returns the current text displayed on the button.
264    ///
265    /// # Examples
266    /// ```no_run
267    /// use telegram_webapp_sdk::webapp::{BottomButton, TelegramWebApp};
268    ///
269    /// if let Some(app) = TelegramWebApp::instance() {
270    ///     let _ = app.bottom_button_text(BottomButton::Main);
271    /// }
272    /// ```
273    pub fn bottom_button_text(&self, button: BottomButton) -> Option<String> {
274        self.bottom_button_property(button, "text")?.as_string()
275    }
276
277    /// Returns the current custom emoji icon ID of the button (Bot API 9.5+).
278    ///
279    /// # Arguments
280    /// * `button` - The button to query (Main or Secondary)
281    ///
282    /// # Examples
283    /// ```no_run
284    /// use telegram_webapp_sdk::webapp::{BottomButton, TelegramWebApp};
285    ///
286    /// if let Some(app) = TelegramWebApp::instance() {
287    ///     if let Some(icon_id) = app.bottom_button_icon_custom_emoji_id(BottomButton::Main) {
288    ///         println!("Icon ID: {}", icon_id);
289    ///     }
290    /// }
291    /// ```
292    pub fn bottom_button_icon_custom_emoji_id(&self, button: BottomButton) -> Option<String> {
293        self.bottom_button_property(button, "iconCustomEmojiId")?
294            .as_string()
295    }
296
297    /// Returns the current text color of the button.
298    ///
299    /// # Examples
300    /// ```no_run
301    /// use telegram_webapp_sdk::webapp::{BottomButton, TelegramWebApp};
302    ///
303    /// if let Some(app) = TelegramWebApp::instance() {
304    ///     let _ = app.bottom_button_text_color(BottomButton::Main);
305    /// }
306    /// ```
307    pub fn bottom_button_text_color(&self, button: BottomButton) -> Option<String> {
308        self.bottom_button_property(button, "textColor")?
309            .as_string()
310    }
311
312    /// Returns the current background color of the button.
313    ///
314    /// # Examples
315    /// ```no_run
316    /// use telegram_webapp_sdk::webapp::{BottomButton, TelegramWebApp};
317    ///
318    /// if let Some(app) = TelegramWebApp::instance() {
319    ///     let _ = app.bottom_button_color(BottomButton::Main);
320    /// }
321    /// ```
322    pub fn bottom_button_color(&self, button: BottomButton) -> Option<String> {
323        self.bottom_button_property(button, "color")?.as_string()
324    }
325
326    /// Returns whether the shine effect is enabled on the button.
327    ///
328    /// # Examples
329    /// ```no_run
330    /// use telegram_webapp_sdk::webapp::{BottomButton, TelegramWebApp};
331    ///
332    /// if let Some(app) = TelegramWebApp::instance() {
333    ///     let _ = app.bottom_button_has_shine_effect(BottomButton::Main);
334    /// }
335    /// ```
336    pub fn bottom_button_has_shine_effect(&self, button: BottomButton) -> bool {
337        self.bottom_button_property(button, "hasShineEffect")
338            .and_then(|v| v.as_bool())
339            .unwrap_or(false)
340    }
341
342    /// Update bottom button state via `setParams`.
343    ///
344    /// # Examples
345    /// ```no_run
346    /// use telegram_webapp_sdk::webapp::{BottomButton, BottomButtonParams, TelegramWebApp};
347    ///
348    /// if let Some(app) = TelegramWebApp::instance() {
349    ///     let params = BottomButtonParams {
350    ///         text: Some("Send"),
351    ///         ..Default::default()
352    ///     };
353    ///     let _ = app.set_bottom_button_params(BottomButton::Main, &params);
354    /// }
355    /// ```
356    pub fn set_bottom_button_params(
357        &self,
358        button: BottomButton,
359        params: &BottomButtonParams<'_>
360    ) -> Result<(), JsValue> {
361        let value = to_value(params).map_err(|err| JsValue::from_str(&err.to_string()))?;
362        self.bottom_button_method(button, "setParams", Some(&value))
363    }
364
365    /// Update secondary button state via `setParams`, including position.
366    ///
367    /// # Examples
368    /// ```no_run
369    /// use telegram_webapp_sdk::webapp::{
370    ///     SecondaryButtonParams, SecondaryButtonPosition, TelegramWebApp
371    /// };
372    ///
373    /// if let Some(app) = TelegramWebApp::instance() {
374    ///     let params = SecondaryButtonParams {
375    ///         position: Some(SecondaryButtonPosition::Left),
376    ///         ..Default::default()
377    ///     };
378    ///     let _ = app.set_secondary_button_params(&params);
379    /// }
380    /// ```
381    pub fn set_secondary_button_params(
382        &self,
383        params: &SecondaryButtonParams<'_>
384    ) -> Result<(), JsValue> {
385        let value = to_value(params).map_err(|err| JsValue::from_str(&err.to_string()))?;
386        self.bottom_button_method(BottomButton::Secondary, "setParams", Some(&value))
387    }
388
389    /// Returns the configured position of the secondary button, if available.
390    ///
391    /// # Examples
392    /// ```no_run
393    /// use telegram_webapp_sdk::webapp::{SecondaryButtonPosition, TelegramWebApp};
394    ///
395    /// if let Some(app) = TelegramWebApp::instance() {
396    ///     let _ = app.secondary_button_position();
397    /// }
398    /// ```
399    pub fn secondary_button_position(&self) -> Option<SecondaryButtonPosition> {
400        self.bottom_button_property(BottomButton::Secondary, "position")
401            .and_then(SecondaryButtonPosition::from_js_value)
402    }
403
404    /// Set callback for `onClick()` on a bottom button.
405    ///
406    /// Returns an [`EventHandle`] that can be used to remove the callback.
407    ///
408    /// # Errors
409    /// Returns [`JsValue`] if the underlying JS call fails.
410    pub fn set_bottom_button_callback<F>(
411        &self,
412        button: BottomButton,
413        callback: F
414    ) -> Result<EventHandle<dyn FnMut()>, JsValue>
415    where
416        F: 'static + Fn()
417    {
418        let btn_val = Reflect::get(&self.inner, &button.js_name().into())?;
419        let btn = btn_val.dyn_into::<Object>()?;
420        let cb = Closure::<dyn FnMut()>::new(callback);
421        let f = Reflect::get(&btn, &"onClick".into())?;
422        let func = f
423            .dyn_ref::<Function>()
424            .ok_or_else(|| JsValue::from_str("onClick is not a function"))?;
425        func.call1(&btn, cb.as_ref().unchecked_ref())?;
426        Ok(EventHandle::new(btn, "offClick", None, cb))
427    }
428
429    /// Remove previously set bottom button callback.
430    ///
431    /// # Errors
432    /// Returns [`JsValue`] if the underlying JS call fails.
433    pub fn remove_bottom_button_callback(
434        &self,
435        handle: EventHandle<dyn FnMut()>
436    ) -> Result<(), JsValue> {
437        handle.unregister()
438    }
439
440    // === Back button operations ===
441
442    /// Show back button.
443    ///
444    /// # Errors
445    /// Returns [`JsValue`] if the underlying JS call fails.
446    pub fn show_back_button(&self) -> Result<(), JsValue> {
447        self.call_nested0("BackButton", "show")
448    }
449
450    /// Hide back button.
451    ///
452    /// # Errors
453    /// Returns [`JsValue`] if the underlying JS call fails.
454    pub fn hide_back_button(&self) -> Result<(), JsValue> {
455        self.call_nested0("BackButton", "hide")
456    }
457
458    /// Registers a callback for the native back button.
459    ///
460    /// Returns an [`EventHandle`] that can be passed to
461    /// [`remove_back_button_callback`](Self::remove_back_button_callback).
462    ///
463    /// # Examples
464    /// ```no_run
465    /// # use telegram_webapp_sdk::webapp::TelegramWebApp;
466    /// # let app = TelegramWebApp::instance().unwrap();
467    /// let handle = app.set_back_button_callback(|| {}).expect("callback");
468    /// app.remove_back_button_callback(handle).unwrap();
469    /// ```
470    ///
471    /// # Errors
472    /// Returns [`JsValue`] if the underlying JS call fails.
473    pub fn set_back_button_callback<F>(
474        &self,
475        callback: F
476    ) -> Result<EventHandle<dyn FnMut()>, JsValue>
477    where
478        F: 'static + Fn()
479    {
480        let back_button_val = Reflect::get(&self.inner, &"BackButton".into())?;
481        let back_button = back_button_val.dyn_into::<Object>()?;
482        let cb = Closure::<dyn FnMut()>::new(callback);
483        let f = Reflect::get(&back_button, &"onClick".into())?;
484        let func = f
485            .dyn_ref::<Function>()
486            .ok_or_else(|| JsValue::from_str("onClick is not a function"))?;
487        func.call1(&back_button, cb.as_ref().unchecked_ref())?;
488        Ok(EventHandle::new(back_button, "offClick", None, cb))
489    }
490
491    /// Remove previously set back button callback.
492    ///
493    /// # Errors
494    /// Returns [`JsValue`] if the underlying JS call fails.
495    pub fn remove_back_button_callback(
496        &self,
497        handle: EventHandle<dyn FnMut()>
498    ) -> Result<(), JsValue> {
499        handle.unregister()
500    }
501
502    /// Returns whether the native back button is visible.
503    ///
504    /// # Examples
505    /// ```no_run
506    /// # use telegram_webapp_sdk::webapp::TelegramWebApp;
507    /// # let app = TelegramWebApp::instance().unwrap();
508    /// let _ = app.is_back_button_visible();
509    /// ```
510    pub fn is_back_button_visible(&self) -> bool {
511        Reflect::get(&self.inner, &"BackButton".into())
512            .ok()
513            .and_then(|bb| Reflect::get(&bb, &"isVisible".into()).ok())
514            .and_then(|v| v.as_bool())
515            .unwrap_or(false)
516    }
517
518    // === Settings button operations ===
519
520    /// Show the native settings button.
521    ///
522    /// # Errors
523    /// Returns [`JsValue`] if the underlying JS call fails.
524    pub fn show_settings_button(&self) -> Result<(), JsValue> {
525        self.call_nested0("SettingsButton", "show")
526    }
527
528    /// Hide the native settings button.
529    ///
530    /// # Errors
531    /// Returns [`JsValue`] if the underlying JS call fails.
532    pub fn hide_settings_button(&self) -> Result<(), JsValue> {
533        self.call_nested0("SettingsButton", "hide")
534    }
535
536    /// Registers a callback for the native settings button.
537    ///
538    /// Returns an [`EventHandle`] that can be passed to
539    /// [`remove_settings_button_callback`](Self::remove_settings_button_callback).
540    ///
541    /// # Errors
542    /// Returns [`JsValue`] if the underlying JS call fails.
543    pub fn set_settings_button_callback<F>(
544        &self,
545        callback: F
546    ) -> Result<EventHandle<dyn FnMut()>, JsValue>
547    where
548        F: 'static + Fn()
549    {
550        let button_val = Reflect::get(&self.inner, &"SettingsButton".into())?;
551        let button = button_val.dyn_into::<Object>()?;
552        let cb = Closure::<dyn FnMut()>::new(callback);
553        let f = Reflect::get(&button, &"onClick".into())?;
554        let func = f
555            .dyn_ref::<Function>()
556            .ok_or_else(|| JsValue::from_str("onClick is not a function"))?;
557        func.call1(&button, cb.as_ref().unchecked_ref())?;
558        Ok(EventHandle::new(button, "offClick", None, cb))
559    }
560
561    /// Remove previously set settings button callback.
562    ///
563    /// # Errors
564    /// Returns [`JsValue`] if the underlying JS call fails.
565    pub fn remove_settings_button_callback(
566        &self,
567        handle: EventHandle<dyn FnMut()>
568    ) -> Result<(), JsValue> {
569        handle.unregister()
570    }
571
572    /// Returns whether the native settings button is visible.
573    pub fn is_settings_button_visible(&self) -> bool {
574        Reflect::get(&self.inner, &"SettingsButton".into())
575            .ok()
576            .and_then(|bb| Reflect::get(&bb, &"isVisible".into()).ok())
577            .and_then(|v| v.as_bool())
578            .unwrap_or(false)
579    }
580
581    // === Legacy aliases for main button ===
582
583    /// Legacy alias for [`Self::show_bottom_button`] with
584    /// [`BottomButton::Main`].
585    pub fn show_main_button(&self) -> Result<(), JsValue> {
586        self.show_bottom_button(BottomButton::Main)
587    }
588
589    /// Legacy alias for [`Self::hide_bottom_button`] with
590    /// [`BottomButton::Main`].
591    pub fn hide_main_button(&self) -> Result<(), JsValue> {
592        self.hide_bottom_button(BottomButton::Main)
593    }
594
595    /// Legacy alias for [`Self::set_bottom_button_text`] with
596    /// [`BottomButton::Main`].
597    pub fn set_main_button_text(&self, text: &str) -> Result<(), JsValue> {
598        self.set_bottom_button_text(BottomButton::Main, text)
599    }
600
601    /// Legacy alias for [`Self::set_bottom_button_color`] with
602    /// [`BottomButton::Main`].
603    pub fn set_main_button_color(&self, color: &str) -> Result<(), JsValue> {
604        self.set_bottom_button_color(BottomButton::Main, color)
605    }
606
607    /// Legacy alias for [`Self::set_bottom_button_text_color`] with
608    /// [`BottomButton::Main`].
609    pub fn set_main_button_text_color(&self, color: &str) -> Result<(), JsValue> {
610        self.set_bottom_button_text_color(BottomButton::Main, color)
611    }
612
613    /// Set custom emoji icon for the main button (Bot API 9.5+).
614    ///
615    /// Legacy alias for [`Self::set_bottom_button_icon_custom_emoji_id`] with
616    /// [`BottomButton::Main`].
617    ///
618    /// # Examples
619    /// ```no_run
620    /// use telegram_webapp_sdk::webapp::TelegramWebApp;
621    ///
622    /// if let Some(app) = TelegramWebApp::instance() {
623    ///     let _ = app.set_main_button_icon_custom_emoji_id("123456789");
624    /// }
625    /// ```
626    pub fn set_main_button_icon_custom_emoji_id(&self, icon_id: &str) -> Result<(), JsValue> {
627        self.set_bottom_button_icon_custom_emoji_id(BottomButton::Main, icon_id)
628    }
629
630    /// Enable the main bottom button.
631    ///
632    /// # Examples
633    /// ```no_run
634    /// use telegram_webapp_sdk::webapp::TelegramWebApp;
635    ///
636    /// if let Some(app) = TelegramWebApp::instance() {
637    ///     let _ = app.enable_main_button();
638    /// }
639    /// ```
640    pub fn enable_main_button(&self) -> Result<(), JsValue> {
641        self.enable_bottom_button(BottomButton::Main)
642    }
643
644    /// Disable the main bottom button.
645    ///
646    /// # Examples
647    /// ```no_run
648    /// use telegram_webapp_sdk::webapp::TelegramWebApp;
649    ///
650    /// if let Some(app) = TelegramWebApp::instance() {
651    ///     let _ = app.disable_main_button();
652    /// }
653    /// ```
654    pub fn disable_main_button(&self) -> Result<(), JsValue> {
655        self.disable_bottom_button(BottomButton::Main)
656    }
657
658    /// Show progress on the main bottom button.
659    ///
660    /// # Examples
661    /// ```no_run
662    /// use telegram_webapp_sdk::webapp::TelegramWebApp;
663    ///
664    /// if let Some(app) = TelegramWebApp::instance() {
665    ///     let _ = app.show_main_button_progress(false);
666    /// }
667    /// ```
668    pub fn show_main_button_progress(&self, leave_active: bool) -> Result<(), JsValue> {
669        self.show_bottom_button_progress(BottomButton::Main, leave_active)
670    }
671
672    /// Hide progress indicator from the main bottom button.
673    ///
674    /// # Examples
675    /// ```no_run
676    /// use telegram_webapp_sdk::webapp::TelegramWebApp;
677    ///
678    /// if let Some(app) = TelegramWebApp::instance() {
679    ///     let _ = app.hide_main_button_progress();
680    /// }
681    /// ```
682    pub fn hide_main_button_progress(&self) -> Result<(), JsValue> {
683        self.hide_bottom_button_progress(BottomButton::Main)
684    }
685
686    /// Update the main button state via
687    /// [`set_bottom_button_params`](Self::set_bottom_button_params).
688    pub fn set_main_button_params(&self, params: &BottomButtonParams<'_>) -> Result<(), JsValue> {
689        self.set_bottom_button_params(BottomButton::Main, params)
690    }
691
692    /// Legacy alias for [`Self::set_bottom_button_callback`] with
693    /// [`BottomButton::Main`].
694    pub fn set_main_button_callback<F>(
695        &self,
696        callback: F
697    ) -> Result<EventHandle<dyn FnMut()>, JsValue>
698    where
699        F: 'static + Fn()
700    {
701        self.set_bottom_button_callback(BottomButton::Main, callback)
702    }
703
704    /// Remove callback for the main button.
705    ///
706    /// Legacy alias for [`Self::remove_bottom_button_callback`].
707    pub fn remove_main_button_callback(
708        &self,
709        handle: EventHandle<dyn FnMut()>
710    ) -> Result<(), JsValue> {
711        self.remove_bottom_button_callback(handle)
712    }
713
714    // === Secondary button convenience methods ===
715
716    /// Show the secondary bottom button.
717    pub fn show_secondary_button(&self) -> Result<(), JsValue> {
718        self.show_bottom_button(BottomButton::Secondary)
719    }
720
721    /// Hide the secondary bottom button.
722    pub fn hide_secondary_button(&self) -> Result<(), JsValue> {
723        self.hide_bottom_button(BottomButton::Secondary)
724    }
725
726    /// Set text for the secondary bottom button.
727    pub fn set_secondary_button_text(&self, text: &str) -> Result<(), JsValue> {
728        self.set_bottom_button_text(BottomButton::Secondary, text)
729    }
730
731    /// Set color for the secondary bottom button.
732    pub fn set_secondary_button_color(&self, color: &str) -> Result<(), JsValue> {
733        self.set_bottom_button_color(BottomButton::Secondary, color)
734    }
735
736    /// Set text color for the secondary bottom button.
737    pub fn set_secondary_button_text_color(&self, color: &str) -> Result<(), JsValue> {
738        self.set_bottom_button_text_color(BottomButton::Secondary, color)
739    }
740
741    /// Set custom emoji icon for the secondary button (Bot API 9.5+).
742    ///
743    /// Convenience alias for [`Self::set_bottom_button_icon_custom_emoji_id`]
744    /// with [`BottomButton::Secondary`].
745    ///
746    /// # Examples
747    /// ```no_run
748    /// use telegram_webapp_sdk::webapp::TelegramWebApp;
749    ///
750    /// if let Some(app) = TelegramWebApp::instance() {
751    ///     let _ = app.set_secondary_button_icon_custom_emoji_id("123456789");
752    /// }
753    /// ```
754    pub fn set_secondary_button_icon_custom_emoji_id(&self, icon_id: &str) -> Result<(), JsValue> {
755        self.set_bottom_button_icon_custom_emoji_id(BottomButton::Secondary, icon_id)
756    }
757
758    /// Enable the secondary bottom button.
759    ///
760    /// # Examples
761    /// ```no_run
762    /// use telegram_webapp_sdk::webapp::TelegramWebApp;
763    ///
764    /// if let Some(app) = TelegramWebApp::instance() {
765    ///     let _ = app.enable_secondary_button();
766    /// }
767    /// ```
768    pub fn enable_secondary_button(&self) -> Result<(), JsValue> {
769        self.enable_bottom_button(BottomButton::Secondary)
770    }
771
772    /// Disable the secondary bottom button.
773    ///
774    /// # Examples
775    /// ```no_run
776    /// use telegram_webapp_sdk::webapp::TelegramWebApp;
777    ///
778    /// if let Some(app) = TelegramWebApp::instance() {
779    ///     let _ = app.disable_secondary_button();
780    /// }
781    /// ```
782    pub fn disable_secondary_button(&self) -> Result<(), JsValue> {
783        self.disable_bottom_button(BottomButton::Secondary)
784    }
785
786    /// Show progress on the secondary bottom button.
787    ///
788    /// # Examples
789    /// ```no_run
790    /// use telegram_webapp_sdk::webapp::TelegramWebApp;
791    ///
792    /// if let Some(app) = TelegramWebApp::instance() {
793    ///     let _ = app.show_secondary_button_progress(false);
794    /// }
795    /// ```
796    pub fn show_secondary_button_progress(&self, leave_active: bool) -> Result<(), JsValue> {
797        self.show_bottom_button_progress(BottomButton::Secondary, leave_active)
798    }
799
800    /// Hide progress indicator from the secondary bottom button.
801    ///
802    /// # Examples
803    /// ```no_run
804    /// use telegram_webapp_sdk::webapp::TelegramWebApp;
805    ///
806    /// if let Some(app) = TelegramWebApp::instance() {
807    ///     let _ = app.hide_secondary_button_progress();
808    /// }
809    /// ```
810    pub fn hide_secondary_button_progress(&self) -> Result<(), JsValue> {
811        self.hide_bottom_button_progress(BottomButton::Secondary)
812    }
813
814    /// Set callback for the secondary bottom button.
815    pub fn set_secondary_button_callback<F>(
816        &self,
817        callback: F
818    ) -> Result<EventHandle<dyn FnMut()>, JsValue>
819    where
820        F: 'static + Fn()
821    {
822        self.set_bottom_button_callback(BottomButton::Secondary, callback)
823    }
824
825    /// Remove callback for the secondary bottom button.
826    pub fn remove_secondary_button_callback(
827        &self,
828        handle: EventHandle<dyn FnMut()>
829    ) -> Result<(), JsValue> {
830        self.remove_bottom_button_callback(handle)
831    }
832
833    /// Hide the on-screen keyboard.
834    /// Call `WebApp.hideKeyboard()`.
835    ///
836    /// # Examples
837    /// ```no_run
838    /// # use telegram_webapp_sdk::webapp::TelegramWebApp;
839    /// # let app = TelegramWebApp::instance().unwrap();
840    /// app.hide_keyboard().unwrap();
841    /// ```
842    ///
843    /// # Errors
844    /// Returns [`JsValue`] if the underlying JS call fails.
845    pub fn hide_keyboard(&self) -> Result<(), JsValue> {
846        self.call0("hideKeyboard")
847    }
848}