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, ¶ms);
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(¶ms);
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}