bubbletea-widgets 0.1.12

A collection of reusable TUI components for building terminal applications with bubbletea-rs
Documentation
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
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
#![warn(missing_docs)]
#![doc(html_root_url = "https://docs.rs/bubbletea-widgets/")]

//! # bubbletea-widgets
//!
//! A Rust port of the Go library [charmbracelet/bubbles](https://github.com/charmbracelet/bubbles),
//! providing reusable TUI components for building terminal applications with [bubbletea-rs](https://github.com/joshka/bubbletea-rs).
//!
//! [![Crates.io](https://img.shields.io/crates/v/bubbletea-widgets.svg)](https://crates.io/crates/bubbletea-widgets)
//! [![Documentation](https://docs.rs/bubbletea-widgets/badge.svg)](https://docs.rs/bubbletea-widgets)
//! [![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://opensource.org/licenses/MIT)
//!
//! ## Overview
//!
//! bubbletea-widgets offers a collection of common terminal UI components that can be easily integrated
//! into bubbletea-rs applications. Each component follows the Elm Architecture pattern with
//! `init()`, `update()`, and `view()` methods, providing a consistent and predictable API
//! for building complex terminal user interfaces.
//!
//! ## Features
//!
//! - **Type-safe key bindings** with comprehensive key combination support
//! - **Focus management** system for keyboard navigation between components
//! - **Responsive design** with automatic width/height handling
//! - **Theming support** through customizable styles
//! - **Go compatibility** for easy migration from charmbracelet/bubbles
//! - **Performance optimized** with efficient rendering and state management
//!
//! ## Components
//!
//! - **Input Components**: `TextInput`, `TextArea`, `FilePicker`
//! - **Display Components**: `List`, `Table`, `Progress`, `Spinner`, `Help`
//! - **Utility Components**: `Cursor`, `Viewport`, `Paginator`, `Timer`, `Stopwatch`
//!
//! ## Focus Management
//!
//! All components implement the `Component` trait which provides standardized focus management:
//!
//! ```rust
//! use bubbletea_widgets::prelude::*;
//! use bubbletea_rs::Cmd;
//!
//! fn handle_focus<T: Component>(component: &mut T) {
//!     let _cmd: Option<Cmd> = component.focus();
//!     assert!(component.focused());
//!     component.blur();
//!     assert!(!component.focused());
//! }
//!
//! // Example with a text area (implements Component trait)
//! let mut textarea = textarea_new();
//! handle_focus(&mut textarea);
//! ```
//!
//! ## Key Bindings
//!
//! Components use the type-safe key binding system from the `key` module:
//!
//! ```rust
//! use bubbletea_widgets::key::{Binding, KeyMap};
//! use crossterm::event::{KeyCode, KeyModifiers};
//!
//! // Create key bindings
//! let confirm = Binding::new(vec![KeyCode::Enter])
//!     .with_help("enter", "Confirm selection");
//!
//! let save = Binding::new(vec![(KeyCode::Char('s'), KeyModifiers::CONTROL)])
//!     .with_help("ctrl+s", "Save file");
//!
//! // Implement KeyMap for your component
//! struct MyKeyMap {
//!     confirm: Binding,
//!     save: Binding,
//! }
//!
//! impl KeyMap for MyKeyMap {
//!     fn short_help(&self) -> Vec<&Binding> {
//!         vec![&self.confirm, &self.save]
//!     }
//!
//!     fn full_help(&self) -> Vec<Vec<&Binding>> {
//!         vec![
//!             vec![&self.confirm],
//!             vec![&self.save],
//!         ]
//!     }
//! }
//! ```
//!
//! ## Integration with bubbletea-rs
//!
//! Components are designed to work seamlessly with bubbletea-rs models:
//!
//! ```rust
//! use bubbletea_widgets::prelude::*;
//! use bubbletea_rs::{Model, Cmd, Msg};
//!
//! struct App {
//!     input: TextInput,
//! }
//!
//! impl Model for App {
//!     fn init() -> (Self, Option<Cmd>) {
//!         let mut input = textinput_new();
//!         let focus_cmd = input.focus();
//!         (Self { input }, Some(focus_cmd))
//!     }
//!
//!     fn update(&mut self, msg: Msg) -> Option<Cmd> {
//!         // Handle component updates
//!         if let Some(cmd) = self.input.update(msg) {
//!             return Some(cmd);
//!         }
//!
//!         // Handle other messages
//!         match msg {
//!             // Your app-specific message handling
//!             _ => None,
//!         }
//!     }
//!
//!     fn view(&self) -> String {
//!         format!("Enter text: {}\n{}", self.input.view(), "Press Ctrl+C to quit")
//!     }
//! }
//! ```
//!
//! ## Quick Start
//!
//! Add bubbletea-widgets to your `Cargo.toml`:
//!
//! ```toml
//! [dependencies]
//! bubbletea-widgets = "0.1.0"
//! bubbletea-rs = "0.0.7"
//! crossterm = "0.27"
//! ```
//!
//! For convenience, you can import the prelude:
//!
//! ```rust
//! use bubbletea_widgets::prelude::*;
//! ```
//!
//! ## Component Overview
//!
//! | Component | Description | Use Case |
//! |-----------|-------------|----------|
//! | `TextInput` | Single-line text input | Forms, search boxes |
//! | `TextArea` | Multi-line text editor | Code editing, long text |
//! | `List` | Scrollable item list | Menus, file browsers |
//! | `Table` | Tabular data display | Data tables, spreadsheets |
//! | `Progress` | Progress bar with animation | Loading indicators |
//! | `Spinner` | Animated loading spinner | Background operations |
//! | `Help` | Key binding help display | User guidance |
//! | `FilePicker` | File system navigator | File selection |
//! | `Timer` | Countdown timer | Time-based operations |
//! | `Stopwatch` | Elapsed time tracker | Performance monitoring |

