dart_tui 1.2.0
dart_tui: ^1.2.0 copied to clipboard
Elm-style terminal UI framework for Dart — Model–Update–View, async commands, 27+ components, Lipgloss-inspired styling.
Changelog #
1.2.0 #
New components #
-
CursorModel(bubbles/cursor.dart): in-line blinking cursor widget with three display modes —CursorMode.block(█),CursorMode.underline(_), andCursorMode.bar(|). Toggles visibility on everyTickMsgwhenblink: true. Useful for building custom text editors, prompts, or any UI that needs a visible insertion point independent of the real terminal cursor. Supportsfocus()/blur()to pause blinking, andwithMode()/withBlink()builders. -
MultiSelectModel(bubbles/multi_select.dart): scrollable checkbox list supporting multiple concurrent selections. Navigate with↑↓ / jk, toggle withSpaceorx, select/deselect all witha, confirm withEnter. Features:wrap: bool— cursor wraps at list boundariesheight— viewport limitingshowStatusBar—"N/Total selected"footerselectedValuesgetter — returns customvalueor falls back tolabelMultiSelectStylesfor full per-element theming (Catppuccin Mocha defaults)
New ProgramOption functions #
Seven new fluent option functions complement the existing ProgramOptions struct:
withAltScreen() // enter alt-screen buffer at startup
withHideCursor([bool hide = true]) // hide/show terminal cursor at startup
withTickInterval(Duration interval) // emit TickMsg at a fixed interval
withMouseCellMotion() // enable button-event mouse tracking
withMouseAllMotion() // enable all-motion mouse tracking
withReportFocus() // emit FocusMsg / BlurMsg on window focus
withWindowSize(int width, int height) // inject fixed dimensions (useful in tests)
These compose with ProgramOptions and take precedence over it; defaultMouseMode and defaultReportFocus act as floor values so per-View overrides still work.
Style additions #
Border.normal— ASCII-art border (+,-,|) for environments without Unicode box-drawing support.- Per-side border flags —
Bordernow hasshowTop,showRight,showBottom,showLeft(all defaulttrue). UseStyle.withBorderSides({top, right, bottom, left})or the pre-built helpersBorder.topOnly,Border.bottomOnly,Border.sidesOnlyto draw partial borders. Border.copyWith()— produce modifiedBorderinstances without recreating all fields.tabWidth: intfield onStyle(default4) —\tcharacters are expanded to spaces before rendering. UseStyle.withTabWidth(n)fluent builder.marginBackground: RgbColor?field onStyle— fills the margin area with a solid ANSI background colour. UseStyle.withMarginBackground(color)fluent builder.getWidth(String)(public) — visible terminal column width after stripping ANSI and counting double-wide characters.getHeight(String)(public) — number of newline-delimited lines.truncate(String, int)(public) — drop trailing visible columns to fitmaxWidth; ANSI-safe.truncateLeft(String, int)(public) — drop leading visible columns; ANSI codes in the kept portion are preserved.
Component navigation & mouse improvements #
ListModel— addedpgup/ctrl+b,pgdown/ctrl+f,home/g,end/Gkey bindings;viewOffsetYfield for click-to-select mouse handling; mouse wheel scrolling.SelectListModel— addedwrap: bool(cursor wraps at list boundaries).TableModel— addedviewOffsetYand mouse handling (wheel up/down, left-click to select with header offset).TreeModel— addedviewOffsetYand mouse handling (wheel scroll, left-click to move cursor); fixed' 'space key mapping to'space'keystroke.
New examples #
| Example | What it shows |
|---|---|
cursor_model.dart |
CursorModel — all three blink modes side-by-side, toggle blink with b |
multi_select.dart |
MultiSelectModel — toggle, select-all, confirm, display result |
Tests #
10 new test files, 160+ new test cases:
| File | Coverage |
|---|---|
spinner_test.dart |
SpinnerModel state transitions |
select_list_test.dart |
SelectListModel navigation, wrap, view |
progress_test.dart |
ProgressModel rendering and clamping |
help_test.dart |
HelpModel expand/collapse, KeyMap |
paginator_test.dart |
PaginatorModel navigation, bounds, view |
cursor_model_test.dart |
CursorModel blink, focus, modes, view |
multi_select_test.dart |
MultiSelectModel toggle, select-all, wrap, view |
program_options_test.dart |
Integration: each ProgramOption emits correct ANSI sequences |
input_decoder_test.dart |
TerminalInputDecoder — 48 edge-case / fuzz-style tests |
style_properties_test.dart |
Property-based invariants for getWidth, getHeight, truncate, Style.render, joinH/V, stripAnsi |
Other #
- All 54 VHS GIFs regenerated from current kernel snapshots.
1.1.0 #
New features #
ListModel(bubbles/list.dart): full-featured scrollable list with fuzzy/subsequence filtering, keyboard navigation (↑↓ / jk), filter mode (/to enter,Esc/Backspaceto exit), viewport scrolling, optional descriptions, status bar (x/y items), andFullListStylesfor per-element theming.TabsModel(bubbles/tabs.dart): tabbed-interface component with(label, content)pairs,←/→ / h/l / Tab / Shift+Tabnavigation, andTabsStylesfor active/inactive/divider/content theming.- SGR attributes — three new text decorations on
Style:isReverse(SGR 7) — swap foreground and backgroundisBlink(SGR 5) — blinking textisOverline(SGR 53) — overline decoration
Style.inherit(parent)— fills everynullfield from a parentStyle, enabling clean style composition without property repetition. All boolean SGR fields (isBold,isDim,isItalic,isUnderline,isStrikethrough,isReverse,isBlink,isOverline) changed frombool→bool?to support three-valued inheritance semantics.underlineSpaces/strikethroughSpaces— control whether underline / strikethrough decorations extend over padding spaces (defaulttrue, matching Lipgloss).borderForeground/borderBackground— independentRgbColortinting for border characters, separate from text content color.borderTitle/borderTitleAlignment— embed a title string in the top border edge withAlign.left/.center/.rightpositioning.wordWrap—Style(wordWrap: true)wraps at word boundaries before padding/border/constraint are applied; respects thewidthconstraint.transform—Style(transform: fn)applies an arbitraryString Function(String)to the rendered content (useful for upper-casing, truncation, etc.).CompleteColor— per-profile color specification:CompleteColor(trueColor: …, ansi256: …, ansi: …); used viaforegroundComplete/backgroundCompleteonStylefor correct downgrade at each profile level.Border.hidden— a visible-but-blank border (spaces) that preserves box geometry without drawing any characters.EdgeInsets.symmetric({vertical, horizontal})andEdgeInsets.only({top, right, bottom, left})— additional named constructors matching Flutter conventions.- Layout helpers now accept enums instead of raw
doublefractions:joinHorizontal(AlignVertical, List<String>)— wasdoublejoinVertical(Align, List<String>)— wasdoubleplace(width, height, Align, AlignVertical, content)— was twodoubleargsplaceHorizontal(width, Align, content)— new helperplaceVertical(height, AlignVertical, content)— new helper
- Terminal control Cmd helpers (
cmd.dart):enterAltScreen()/exitAltScreen()— emitEnterAltScreenMsg/ExitAltScreenMsghideCursor()/showCursor()— emitHideCursorMsg/ShowCursorMsgsetWindowTitle(title)—Cmdthat emitsSetWindowTitleMsgclearScrollArea()— emitsClearScrollAreaMsgscrollUp([n = 1])/scrollDown([n = 1])—Cmdthat emitsScrollMsg
- Renderer interface extended:
setAltScreen(bool),setCursorVisibility(bool),scroll(int, {bool up})added toTeaRendererand implemented byAnsiRenderer,CellRenderer, andNilRenderer. stripAnsi(String)— public utility that strips all ANSI escape sequences from a string; previously internal-only.
New examples #
| Example | What it shows |
|---|---|
word_wrap.dart |
Style.wordWrap at multiple widths with box, rounded, and thick borders + border title |
border_style.dart |
All 6 Border variants, borderForeground / borderBackground, and all three borderTitle alignments |
sgr_attrs.dart |
All 8 SGR text attributes; Style.inherit() composition |
list_filter.dart |
ListModel with fuzzy filtering, descriptions, status bar, and selection |
tabs.dart updated to use the exported TabsModel bubble instead of an inline reimplementation.
Tests #
9 new test files, 90+ new test cases:
style_sgr_test.dart, style_border_test.dart, style_wordwrap_test.dart, canvas_test.dart, gradient_test.dart, adaptive_color_test.dart, cmd_terminal_test.dart, list_model_test.dart, tabs_model_test.dart.
Breaking changes #
joinHorizontal,joinVertical, andplacenow accept enum arguments (AlignVertical,Align) instead of rawdoublefractions. Migrate:0.0 → AlignVertical.top,0.5 → AlignVertical.middle,1.0 → AlignVertical.bottom;0.0 → Align.left,0.5 → Align.center,1.0 → Align.right.- All boolean SGR fields on
Style(isBold,isDim,isItalic,isUnderline,isStrikethrough) changed frombool(defaultfalse) tobool?(defaultnull). Existing code that reads these fields may need a null-aware comparison (style.isBold == trueorstyle.isBold ?? false).
1.0.0+1 #
Bug fixes #
- Terminal hang on exit: awaiting the stdin subscription cancel in the shutdown path so the Dart event loop is fully released before the process exits. Previously the
unawaitedcancel could leave stdin holding the event loop open, requiring a manual Ctrl-C to regain the shell prompt. - Terminal not restored on quit: flushing ANSI reset sequences (show cursor, exit alt-screen) before the process exits so the shell prompt appears on a clean line.
Other changes #
- Enter / LF key fix:
0x0a(LF /\n) now correctly maps toKeyCode.enter, fixing silent key drops on Linux/WSL terminals that send LF instead of CR for Enter. - Batch render loop: all pending messages are drained before each render; a single render fires per batch, eliminating up to 16 ms of FPS-throttle lag per key press.
- Deferred capability queries:
CSI ?2026$yandOSC 11are sent after the first rendered frame so startup is not delayed. - Makefile:
make format(dart format check),make test,make analyze,make run/run-fast/kernels/bench/gifs/new-example. - Analyzer clean: resolved all
strict_raw_type,unused_local_variable,prefer_const_constructors,library_private_types_in_public_api, andavoid_relative_lib_importswarnings.
1.0.0 #
New features #
- CellRenderer: cell-level diff renderer using grapheme clusters (
characterspackage) — only changed cells emit ANSI sequences, eliminating flicker. - Synchronized updates: CSI
?2026h/lwrapping for flicker-free frames on terminals that support it. - ExecMsg / execProcess(): run external processes (e.g.
$EDITOR) with full terminal hand-off and optional exit-code callback. - TextAreaModel: multi-line editor with
charLimit, ctrl+k/ctrl+u line-kill, cursor navigation. - ViewportModel: scrollable content pane with soft-wrap,
atBottom/scrollPercent, keyboard navigation. - TableModel + TableColumn: tabular data viewer with header, separator, scrolling cursor, and optional row styles.
- TimerModel: countdown timer,
start()/stop()/reset()builders,TickMsgrouting byid. - StopwatchModel: elapsed-time stopwatch with millisecond display.
- KeyMap + KeyBinding + HelpModel: declarative keybinding registry; help UI with compact/full toggle.
- FilePickerModel: async directory browser with extension filtering.
- Style system: Lipgloss-inspired
Stylewithwidth/heightconstraints,Align/AlignVertical,inlinemode,AdaptiveColor,Bordervariants;joinHorizontal(),joinVertical(),place()layout helpers. - EchoMode (normal / password / none) and
suggestions(tab-completion) onTextInputModel. - ValidationFailedMsg + validate callback on
TextInputModel. tickWithId(Duration, Object): tick Cmd with routing ID for composable timers.batch()/sequence(): concurrent vs. ordered command scheduling.- Mouse support:
MouseMode.cellMotion/allMotion;MouseClickMsg,MouseMotionMsg,MouseWheelMsg. - Cursor control:
View.cursorwithCursorShape(block / underline / bar) and blink flag. - Focus reporting:
View.reportFocus,FocusMsg,BlurMsg. - Window title:
View.windowTitleOSC sequence. - 41 examples: complete port of the Bubbletea example gallery.
Performance improvements #
- Enter / LF key fix:
0x0a(LF /\n) was silently decoded asctrl+jand dropped by all components. It is now correctly mapped toKeyCode.enter, matching Linux/WSL terminal behaviour. - Batch render loop: messages are now drained without re-rendering between each one; a single render fires per batch. Eliminates up to 16 ms of FPS-throttle lag per key press.
- Unawaited commands:
runCmdis now fire-and-forget so command results arrive as the next queued message without blocking key event processing. - Deferred capability queries:
CSI ?2026$y(synchronized-updates query) andOSC 11(background-color query) are sent after the first rendered frame, so the initial visible output is not delayed. - Kernel snapshot build:
tool/build.sh --kernelcompiles examples to.dillkernel snapshots (~550 ms startup vs ~1 050 ms JIT source on WSL2). - Makefile: added targets for
test,analyze,run,run-fast,kernels,bench,gifs,new-example, and more. - 48 GIFs re-recorded from kernel snapshots for faster, cleaner recordings.
Bug fixes #
- Terminal not restored on quit: added
_output.writeln()+await _output.flush()in thefinallyblock so ANSI reset sequences (show cursor, exit alt-screen) are flushed to the terminal before the shell regains control. - Terminal hang on exit: awaiting the stdin subscription cancel in the shutdown path so the Dart event loop is fully released before the process exits. Previously the
unawaitedcancel could leave stdin holding the event loop open, requiring a manual Ctrl-C to regain the shell prompt.
Breaking changes from 0.1.0 #
- Legacy Go-style uppercase API aliases removed (
Batch,Sequence,Quit,Println,WithInput, etc.) — use canonical lowercase equivalents. request*commands now emit real terminal protocol queries; decoder responses feed back as typedMsgvalues.ListModelrenamed toSelectListModel.
0.1.0 #
- Initial release:
Programruntime (Elm-styleModel,Msg,Cmd,Batch). - Terminal integration via
dart_console(raw keys, window size, resize). - Components:
ListModel,TextInputModel,SpinnerModel,ProgressModel. - Optional
promptshelpers:select,confirm,input. - Example: shopping list (Bubble Tea tutorial port).