1use crate::{Context, Event, Html, LiveResult};
2use std::fmt;
3
4#[derive(Debug, Clone, PartialEq, Eq, Hash)]
6pub struct ComponentId(String);
7
8impl ComponentId {
9 pub fn new(id: impl Into<String>) -> Self {
10 Self(id.into())
11 }
12
13 pub fn as_str(&self) -> &str {
14 &self.0
15 }
16}
17
18impl fmt::Display for ComponentId {
19 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
20 f.write_str(&self.0)
21 }
22}
23
24impl From<&str> for ComponentId {
25 fn from(value: &str) -> Self {
26 Self::new(value)
27 }
28}
29
30impl From<String> for ComponentId {
31 fn from(value: String) -> Self {
32 Self::new(value)
33 }
34}
35
36#[derive(Debug, Clone, PartialEq)]
38pub struct ComponentRender {
39 id: ComponentId,
40 html: Html,
41}
42
43impl ComponentRender {
44 pub fn new(id: impl Into<ComponentId>, html: Html) -> Self {
45 Self {
46 id: id.into(),
47 html,
48 }
49 }
50
51 pub fn id(&self) -> &ComponentId {
52 &self.id
53 }
54
55 pub fn html(&self) -> &Html {
56 &self.html
57 }
58
59 pub fn into_parts(self) -> (ComponentId, Html) {
60 (self.id, self.html)
61 }
62}
63
64pub trait LiveComponent: Send + 'static {
70 fn id(&self) -> ComponentId;
72
73 fn mount(&mut self, _ctx: &mut Context) -> LiveResult {
75 Ok(())
76 }
77
78 fn handle_event(&mut self, _event: Event, _ctx: &mut Context) -> LiveResult {
80 Ok(())
81 }
82
83 fn render(&self) -> Html;
85}
86
87#[cfg(test)]
88mod tests {
89 use super::{ComponentId, ComponentRender, LiveComponent};
90 use crate::{Context, Event, Html};
91
92 #[derive(Default)]
93 struct StubComponent;
94
95 impl LiveComponent for StubComponent {
96 fn id(&self) -> ComponentId {
97 ComponentId::new("stub")
98 }
99
100 fn render(&self) -> Html {
101 Html::new("<div>stub</div>")
102 }
103 }
104
105 #[test]
106 fn component_id_and_render_parts_round_trip() {
107 let render = ComponentRender::new("item-1", Html::new("<p>x</p>"));
108 assert_eq!(render.id().as_str(), "item-1");
109 assert_eq!(render.id().to_string(), "item-1");
110 assert_eq!(render.html().as_str(), "<p>x</p>");
111
112 let (id, html) = render.into_parts();
113 assert_eq!(id.as_str(), "item-1");
114 assert_eq!(html.as_str(), "<p>x</p>");
115
116 let from_string = ComponentId::from(String::from("item-2"));
117 assert_eq!(from_string.as_str(), "item-2");
118 }
119
120 #[test]
121 fn live_component_default_callbacks_return_ok() {
122 let mut component = StubComponent;
123 let mut ctx = Context::new("root");
124 assert!(component.mount(&mut ctx).is_ok());
125 assert!(component.handle_event(Event::new("noop"), &mut ctx).is_ok());
126 assert_eq!(component.id().as_str(), "stub");
127 assert_eq!(component.render().as_str(), "<div>stub</div>");
128 }
129}