pub mod cursor;
pub mod filepicker;
pub mod help;
pub mod key;
pub mod list;
pub mod paginator;
pub mod progress;
pub mod spinner;
pub mod stopwatch;
pub mod table;
pub mod textarea;
pub mod textinput;
pub mod timer;
pub mod viewport;

use bubbletea_rs::Cmd;

/// Core trait for components that support focus management.
///
/// This trait provides a standardized interface for managing keyboard focus
/// across all bubbletea-widgets components. Components that implement this trait can
/// participate in focus management systems and provide consistent behavior
/// for keyboard navigation.
///
/// ## Focus States
///
/// - **Focused**: The component can receive keyboard input and should visually
///   indicate its active state
/// - **Blurred**: The component cannot receive keyboard input and should
///   display in an inactive state
///
/// ## Implementation Guidelines
///
/// When implementing this trait:
/// - `focus()` should set the component's focused state and may return a command
///   for initialization (e.g., starting a cursor blink timer)
/// - `blur()` should unset the focused state and clean up any focus-related state
/// - `focused()` should return the current focus state consistently
///
/// ## Examples
///
/// ### Basic Usage
///
/// ```rust
/// use bubbletea_widgets::prelude::*;
///
/// let mut input = textinput_new();
/// assert!(!input.focused());
///
/// input.focus();
/// assert!(input.focused());
///
/// input.blur();
/// assert!(!input.focused());
/// ```
///
/// ### Focus Management in Applications
///
/// ```rust
/// use bubbletea_widgets::prelude::*;
/// use bubbletea_rs::Cmd;
///
/// struct App {
///     input: TextInput,
///     textarea: TextArea,
///     focused_component: usize,
/// }
///
/// impl App {
///     fn focus_next(&mut self) -> Option<Cmd> {
///         // Blur current component
///         match self.focused_component {
///             0 => self.input.blur(),
///             1 => self.textarea.blur(),
///             _ => {}
///         }
///
///         // Focus next component
///         self.focused_component = (self.focused_component + 1) % 2;
///         match self.focused_component {
///             0 => Some(self.input.focus()),
///             1 => self.textarea.focus(),
///             _ => None,
///         }
///     }
/// }
/// ```
pub trait Component {
    /// Sets the component to focused state.
    ///
    /// This method should update the component's internal state to indicate
    /// that it can receive keyboard input. It may return a command for
    /// initialization tasks like starting timers or triggering redraws.
    ///
    /// # Returns
    ///
    /// An optional command to be executed by the bubbletea runtime. Common
    /// use cases include starting cursor blink timers or triggering immediate
    /// redraws to show the focus state change.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use bubbletea_widgets::prelude::*;
    ///
    /// let mut input = textinput_new();
    /// let cmd = input.focus();
    /// assert!(input.focused());
    /// // cmd may contain a cursor blink timer start command
    /// ```
    fn focus(&mut self) -> Option<Cmd>;

    /// Sets the component to blurred (unfocused) state.
    ///
    /// This method should update the component's internal state to indicate
    /// that it cannot receive keyboard input. It should clean up any
    /// focus-related resources like stopping timers.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use bubbletea_widgets::prelude::*;
    ///
    /// let mut input = textinput_new();
    /// input.focus();
    /// assert!(input.focused());
    ///
    /// input.blur();
    /// assert!(!input.focused());
    /// ```
    fn blur(&mut self);

    /// Returns the current focus state of the component.
    ///
    /// # Returns
    ///
    /// `true` if the component is currently focused and can receive keyboard
    /// input, `false` otherwise.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use bubbletea_widgets::prelude::*;
    ///
    /// let mut input = textinput_new();
    /// assert!(!input.focused()); // Initially unfocused
    ///
    /// input.focus();
    /// assert!(input.focused()); // Now focused
    /// ```
    fn focused(&self) -> bool;
}

