telegram_webapp_sdk/api/
haptic.rs

1use js_sys::{Function, Reflect};
2use wasm_bindgen::{JsCast, prelude::*};
3use web_sys::window;
4
5/// Available styles for [`impact_occurred`].
6#[derive(Debug, Clone, Copy)]
7pub enum HapticImpactStyle {
8    /// A light impact feedback.
9    Light,
10    /// A medium impact feedback.
11    Medium,
12    /// A heavy impact feedback.
13    Heavy,
14    /// A rigid impact feedback.
15    Rigid,
16    /// A soft impact feedback.
17    Soft
18}
19
20impl HapticImpactStyle {
21    const fn as_str(self) -> &'static str {
22        match self {
23            Self::Light => "light",
24            Self::Medium => "medium",
25            Self::Heavy => "heavy",
26            Self::Rigid => "rigid",
27            Self::Soft => "soft"
28        }
29    }
30}
31
32/// Available types for [`notification_occurred`].
33#[derive(Debug, Clone, Copy)]
34pub enum HapticNotificationType {
35    /// Error notification feedback.
36    Error,
37    /// Success notification feedback.
38    Success,
39    /// Warning notification feedback.
40    Warning
41}
42
43impl HapticNotificationType {
44    const fn as_str(self) -> &'static str {
45        match self {
46            Self::Error => "error",
47            Self::Success => "success",
48            Self::Warning => "warning"
49        }
50    }
51}
52
53/// Triggers a haptic impact feedback.
54///
55/// # Errors
56/// Returns `Err(JsValue)` if the JavaScript call fails or `HapticFeedback` is
57/// missing.
58///
59/// # Examples
60/// ```
61/// use telegram_webapp_sdk::api::haptic::{HapticImpactStyle, impact_occurred};
62/// # fn run() -> Result<(), wasm_bindgen::JsValue> {
63/// impact_occurred(HapticImpactStyle::Light)?;
64/// # Ok(()) }
65/// ```
66pub fn impact_occurred(style: HapticImpactStyle) -> Result<(), JsValue> {
67    let haptic = haptic_object()?;
68    let func = Reflect::get(&haptic, &"impactOccurred".into())?.dyn_into::<Function>()?;
69    func.call1(&haptic, &JsValue::from_str(style.as_str()))?;
70    Ok(())
71}
72
73/// Triggers a haptic notification feedback.
74///
75/// # Errors
76/// Returns `Err(JsValue)` if the JavaScript call fails or `HapticFeedback` is
77/// missing.
78///
79/// # Examples
80/// ```
81/// use telegram_webapp_sdk::api::haptic::{HapticNotificationType, notification_occurred};
82/// # fn run() -> Result<(), wasm_bindgen::JsValue> {
83/// notification_occurred(HapticNotificationType::Success)?;
84/// # Ok(()) }
85/// ```
86pub fn notification_occurred(ty: HapticNotificationType) -> Result<(), JsValue> {
87    let haptic = haptic_object()?;
88    let func = Reflect::get(&haptic, &"notificationOccurred".into())?.dyn_into::<Function>()?;
89    func.call1(&haptic, &JsValue::from_str(ty.as_str()))?;
90    Ok(())
91}
92
93/// Triggers a haptic selection change feedback.
94///
95/// # Errors
96/// Returns `Err(JsValue)` if the JavaScript call fails or `HapticFeedback` is
97/// missing.
98///
99/// # Examples
100/// ```
101/// use telegram_webapp_sdk::api::haptic::selection_changed;
102/// # fn run() -> Result<(), wasm_bindgen::JsValue> {
103/// selection_changed()?;
104/// # Ok(()) }
105/// ```
106pub fn selection_changed() -> Result<(), JsValue> {
107    let haptic = haptic_object()?;
108    let func = Reflect::get(&haptic, &"selectionChanged".into())?.dyn_into::<Function>()?;
109    func.call0(&haptic)?;
110    Ok(())
111}
112
113/// Internal helper to get `Telegram.WebApp.HapticFeedback` object.
114fn haptic_object() -> Result<JsValue, JsValue> {
115    let window = window().ok_or_else(|| JsValue::from_str("no window"))?;
116    let tg = Reflect::get(&window, &"Telegram".into())?;
117    let webapp = Reflect::get(&tg, &"WebApp".into())?;
118    Reflect::get(&webapp, &"HapticFeedback".into())
119}
120
121#[cfg(test)]
122mod tests {
123    use js_sys::{Object, Reflect};
124    use wasm_bindgen::prelude::*;
125    use wasm_bindgen_test::{wasm_bindgen_test, wasm_bindgen_test_configure};
126    use web_sys::window;
127
128    use super::*;
129
130    wasm_bindgen_test_configure!(run_in_browser);
131
132    #[allow(dead_code)]
133    fn setup_haptic() -> Object {
134        let win = window().unwrap();
135        let telegram = Object::new();
136        let webapp = Object::new();
137        let haptic = Object::new();
138        let _ = Reflect::set(&win, &"Telegram".into(), &telegram);
139        let _ = Reflect::set(&telegram, &"WebApp".into(), &webapp);
140        let _ = Reflect::set(&webapp, &"HapticFeedback".into(), &haptic);
141        haptic
142    }
143
144    #[wasm_bindgen_test]
145    #[allow(dead_code)]
146    fn impact_calls_js() {
147        let haptic = setup_haptic();
148        let _ = Reflect::set(&haptic, &"impact_called".into(), &JsValue::FALSE);
149        let haptic_clone = haptic.clone();
150        let closure = Closure::wrap(Box::new(move |_style: JsValue| {
151            let _ = Reflect::set(&haptic_clone, &"impact_called".into(), &JsValue::TRUE);
152        }) as Box<dyn FnMut(JsValue)>);
153        let _ = Reflect::set(&haptic, &"impactOccurred".into(), closure.as_ref());
154        closure.forget();
155        let _ = impact_occurred(HapticImpactStyle::Light);
156        let flag = Reflect::get(&haptic, &"impact_called".into()).unwrap();
157        assert!(flag.as_bool().unwrap());
158    }
159
160    #[wasm_bindgen_test]
161    #[allow(dead_code)]
162    fn notification_calls_js() {
163        let haptic = setup_haptic();
164        let _ = Reflect::set(&haptic, &"notification_called".into(), &JsValue::FALSE);
165        let haptic_clone = haptic.clone();
166        let closure = Closure::wrap(Box::new(move |_ty: JsValue| {
167            let _ = Reflect::set(&haptic_clone, &"notification_called".into(), &JsValue::TRUE);
168        }) as Box<dyn FnMut(JsValue)>);
169        let _ = Reflect::set(&haptic, &"notificationOccurred".into(), closure.as_ref());
170        closure.forget();
171        let _ = notification_occurred(HapticNotificationType::Error);
172        let flag = Reflect::get(&haptic, &"notification_called".into()).unwrap();
173        assert!(flag.as_bool().unwrap());
174    }
175
176    #[wasm_bindgen_test]
177    #[allow(dead_code)]
178    fn selection_calls_js() {
179        let haptic = setup_haptic();
180        let _ = Reflect::set(&haptic, &"selection_called".into(), &JsValue::FALSE);
181        let haptic_clone = haptic.clone();
182        let closure = Closure::wrap(Box::new(move || {
183            let _ = Reflect::set(&haptic_clone, &"selection_called".into(), &JsValue::TRUE);
184        }) as Box<dyn FnMut()>);
185        let _ = Reflect::set(&haptic, &"selectionChanged".into(), closure.as_ref());
186        closure.forget();
187        let _ = selection_changed();
188        let flag = Reflect::get(&haptic, &"selection_called".into()).unwrap();
189        assert!(flag.as_bool().unwrap());
190    }
191}