windjammer_ui/
component_runtime.rs1#[cfg(target_arch = "wasm32")]
8use wasm_bindgen::prelude::*;
9#[cfg(target_arch = "wasm32")]
10use web_sys::{Document, Element, Event, HtmlElement, Window};
11
12#[cfg(target_arch = "wasm32")]
14pub fn window() -> Result<Window, JsValue> {
15 web_sys::window().ok_or_else(|| JsValue::from_str("No window found"))
16}
17
18#[cfg(target_arch = "wasm32")]
20pub fn document() -> Result<Document, JsValue> {
21 window()?
22 .document()
23 .ok_or_else(|| JsValue::from_str("No document found"))
24}
25
26#[cfg(target_arch = "wasm32")]
28pub fn create_element(tag: &str) -> Result<Element, JsValue> {
29 document()?.create_element(tag)
30}
31
32#[cfg(target_arch = "wasm32")]
34pub fn set_text(element: &Element, text: &str) {
35 element.set_text_content(Some(text));
36}
37
38#[cfg(target_arch = "wasm32")]
40pub fn set_attribute(element: &Element, name: &str, value: &str) -> Result<(), JsValue> {
41 element.set_attribute(name, value)
42}
43
44#[cfg(target_arch = "wasm32")]
46pub fn add_class(element: &Element, class: &str) -> Result<(), JsValue> {
47 if let Some(html_element) = element.dyn_ref::<HtmlElement>() {
48 html_element.class_list().add_1(class)?;
49 }
50 Ok(())
51}
52
53#[cfg(target_arch = "wasm32")]
55pub fn remove_class(element: &Element, class: &str) -> Result<(), JsValue> {
56 if let Some(html_element) = element.dyn_ref::<HtmlElement>() {
57 html_element.class_list().remove_1(class)?;
58 }
59 Ok(())
60}
61
62#[cfg(target_arch = "wasm32")]
64pub fn append_child(parent: &Element, child: &Element) -> Result<(), JsValue> {
65 parent.append_child(child)?;
66 Ok(())
67}
68
69#[cfg(target_arch = "wasm32")]
71pub fn get_element_by_id(id: &str) -> Result<Element, JsValue> {
72 document()?
73 .get_element_by_id(id)
74 .ok_or_else(|| JsValue::from_str(&format!("Element with id '{}' not found", id)))
75}
76
77#[cfg(target_arch = "wasm32")]
79pub type EventCallback = Box<dyn FnMut(Event)>;
80
81#[cfg(target_arch = "wasm32")]
83pub fn create_event_listener<F>(mut handler: F) -> Closure<dyn FnMut(Event)>
84where
85 F: FnMut(Event) + 'static,
86{
87 Closure::wrap(Box::new(move |event: Event| {
88 handler(event);
89 }) as Box<dyn FnMut(Event)>)
90}
91
92#[cfg(target_arch = "wasm32")]
94pub fn add_event_listener(
95 element: &Element,
96 event_type: &str,
97 closure: &Closure<dyn FnMut(Event)>,
98) -> Result<(), JsValue> {
99 element.add_event_listener_with_callback(event_type, closure.as_ref().unchecked_ref())
100}
101
102#[cfg(target_arch = "wasm32")]
104pub fn remove_element(element: &Element) -> Result<(), JsValue> {
105 if let Some(parent) = element.parent_element() {
106 parent.remove_child(element)?;
107 }
108 Ok(())
109}
110
111#[cfg(target_arch = "wasm32")]
113pub fn toggle_class(element: &Element, class: &str) -> Result<(), JsValue> {
114 if let Some(html_element) = element.dyn_ref::<HtmlElement>() {
115 html_element.class_list().toggle(class)?;
116 }
117 Ok(())
118}
119
120#[cfg(target_arch = "wasm32")]
122pub fn has_class(element: &Element, class: &str) -> bool {
123 if let Some(html_element) = element.dyn_ref::<HtmlElement>() {
124 html_element.class_list().contains(class)
125 } else {
126 false
127 }
128}
129
130#[cfg(target_arch = "wasm32")]
132pub fn set_classes(element: &Element, classes: &[&str]) -> Result<(), JsValue> {
133 if let Some(html_element) = element.dyn_ref::<HtmlElement>() {
134 html_element.set_class_name(&classes.join(" "));
135 }
136 Ok(())
137}
138
139#[cfg(target_arch = "wasm32")]
141pub fn get_attribute(element: &Element, name: &str) -> Option<String> {
142 element.get_attribute(name)
143}
144
145#[cfg(target_arch = "wasm32")]
147pub fn remove_attribute(element: &Element, name: &str) -> Result<(), JsValue> {
148 element.remove_attribute(name)
149}
150
151#[cfg(target_arch = "wasm32")]
153pub fn set_inner_html(element: &Element, html: &str) {
154 element.set_inner_html(html);
155}
156
157#[cfg(target_arch = "wasm32")]
159pub fn get_inner_html(element: &Element) -> String {
160 element.inner_html()
161}
162
163#[cfg(target_arch = "wasm32")]
165pub fn set_style(element: &Element, property: &str, value: &str) -> Result<(), JsValue> {
166 use wasm_bindgen::JsCast;
167 if let Some(html_element) = element.dyn_ref::<HtmlElement>() {
168 let current_style = html_element.get_attribute("style").unwrap_or_default();
170 let new_style = if current_style.is_empty() {
171 format!("{}: {};", property, value)
172 } else {
173 format!("{}; {}: {};", current_style, property, value)
174 };
175 html_element.set_attribute("style", &new_style)?;
176 }
177 Ok(())
178}
179
180#[cfg(target_arch = "wasm32")]
182pub fn get_style(element: &Element, property: &str) -> Result<String, JsValue> {
183 if let Some(html_element) = element.dyn_ref::<HtmlElement>() {
184 if let Some(style_attr) = html_element.get_attribute("style") {
186 for part in style_attr.split(';') {
187 let parts: Vec<&str> = part.split(':').collect();
188 if parts.len() == 2 && parts[0].trim() == property {
189 return Ok(parts[1].trim().to_string());
190 }
191 }
192 }
193 }
194 Ok(String::new())
195}
196
197#[cfg(target_arch = "wasm32")]
199pub fn focus(element: &Element) -> Result<(), JsValue> {
200 if let Some(html_element) = element.dyn_ref::<HtmlElement>() {
201 html_element.focus()?;
202 }
203 Ok(())
204}
205
206#[cfg(target_arch = "wasm32")]
208pub fn blur(element: &Element) -> Result<(), JsValue> {
209 if let Some(html_element) = element.dyn_ref::<HtmlElement>() {
210 html_element.blur()?;
211 }
212 Ok(())
213}
214
215#[cfg(target_arch = "wasm32")]
217pub fn query_selector(selector: &str) -> Result<Option<Element>, JsValue> {
218 document()?.query_selector(selector)
219}
220
221#[cfg(target_arch = "wasm32")]
223pub fn query_selector_all(selector: &str) -> Result<web_sys::NodeList, JsValue> {
224 document()?.query_selector_all(selector)
225}
226
227#[cfg(target_arch = "wasm32")]
229pub fn create_text_node(text: &str) -> Result<web_sys::Text, JsValue> {
230 Ok(document()?.create_text_node(text))
231}
232
233#[cfg(target_arch = "wasm32")]
235pub fn request_animation_frame<F>(f: F) -> Result<i32, JsValue>
236where
237 F: FnMut() + 'static,
238{
239 let closure = Closure::wrap(Box::new(f) as Box<dyn FnMut()>);
240 let handle = window()?.request_animation_frame(closure.as_ref().unchecked_ref())?;
241 closure.forget();
242 Ok(handle)
243}
244
245#[cfg(target_arch = "wasm32")]
247pub fn set_timeout<F>(f: F, millis: i32) -> Result<i32, JsValue>
248where
249 F: FnMut() + 'static,
250{
251 let closure = Closure::wrap(Box::new(f) as Box<dyn FnMut()>);
252 let handle = window()?.set_timeout_with_callback_and_timeout_and_arguments_0(
253 closure.as_ref().unchecked_ref(),
254 millis,
255 )?;
256 closure.forget();
257 Ok(handle)
258}
259
260#[cfg(target_arch = "wasm32")]
262pub fn console_log(message: &str) {
263 web_sys::console::log_1(&JsValue::from_str(message));
264}
265
266#[cfg(target_arch = "wasm32")]
268pub fn console_error(message: &str) {
269 web_sys::console::error_1(&JsValue::from_str(message));
270}
271
272#[cfg(target_arch = "wasm32")]
274pub fn console_warn(message: &str) {
275 web_sys::console::warn_1(&JsValue::from_str(message));
276}
277
278#[cfg(not(target_arch = "wasm32"))]
279pub fn window() -> Result<(), String> {
280 Err("Window is only available on WASM target".to_string())
281}
282
283#[cfg(not(target_arch = "wasm32"))]
284pub fn document() -> Result<(), String> {
285 Err("Document is only available on WASM target".to_string())
286}