tui_dispatch_core/component.rs
1//! Component trait for pure UI elements
2
3use ratatui::{layout::Rect, Frame};
4
5use crate::event::EventKind;
6
7/// A pure UI component that renders based on props and emits actions
8///
9/// Components follow these rules:
10/// 1. Props contain ALL read-only data needed for rendering
11/// 2. `handle_event` returns actions, never mutates external state
12/// 3. `render` is a pure function of props (plus internal UI state like scroll position)
13///
14/// Internal UI state (scroll position, selection highlight) can be stored in `&mut self`,
15/// but data mutations must go through actions.
16///
17/// # Focus and Context
18///
19/// Components receive `EventKind` (the raw event) rather than the full `Event` with context.
20/// Focus information and other context should be passed through `Props`. This keeps components
21/// decoupled from the specific ComponentId type used by the application.
22///
23/// # Example
24///
25/// ```
26/// use tui_dispatch_core::{Component, EventKind};
27/// use ratatui::{Frame, layout::Rect, widgets::Paragraph};
28/// use crossterm::event::KeyCode;
29///
30/// #[derive(Clone)]
31/// enum Action { Increment, Decrement }
32///
33/// struct Counter;
34///
35/// struct CounterProps { count: i32, is_focused: bool }
36///
37/// impl Component<Action> for Counter {
38/// type Props<'a> = CounterProps;
39///
40/// fn handle_event(
41/// &mut self,
42/// event: &EventKind,
43/// props: Self::Props<'_>,
44/// ) -> impl IntoIterator<Item = Action> {
45/// if !props.is_focused { return None; }
46/// if let EventKind::Key(key) = event {
47/// match key.code {
48/// KeyCode::Up => return Some(Action::Increment),
49/// KeyCode::Down => return Some(Action::Decrement),
50/// _ => {}
51/// }
52/// }
53/// None
54/// }
55///
56/// fn render(&mut self, frame: &mut Frame, area: Rect, props: Self::Props<'_>) {
57/// let text = format!("Count: {}", props.count);
58/// frame.render_widget(Paragraph::new(text), area);
59/// }
60/// }
61/// ```
62pub trait Component<A> {
63 /// Data required to render the component (read-only)
64 type Props<'a>;
65
66 /// Handle an event and return actions to dispatch
67 ///
68 /// Components receive the raw `EventKind` (key press, mouse event, etc.).
69 /// Focus state and other context should be passed through `Props`.
70 ///
71 /// Returns any type implementing `IntoIterator<Item = A>`:
72 /// - `None` - no actions (most common)
73 /// - `Some(action)` - single action
74 /// - `[a, b]` or `vec![...]` - multiple actions
75 ///
76 /// Default implementation returns no actions (render-only components).
77 #[allow(unused_variables)]
78 fn handle_event(
79 &mut self,
80 event: &EventKind,
81 props: Self::Props<'_>,
82 ) -> impl IntoIterator<Item = A> {
83 None::<A>
84 }
85
86 /// Render the component to the frame
87 fn render(&mut self, frame: &mut Frame, area: Rect, props: Self::Props<'_>);
88}