Skip to main content

karbon_framework/livewire/
live_component.rs

1use std::collections::HashMap;
2use std::future::Future;
3use std::pin::Pin;
4
5/// Escape a string for safe HTML output inside a LiveComponent's render().
6///
7/// ```ignore
8/// fn render(&self) -> String {
9///     format!("<span>{}</span>", html_escape(&self.user_input))
10/// }
11/// ```
12pub fn html_escape(s: &str) -> String {
13    s.replace('&', "&amp;")
14        .replace('<', "&lt;")
15        .replace('>', "&gt;")
16        .replace('"', "&quot;")
17        .replace('\'', "&#39;")
18}
19
20/// Trait for server-rendered live components.
21///
22/// A LiveComponent holds state, renders HTML, and handles events from the browser.
23/// When an event is received via WebSocket, `handle_event` is called, the state
24/// is updated, and `render` produces new HTML that is sent back to the client.
25///
26/// **SECURITY**: The HTML returned by `render()` is sent directly to the browser.
27/// Always use `html_escape()` for any user-provided content to prevent XSS.
28///
29/// ```ignore
30/// struct Counter {
31///     count: i32,
32/// }
33///
34/// impl LiveComponent for Counter {
35///     fn render(&self) -> String {
36///         format!(r#"
37///             <div>
38///                 <span>{}</span>
39///                 <button lw-click="increment">+</button>
40///                 <button lw-click="decrement">-</button>
41///             </div>
42///         "#, self.count)
43///     }
44///
45///     fn handle_event<'a>(
46///         &'a mut self,
47///         event: &'a str,
48///         _params: &'a HashMap<String, String>,
49///     ) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
50///         Box::pin(async move {
51///             match event {
52///                 "increment" => self.count += 1,
53///                 "decrement" => self.count -= 1,
54///                 _ => {}
55///             }
56///         })
57///     }
58/// }
59/// ```
60pub trait LiveComponent: Send + Sync + 'static {
61    /// Render the component to an HTML string
62    fn render(&self) -> String;
63
64    /// Handle an event from the browser, mutating state as needed
65    fn handle_event<'a>(
66        &'a mut self,
67        event: &'a str,
68        params: &'a HashMap<String, String>,
69    ) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>>;
70
71    /// Called once when the component is first mounted (optional)
72    fn mount(&mut self) -> Pin<Box<dyn Future<Output = ()> + Send + '_>> {
73        Box::pin(async {})
74    }
75}