pub use cursor::Model as Cursor;
pub use filepicker::Model as FilePicker;
pub use help::Model as HelpModel;
pub use key::{
    matches, matches_binding, new_binding, with_disabled, with_help, with_keys, Binding,
    Help as KeyHelp, KeyMap, KeyPress,
};
pub use list::Model as List;
pub use list::{
    DefaultDelegate as ListDefaultDelegate, DefaultItem as ListDefaultItem,
    DefaultItemStyles as ListDefaultItemStyles, FilterState, FilterStateInfo, ListKeyMap,
    ListStyles,
};
pub use paginator::Model as Paginator;
pub use progress::Model as Progress;
pub use spinner::{
    new as spinner_new, with_spinner, with_style, Model as Spinner, SpinnerOption,
    TickMsg as SpinnerTickMsg, DOT, ELLIPSIS, GLOBE, HAMBURGER, JUMP, LINE, METER, MINI_DOT,
    MONKEY, MOON, POINTS, PULSE,
};
pub use stopwatch::Model as Stopwatch;
pub use table::Model as Table;
pub use textarea::{
    default_styles as textarea_default_styles, new as textarea_new, LineInfo, Model as TextArea,
    PasteErrMsg as TextAreaPasteErrMsg, PasteMsg as TextAreaPasteMsg,
};
pub use textinput::{
    blink, default_key_map as textinput_default_key_map, new as textinput_new, paste, EchoMode,
    KeyMap as TextInputKeyMap, Model as TextInput, PasteErrMsg, PasteMsg, ValidateFunc,
};
pub use timer::{
    new as timer_new, new_with_interval as timer_new_with_interval, Model as Timer,
    StartStopMsg as TimerStartStopMsg, TickMsg as TimerTickMsg, TimeoutMsg as TimerTimeoutMsg,
};
pub use viewport::Model as Viewport;

/// Prelude module for convenient imports.
///
/// This module re-exports the most commonly used types and functions from
/// bubbletea-widgets, allowing users to import everything they need with a single
/// `use` statement.
///
/// # Usage
///
/// Instead of importing each component individually:
///
/// ```rust
/// use bubbletea_widgets::{TextInput, List, Spinner, Component};
/// use bubbletea_widgets::key::{Binding, KeyMap};
/// ```
///
/// You can use the prelude:
///
/// ```rust
/// use bubbletea_widgets::prelude::*;
/// ```
///
/// # What's Included
///
/// The prelude includes:
/// - All component types (`TextInput`, `List`, `Table`, etc.)
/// - The `Component` trait for focus management
/// - Key binding types and functions (`Binding`, `KeyMap`, etc.)
/// - Utility types and commonly used enums
/// - Constructor functions for components
///
/// # Examples
///
/// ```rust
/// use bubbletea_widgets::prelude::*;
/// use bubbletea_rs::{Model, Cmd, Msg};
///
/// struct App {
///     input: TextInput,
///     list: List<ListDefaultItem>,
/// }
///
/// impl Model for App {
///     fn init() -> (Self, Option<Cmd>) {
///         let mut input = textinput_new();
///         let focus_cmd = input.focus();
///         
///         let delegate = ListDefaultDelegate::new();
///         let items = vec![
///             ListDefaultItem::new("Item 1", "First item"),
///             ListDefaultItem::new("Item 2", "Second item"),
///         ];
///         let list = List::new(items, delegate, 80, 10);
///         
///         (Self { input, list }, Some(focus_cmd))
///     }
///
///     fn update(&mut self, msg: Msg) -> Option<Cmd> {
///         // Handle updates...
///         None
///     }
///
///     fn view(&self) -> String {
///         format!("{}\\n{}", self.input.view(), self.list.view())
///     }
/// }
/// ```
pub mod prelude {
    pub use crate::cursor::Model as Cursor;
    pub use crate::help::Model as HelpModel;
    pub use crate::key::{
        matches, matches_binding, new_binding, with_disabled, with_help, with_keys, Binding,
        Help as KeyHelp, KeyMap, KeyPress,
    };
    pub use crate::list::Model as List;
    pub use crate::list::{
        DefaultDelegate as ListDefaultDelegate, DefaultItem as ListDefaultItem,
        DefaultItemStyles as ListDefaultItemStyles, FilterState, FilterStateInfo, ListKeyMap,
        ListStyles,
    };
    pub use crate::paginator::Model as Paginator;
    pub use crate::progress::Model as Progress;
    pub use crate::spinner::{
        new as spinner_new, with_spinner, with_style, Model as Spinner, SpinnerOption,
        TickMsg as SpinnerTickMsg, DOT, ELLIPSIS, GLOBE, HAMBURGER, JUMP, LINE, METER, MINI_DOT,
        MONKEY, MOON, POINTS, PULSE,
    };
    pub use crate::table::Model as Table;
    pub use crate::textarea::{
        default_styles as textarea_default_styles, new as textarea_new, LineInfo,
        Model as TextArea, PasteErrMsg as TextAreaPasteErrMsg, PasteMsg as TextAreaPasteMsg,
    };
    pub use crate::textinput::{
        blink, default_key_map as textinput_default_key_map, new as textinput_new, paste, EchoMode,
        KeyMap as TextInputKeyMap, Model as TextInput, PasteErrMsg, PasteMsg, ValidateFunc,
    };
    pub use crate::timer::{
        new as timer_new, new_with_interval as timer_new_with_interval, Model as Timer,
        StartStopMsg as TimerStartStopMsg, TickMsg as TimerTickMsg, TimeoutMsg as TimerTimeoutMsg,
    };
    pub use crate::viewport::Model as Viewport;
    pub use crate::Component;
}