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