bubbletea_widgets/lib.rs
1#![warn(missing_docs)]
2#![doc(html_root_url = "https://docs.rs/bubbletea-widgets/")]
3
4//! # bubbletea-widgets
5//!
6//! A Rust port of the Go library [charmbracelet/bubbles](https://github.com/charmbracelet/bubbles),
7//! providing reusable TUI components for building terminal applications with [bubbletea-rs](https://github.com/joshka/bubbletea-rs).
8//!
9//! [](https://crates.io/crates/bubbletea-widgets)
10//! [](https://docs.rs/bubbletea-widgets)
11//! [](https://opensource.org/licenses/MIT)
12//!
13//! ## Overview
14//!
15//! bubbletea-widgets offers a collection of common terminal UI components that can be easily integrated
16//! into bubbletea-rs applications. Each component follows the Elm Architecture pattern with
17//! `init()`, `update()`, and `view()` methods, providing a consistent and predictable API
18//! for building complex terminal user interfaces.
19//!
20//! ## Features
21//!
22//! - **Type-safe key bindings** with comprehensive key combination support
23//! - **Focus management** system for keyboard navigation between components
24//! - **Responsive design** with automatic width/height handling
25//! - **Theming support** through customizable styles
26//! - **Go compatibility** for easy migration from charmbracelet/bubbles
27//! - **Performance optimized** with efficient rendering and state management
28//!
29//! ## Components
30//!
31//! - **Input Components**: `TextInput`, `TextArea`, `FilePicker`
32//! - **Display Components**: `List`, `Table`, `Progress`, `Spinner`, `Help`
33//! - **Utility Components**: `Cursor`, `Viewport`, `Paginator`, `Timer`, `Stopwatch`
34//!
35//! ## Focus Management
36//!
37//! All components implement the `Component` trait which provides standardized focus management:
38//!
39//! ```rust
40//! use bubbletea_widgets::prelude::*;
41//! use bubbletea_rs::Cmd;
42//!
43//! fn handle_focus<T: Component>(component: &mut T) {
44//! let _cmd: Option<Cmd> = component.focus();
45//! assert!(component.focused());
46//! component.blur();
47//! assert!(!component.focused());
48//! }
49//!
50//! // Example with a text area (implements Component trait)
51//! let mut textarea = textarea_new();
52//! handle_focus(&mut textarea);
53//! ```
54//!
55//! ## Key Bindings
56//!
57//! Components use the type-safe key binding system from the `key` module:
58//!
59//! ```rust
60//! use bubbletea_widgets::key::{Binding, KeyMap};
61//! use crossterm::event::{KeyCode, KeyModifiers};
62//!
63//! // Create key bindings
64//! let confirm = Binding::new(vec![KeyCode::Enter])
65//! .with_help("enter", "Confirm selection");
66//!
67//! let save = Binding::new(vec![(KeyCode::Char('s'), KeyModifiers::CONTROL)])
68//! .with_help("ctrl+s", "Save file");
69//!
70//! // Implement KeyMap for your component
71//! struct MyKeyMap {
72//! confirm: Binding,
73//! save: Binding,
74//! }
75//!
76//! impl KeyMap for MyKeyMap {
77//! fn short_help(&self) -> Vec<&Binding> {
78//! vec![&self.confirm, &self.save]
79//! }
80//!
81//! fn full_help(&self) -> Vec<Vec<&Binding>> {
82//! vec![
83//! vec![&self.confirm],
84//! vec![&self.save],
85//! ]
86//! }
87//! }
88//! ```
89//!
90//! ## Integration with bubbletea-rs
91//!
92//! Components are designed to work seamlessly with bubbletea-rs models:
93//!
94//! ```rust
95//! use bubbletea_widgets::prelude::*;
96//! use bubbletea_rs::{Model, Cmd, Msg};
97//!
98//! struct App {
99//! input: TextInput,
100//! }
101//!
102//! impl Model for App {
103//! fn init() -> (Self, Option<Cmd>) {
104//! let mut input = textinput_new();
105//! let focus_cmd = input.focus();
106//! (Self { input }, Some(focus_cmd))
107//! }
108//!
109//! fn update(&mut self, msg: Msg) -> Option<Cmd> {
110//! // Handle component updates
111//! if let Some(cmd) = self.input.update(msg) {
112//! return Some(cmd);
113//! }
114//!
115//! // Handle other messages
116//! match msg {
117//! // Your app-specific message handling
118//! _ => None,
119//! }
120//! }
121//!
122//! fn view(&self) -> String {
123//! format!("Enter text: {}\n{}", self.input.view(), "Press Ctrl+C to quit")
124//! }
125//! }
126//! ```
127//!
128//! ## Quick Start
129//!
130//! Add bubbletea-widgets to your `Cargo.toml`:
131//!
132//! ```toml
133//! [dependencies]
134//! bubbletea-widgets = "0.1.0"
135//! bubbletea-rs = "0.0.7"
136//! crossterm = "0.27"
137//! ```
138//!
139//! For convenience, you can import the prelude:
140//!
141//! ```rust
142//! use bubbletea_widgets::prelude::*;
143//! ```
144//!
145//! ## Component Overview
146//!
147//! | Component | Description | Use Case |
148//! |-----------|-------------|----------|
149//! | `TextInput` | Single-line text input | Forms, search boxes |
150//! | `TextArea` | Multi-line text editor | Code editing, long text |
151//! | `List` | Scrollable item list | Menus, file browsers |
152//! | `Table` | Tabular data display | Data tables, spreadsheets |
153//! | `Progress` | Progress bar with animation | Loading indicators |
154//! | `Spinner` | Animated loading spinner | Background operations |
155//! | `Help` | Key binding help display | User guidance |
156//! | `FilePicker` | File system navigator | File selection |
157//! | `Timer` | Countdown timer | Time-based operations |
158//! | `Stopwatch` | Elapsed time tracker | Performance monitoring |
159
160pub mod cursor;
161pub mod filepicker;
162pub mod help;
163pub mod key;
164pub mod list;
165pub mod paginator;
166pub mod progress;
167pub mod spinner;
168pub mod stopwatch;
169pub mod table;
170pub mod textarea;
171pub mod textinput;
172pub mod timer;
173pub mod viewport;
174
175use bubbletea_rs::Cmd;
176
177/// Core trait for components that support focus management.
178///
179/// This trait provides a standardized interface for managing keyboard focus
180/// across all bubbletea-widgets components. Components that implement this trait can
181/// participate in focus management systems and provide consistent behavior
182/// for keyboard navigation.
183///
184/// ## Focus States
185///
186/// - **Focused**: The component can receive keyboard input and should visually
187/// indicate its active state
188/// - **Blurred**: The component cannot receive keyboard input and should
189/// display in an inactive state
190///
191/// ## Implementation Guidelines
192///
193/// When implementing this trait:
194/// - `focus()` should set the component's focused state and may return a command
195/// for initialization (e.g., starting a cursor blink timer)
196/// - `blur()` should unset the focused state and clean up any focus-related state
197/// - `focused()` should return the current focus state consistently
198///
199/// ## Examples
200///
201/// ### Basic Usage
202///
203/// ```rust
204/// use bubbletea_widgets::prelude::*;
205///
206/// let mut input = textinput_new();
207/// assert!(!input.focused());
208///
209/// input.focus();
210/// assert!(input.focused());
211///
212/// input.blur();
213/// assert!(!input.focused());
214/// ```
215///
216/// ### Focus Management in Applications
217///
218/// ```rust
219/// use bubbletea_widgets::prelude::*;
220/// use bubbletea_rs::Cmd;
221///
222/// struct App {
223/// input: TextInput,
224/// textarea: TextArea,
225/// focused_component: usize,
226/// }
227///
228/// impl App {
229/// fn focus_next(&mut self) -> Option<Cmd> {
230/// // Blur current component
231/// match self.focused_component {
232/// 0 => self.input.blur(),
233/// 1 => self.textarea.blur(),
234/// _ => {}
235/// }
236///
237/// // Focus next component
238/// self.focused_component = (self.focused_component + 1) % 2;
239/// match self.focused_component {
240/// 0 => Some(self.input.focus()),
241/// 1 => self.textarea.focus(),
242/// _ => None,
243/// }
244/// }
245/// }
246/// ```
247pub trait Component {
248 /// Sets the component to focused state.
249 ///
250 /// This method should update the component's internal state to indicate
251 /// that it can receive keyboard input. It may return a command for
252 /// initialization tasks like starting timers or triggering redraws.
253 ///
254 /// # Returns
255 ///
256 /// An optional command to be executed by the bubbletea runtime. Common
257 /// use cases include starting cursor blink timers or triggering immediate
258 /// redraws to show the focus state change.
259 ///
260 /// # Examples
261 ///
262 /// ```rust
263 /// use bubbletea_widgets::prelude::*;
264 ///
265 /// let mut input = textinput_new();
266 /// let cmd = input.focus();
267 /// assert!(input.focused());
268 /// // cmd may contain a cursor blink timer start command
269 /// ```
270 fn focus(&mut self) -> Option<Cmd>;
271
272 /// Sets the component to blurred (unfocused) state.
273 ///
274 /// This method should update the component's internal state to indicate
275 /// that it cannot receive keyboard input. It should clean up any
276 /// focus-related resources like stopping timers.
277 ///
278 /// # Examples
279 ///
280 /// ```rust
281 /// use bubbletea_widgets::prelude::*;
282 ///
283 /// let mut input = textinput_new();
284 /// input.focus();
285 /// assert!(input.focused());
286 ///
287 /// input.blur();
288 /// assert!(!input.focused());
289 /// ```
290 fn blur(&mut self);
291
292 /// Returns the current focus state of the component.
293 ///
294 /// # Returns
295 ///
296 /// `true` if the component is currently focused and can receive keyboard
297 /// input, `false` otherwise.
298 ///
299 /// # Examples
300 ///
301 /// ```rust
302 /// use bubbletea_widgets::prelude::*;
303 ///
304 /// let mut input = textinput_new();
305 /// assert!(!input.focused()); // Initially unfocused
306 ///
307 /// input.focus();
308 /// assert!(input.focused()); // Now focused
309 /// ```
310 fn focused(&self) -> bool;
311}
312
313pub use cursor::Model as Cursor;
314pub use filepicker::Model as FilePicker;
315pub use help::Model as HelpModel;
316pub use key::{
317 matches, matches_binding, new_binding, with_disabled, with_help, with_keys, Binding,
318 Help as KeyHelp, KeyMap, KeyPress,
319};
320pub use list::Model as List;
321pub use list::{
322 DefaultDelegate as ListDefaultDelegate, DefaultItem as ListDefaultItem,
323 DefaultItemStyles as ListDefaultItemStyles, FilterState, FilterStateInfo, ListKeyMap,
324 ListStyles,
325};
326pub use paginator::Model as Paginator;
327pub use progress::Model as Progress;
328pub use spinner::{
329 new as spinner_new, with_spinner, with_style, Model as Spinner, SpinnerOption,
330 TickMsg as SpinnerTickMsg, DOT, ELLIPSIS, GLOBE, HAMBURGER, JUMP, LINE, METER, MINI_DOT,
331 MONKEY, MOON, POINTS, PULSE,
332};
333pub use stopwatch::Model as Stopwatch;
334pub use table::Model as Table;
335pub use textarea::{
336 default_styles as textarea_default_styles, new as textarea_new, LineInfo, Model as TextArea,
337 PasteErrMsg as TextAreaPasteErrMsg, PasteMsg as TextAreaPasteMsg,
338};
339pub use textinput::{
340 blink, default_key_map as textinput_default_key_map, new as textinput_new, paste, EchoMode,
341 KeyMap as TextInputKeyMap, Model as TextInput, PasteErrMsg, PasteMsg, ValidateFunc,
342};
343pub use timer::{
344 new as timer_new, new_with_interval as timer_new_with_interval, Model as Timer,
345 StartStopMsg as TimerStartStopMsg, TickMsg as TimerTickMsg, TimeoutMsg as TimerTimeoutMsg,
346};
347pub use viewport::Model as Viewport;
348
349/// Prelude module for convenient imports.
350///
351/// This module re-exports the most commonly used types and functions from
352/// bubbletea-widgets, allowing users to import everything they need with a single
353/// `use` statement.
354///
355/// # Usage
356///
357/// Instead of importing each component individually:
358///
359/// ```rust
360/// use bubbletea_widgets::{TextInput, List, Spinner, Component};
361/// use bubbletea_widgets::key::{Binding, KeyMap};
362/// ```
363///
364/// You can use the prelude:
365///
366/// ```rust
367/// use bubbletea_widgets::prelude::*;
368/// ```
369///
370/// # What's Included
371///
372/// The prelude includes:
373/// - All component types (`TextInput`, `List`, `Table`, etc.)
374/// - The `Component` trait for focus management
375/// - Key binding types and functions (`Binding`, `KeyMap`, etc.)
376/// - Utility types and commonly used enums
377/// - Constructor functions for components
378///
379/// # Examples
380///
381/// ```rust
382/// use bubbletea_widgets::prelude::*;
383/// use bubbletea_rs::{Model, Cmd, Msg};
384///
385/// struct App {
386/// input: TextInput,
387/// list: List<ListDefaultItem>,
388/// }
389///
390/// impl Model for App {
391/// fn init() -> (Self, Option<Cmd>) {
392/// let mut input = textinput_new();
393/// let focus_cmd = input.focus();
394///
395/// let delegate = ListDefaultDelegate::new();
396/// let items = vec![
397/// ListDefaultItem::new("Item 1", "First item"),
398/// ListDefaultItem::new("Item 2", "Second item"),
399/// ];
400/// let list = List::new(items, delegate, 80, 10);
401///
402/// (Self { input, list }, Some(focus_cmd))
403/// }
404///
405/// fn update(&mut self, msg: Msg) -> Option<Cmd> {
406/// // Handle updates...
407/// None
408/// }
409///
410/// fn view(&self) -> String {
411/// format!("{}\\n{}", self.input.view(), self.list.view())
412/// }
413/// }
414/// ```
415pub mod prelude {
416 pub use crate::cursor::Model as Cursor;
417 pub use crate::help::Model as HelpModel;
418 pub use crate::key::{
419 matches, matches_binding, new_binding, with_disabled, with_help, with_keys, Binding,
420 Help as KeyHelp, KeyMap, KeyPress,
421 };
422 pub use crate::list::Model as List;
423 pub use crate::list::{
424 DefaultDelegate as ListDefaultDelegate, DefaultItem as ListDefaultItem,
425 DefaultItemStyles as ListDefaultItemStyles, FilterState, FilterStateInfo, ListKeyMap,
426 ListStyles,
427 };
428 pub use crate::paginator::Model as Paginator;
429 pub use crate::progress::Model as Progress;
430 pub use crate::spinner::{
431 new as spinner_new, with_spinner, with_style, Model as Spinner, SpinnerOption,
432 TickMsg as SpinnerTickMsg, DOT, ELLIPSIS, GLOBE, HAMBURGER, JUMP, LINE, METER, MINI_DOT,
433 MONKEY, MOON, POINTS, PULSE,
434 };
435 pub use crate::table::Model as Table;
436 pub use crate::textarea::{
437 default_styles as textarea_default_styles, new as textarea_new, LineInfo,
438 Model as TextArea, PasteErrMsg as TextAreaPasteErrMsg, PasteMsg as TextAreaPasteMsg,
439 };
440 pub use crate::textinput::{
441 blink, default_key_map as textinput_default_key_map, new as textinput_new, paste, EchoMode,
442 KeyMap as TextInputKeyMap, Model as TextInput, PasteErrMsg, PasteMsg, ValidateFunc,
443 };
444 pub use crate::timer::{
445 new as timer_new, new_with_interval as timer_new_with_interval, Model as Timer,
446 StartStopMsg as TimerStartStopMsg, TickMsg as TimerTickMsg, TimeoutMsg as TimerTimeoutMsg,
447 };
448 pub use crate::viewport::Model as Viewport;
449 pub use crate::Component;
450}