@linxiraos/pi-tui 1.1.0 → 1.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/CHANGELOG.md +10 -1
  2. package/THIRD-PARTY-NOTICES.txt +22909 -0
  3. package/dist/types/autocomplete.d.ts +127 -0
  4. package/dist/types/bracketed-paste.d.ts +51 -0
  5. package/dist/types/components/box.d.ts +31 -0
  6. package/dist/types/components/cancellable-loader.d.ts +21 -0
  7. package/dist/types/components/composer/borderless.d.ts +8 -0
  8. package/dist/types/components/composer/box.d.ts +2 -0
  9. package/dist/types/components/composer/claude.d.ts +8 -0
  10. package/dist/types/components/composer/field.d.ts +3 -0
  11. package/dist/types/components/composer/index.d.ts +9 -0
  12. package/dist/types/components/composer/pi.d.ts +2 -0
  13. package/dist/types/components/composer/rail.d.ts +3 -0
  14. package/dist/types/components/composer/registry.d.ts +12 -0
  15. package/dist/types/components/composer/rule.d.ts +7 -0
  16. package/dist/types/components/composer/types.d.ts +90 -0
  17. package/dist/types/components/editor.d.ts +213 -0
  18. package/dist/types/components/image.d.ts +162 -0
  19. package/dist/types/components/input.d.ts +25 -0
  20. package/dist/types/components/loader.d.ts +25 -0
  21. package/dist/types/components/markdown.d.ts +86 -0
  22. package/dist/types/components/scroll-view.d.ts +62 -0
  23. package/dist/types/components/select-list.d.ts +78 -0
  24. package/dist/types/components/settings-list.d.ts +129 -0
  25. package/dist/types/components/spacer.d.ts +11 -0
  26. package/dist/types/components/tab-bar.d.ts +89 -0
  27. package/dist/types/components/text.d.ts +27 -0
  28. package/dist/types/components/truncated-text.d.ts +10 -0
  29. package/dist/types/deccara.d.ts +49 -0
  30. package/dist/types/desktop-notify.d.ts +52 -0
  31. package/dist/types/editor-component.d.ts +38 -0
  32. package/dist/types/fuzzy.d.ts +48 -0
  33. package/dist/types/index.d.ts +33 -0
  34. package/dist/types/keybindings.d.ts +202 -0
  35. package/dist/types/keys.d.ts +210 -0
  36. package/dist/types/kill-ring.d.ts +20 -0
  37. package/dist/types/kitty-graphics.d.ts +76 -0
  38. package/dist/types/latex-block.d.ts +8 -0
  39. package/dist/types/latex-to-unicode.d.ts +50 -0
  40. package/dist/types/loop-watchdog.d.ts +49 -0
  41. package/dist/types/mouse.d.ts +67 -0
  42. package/dist/types/stdin-buffer.d.ts +60 -0
  43. package/dist/types/symbols.d.ts +25 -0
  44. package/dist/types/terminal-capabilities.d.ts +338 -0
  45. package/dist/types/terminal.d.ts +215 -0
  46. package/dist/types/tmux.d.ts +6 -0
  47. package/dist/types/ttyid.d.ts +9 -0
  48. package/dist/types/tui.d.ts +332 -0
  49. package/dist/types/utils.d.ts +112 -0
  50. package/package.json +71 -68
@@ -0,0 +1,162 @@
1
+ import { type ImageDimensions } from "../terminal-capabilities.js";
2
+ import type { Component } from "../tui.js";
3
+ export interface ImageTheme {
4
+ fallbackColor: (str: string) => string;
5
+ }
6
+ export interface ImageOptions {
7
+ maxWidthCells?: number;
8
+ maxHeightCells?: number;
9
+ filename?: string;
10
+ /** Shared budget that caps how many inline images render as live graphics. */
11
+ budget?: ImageBudget;
12
+ /**
13
+ * Stable identity for the underlying image (e.g. `toolCallId:index`). Lets the
14
+ * budget hand back the same graphics id across component re-creations so a
15
+ * repaint replaces the placement instead of stacking a duplicate.
16
+ */
17
+ imageKey?: string;
18
+ }
19
+ /** Default count of inline images kept as live graphics before older ones fall back to text. */
20
+ export declare const DEFAULT_MAX_INLINE_IMAGES = 8;
21
+ /**
22
+ * Bounds how many inline images render as live terminal graphics at once.
23
+ *
24
+ * Terminal graphics protocols — Kitty especially — keep every transmitted image
25
+ * in a per-terminal store and re-draw placements as content scrolls; text-clear
26
+ * escapes (`CSI 2 J` / `CSI 3 J`) do not remove them. Unbounded, a session that
27
+ * shows many images piles up placements plus store memory and leaves ghosts in
28
+ * scrollback.
29
+ *
30
+ * The budget keeps the most recent `cap` images live and demotes older ones to
31
+ * their text fallback. Demotion needs a full redraw (so off-screen rows are
32
+ * rewritten) plus an explicit graphics purge of the demoted ids — {@link Image}
33
+ * reports display order via {@link observe}, and the TUI drives the purge +
34
+ * redraw on the frame after a new image pushes the count past the cap.
35
+ *
36
+ * `cap <= 0` disables budgeting: every image stays a live graphic.
37
+ */
38
+ export declare class ImageBudget {
39
+ #private;
40
+ constructor(cap?: number, requestRender?: () => void);
41
+ get cap(): number;
42
+ get enabled(): boolean;
43
+ setRequestRender(requestRender: () => void): void;
44
+ setCap(cap: number): void;
45
+ /**
46
+ * Stable graphics id for a logical image. A non-empty `key` maps to the same
47
+ * id across re-creations (so repaints replace the placement); a missing key
48
+ * gets a fresh id every call.
49
+ */
50
+ acquireId(key?: string): number;
51
+ /**
52
+ * Begin a render pass. Called by the renderer before composing the frame.
53
+ * Pass `stable: true` for a partial/throwaway pass that does not walk the
54
+ * whole tree in display order (the resize viewport fast path): {@link observe}
55
+ * then replays the last committed per-id decision instead of one derived from
56
+ * call order, and the pass must NOT be closed with {@link endPass}.
57
+ */
58
+ beginPass(stable?: boolean): void;
59
+ /**
60
+ * Record an image in display order and report whether it must render its text
61
+ * fallback this frame. Called by every {@link Image} during render — including
62
+ * on a cache hit, so the image keeps its display-order slot.
63
+ *
64
+ * During a `stable` pass ({@link beginPass}) the call order and visible subset
65
+ * are not authoritative, so the decision is the committed on-terminal split
66
+ * (`#suppressedIds`) keyed by id — order- and partiality-independent.
67
+ */
68
+ observe(imageId: number): boolean;
69
+ /**
70
+ * End a render pass. Returns true when this frame must purge graphics and
71
+ * fully repaint to apply a stricter budget; read the ids via
72
+ * {@link takePurgeIds}.
73
+ */
74
+ endPass(): boolean;
75
+ /** Image ids to delete from the terminal this frame; clears the pending set. */
76
+ takePurgeIds(): readonly number[];
77
+ /** All image ids believed to be loaded in the terminal store; clears tracking. */
78
+ takeAllTransmittedIds(): readonly number[];
79
+ /** Whether `imageId`'s data still needs to be transmitted to the terminal. */
80
+ shouldTransmit(imageId: number): boolean;
81
+ /**
82
+ * Record a direct-placement image's source pixel geometry so the renderer
83
+ * can clip its placement to the visible slice at write time; cleared when
84
+ * the image is purged from the terminal store.
85
+ */
86
+ registerPlacementGeometry(imageId: number, widthPx: number, heightPx: number): void;
87
+ /**
88
+ * Record this frame's native-scrollback commit target (the frame-row count
89
+ * that is committed once the frame's writes land). Called once per rendered
90
+ * frame — including frames that emit no placements — so an epoch whose rows
91
+ * commit while its line is never rewritten is still flagged before the next
92
+ * re-emission.
93
+ */
94
+ observeCommitWatermark(committedTo: number): void;
95
+ /**
96
+ * End the physical-row coordinate epoch after observing its final commit
97
+ * watermark. Placement ids and latched archive state survive, but attachment
98
+ * rows do not: the next placement emit records them in the new-width frame.
99
+ */
100
+ beginPlacementCoordinateEpoch(): void;
101
+ /**
102
+ * Resolve the placement id and geometry for a direct-placement emit whose
103
+ * topmost attached cell sits at `attachTopFrameRow` — the first frame row
104
+ * the placement covers, i.e. the block's first *visible* row, not its
105
+ * origin (-1 when the writer has no frame-space position: alt-screen,
106
+ * resize, ConPTY-truncated replays). `committedTo` is this frame's commit
107
+ * target in the same frame-row space (-1 when unknown).
108
+ *
109
+ * Invariant: a placement id may be re-used (Kitty replace strips that id's
110
+ * cells everywhere, scrollback included) only while none of the cells it
111
+ * attached have entered native scrollback. The epoch — the `p=` id —
112
+ * advances exactly when the archived flag says otherwise; rewrites with no
113
+ * commit progression keep replacing the same id in place.
114
+ */
115
+ resolvePlacementEmit(imageId: number, attachTopFrameRow: number, committedTo: number): {
116
+ placementId: number;
117
+ widthPx: number;
118
+ heightPx: number;
119
+ } | null;
120
+ /**
121
+ * Restart every placement epoch after a destructive history clear (`CSI 3 J`
122
+ * full paint). The clear destroys all placement cells — scrollback rows are
123
+ * gone and the replay rewrites the viewport — so no archive remains to
124
+ * protect. Reverting to epoch 1 lets the replay recreate every visible
125
+ * placement after the terminal-wide cleanup.
126
+ */
127
+ resetPlacementEpochs(): ReadonlyArray<{
128
+ imageId: number;
129
+ lastEpoch: number;
130
+ }>;
131
+ /**
132
+ * Queue a one-time transmit for `imageId`. No-op if already transmitted, so a
133
+ * repeated call (e.g. a width-change re-render) never re-sends the data.
134
+ */
135
+ enqueueTransmit(imageId: number, sequence: string): void;
136
+ /** Whether a frame has image data queued but not yet written to the terminal. */
137
+ hasPendingTransmits(): boolean;
138
+ /**
139
+ * True when the budget has nothing in flight: no live images observed on
140
+ * the last pass, no queued transmits, no pending purges, and no stricter
141
+ * threshold left to apply. A component-scoped frame may skip the observe
142
+ * pass only then — a partial tree walk would under-count display order.
143
+ */
144
+ get quiescent(): boolean;
145
+ /** Transmit sequences to write before this frame's placements; clears the queue. */
146
+ takeTransmits(): readonly string[];
147
+ /**
148
+ * Drop transmit tracking so every still-live image re-enqueues its data
149
+ * (`a=t`) on the next render. Recovers when the terminal dropped the original
150
+ * transmit — e.g. Ghostty discarding graphics sent during its post-startup
151
+ * window — where a placement-only replay can never bind a Unicode placeholder.
152
+ * Pair with a component invalidate + forced repaint so the data and placement
153
+ * re-emit together; keeps no base64 in budget state (the transmit-once design).
154
+ */
155
+ forgetTransmitted(): void;
156
+ }
157
+ export declare class Image implements Component {
158
+ #private;
159
+ constructor(base64Data: string, mimeType: string, theme: ImageTheme, options?: ImageOptions, dimensions?: ImageDimensions);
160
+ invalidate(): void;
161
+ render(width: number): readonly string[];
162
+ }
@@ -0,0 +1,25 @@
1
+ import { type Component, type Focusable } from "../tui.js";
2
+ /**
3
+ * Input component - single-line text input with horizontal scrolling
4
+ */
5
+ export declare class Input implements Component, Focusable {
6
+ #private;
7
+ /** Rendered before the editable area; set to "" for chrome-less embedding. */
8
+ prompt: string;
9
+ /** Render the editable value as bullets while retaining the real value internally. */
10
+ mask: boolean;
11
+ onSubmit?: (value: string) => void;
12
+ onEscape?: () => void;
13
+ /** Focusable interface - set by TUI when focus changes */
14
+ focused: boolean;
15
+ getValue(): string;
16
+ setValue(value: string): void;
17
+ setUseTerminalCursor(useTerminalCursor: boolean): void;
18
+ getUseTerminalCursor(): boolean;
19
+ handleInput(data: string): void;
20
+ /** Apply terminal paste semantics to text from non-bracketed paste transports
21
+ * (e.g. kitty's OSC 5522 enhanced clipboard read). Mirrors `Editor.pasteText`. */
22
+ pasteText(text: string): void;
23
+ invalidate(): void;
24
+ render(width: number): readonly string[];
25
+ }
@@ -0,0 +1,25 @@
1
+ import type { TUI } from "../tui.js";
2
+ import { Text } from "./text.js";
3
+ type ColorFn = (str: string) => string;
4
+ /**
5
+ * Styles Loader message fragments without changing their visible text or width.
6
+ * Set `animated` for colorizers whose ANSI output changes over time.
7
+ */
8
+ export type LoaderMessageColorFn = ColorFn & {
9
+ readonly animated?: true;
10
+ };
11
+ /** Animates a spinner and colorized message while asynchronous work is pending. */
12
+ export declare class Loader extends Text {
13
+ #private;
14
+ private spinnerColorFn;
15
+ private messageColorFn;
16
+ private message;
17
+ constructor(ui: TUI, spinnerColorFn: ColorFn, messageColorFn: LoaderMessageColorFn, message?: string, spinnerFrames?: string[]);
18
+ render(width: number): readonly string[];
19
+ start(): void;
20
+ stop(): void;
21
+ /** Lifecycle teardown: stop the animation timer. Idempotent. */
22
+ dispose(): void;
23
+ setMessage(message: string): void;
24
+ }
25
+ export {};
@@ -0,0 +1,86 @@
1
+ import type { SymbolTheme } from "../symbols.js";
2
+ import type { Component } from "../tui.js";
3
+ /** @internal exported for tests — must stay index-identical to the old regex scan. */
4
+ export declare function mathStartIndex(src: string): number | undefined;
5
+ /** @internal exported for tests — must stay index-identical to the old regex scan. */
6
+ export declare function autolinkSchemeScanIndex(src: string): number | undefined;
7
+ /** @internal exported for tests — must never return false for a src the built-in url regex matches. */
8
+ export declare function urlTokenPossible(src: string): boolean;
9
+ /** Drop all L2 cache entries. Call on theme change to prevent stale styled output. */
10
+ export declare function clearRenderCache(): void;
11
+ /**
12
+ * Default text styling for markdown content.
13
+ * Applied to all text unless overridden by markdown formatting.
14
+ */
15
+ export interface DefaultTextStyle {
16
+ /** Foreground color function */
17
+ color?: (text: string) => string;
18
+ /** Background color function */
19
+ bgColor?: (text: string) => string;
20
+ /** Bold text */
21
+ bold?: boolean;
22
+ /** Italic text */
23
+ italic?: boolean;
24
+ /** Strikethrough text */
25
+ strikethrough?: boolean;
26
+ /** Underline text */
27
+ underline?: boolean;
28
+ }
29
+ /**
30
+ * Stateful incremental code highlighter carrying parser state across pushes.
31
+ * Produced per streaming fence by {@link MarkdownTheme.createHighlightStream}.
32
+ */
33
+ export interface HighlightStreamSession {
34
+ /** Highlight the next chunk and advance parser state. */
35
+ push(chunk: string): string;
36
+ }
37
+ /**
38
+ * Theme functions for markdown elements.
39
+ * Each function takes text and returns styled text with ANSI codes.
40
+ */
41
+ export interface MarkdownTheme {
42
+ heading: (text: string) => string;
43
+ link: (text: string) => string;
44
+ linkUrl: (text: string) => string;
45
+ code: (text: string) => string;
46
+ codeBlock: (text: string) => string;
47
+ codeBlockBorder: (text: string) => string;
48
+ quote: (text: string) => string;
49
+ quoteBorder: (text: string) => string;
50
+ hr: (text: string) => string;
51
+ listBullet: (text: string) => string;
52
+ bold: (text: string) => string;
53
+ italic: (text: string) => string;
54
+ strikethrough: (text: string) => string;
55
+ underline: (text: string) => string;
56
+ highlightCode?: (code: string, lang?: string) => string[];
57
+ /**
58
+ * Create a stateful incremental highlighter for one streaming code fence.
59
+ * `push` receives newline-terminated complete lines (only the final push
60
+ * may omit the trailing newline) and must return highlighted ANSI text for
61
+ * exactly the pushed chunk, byte-identical to highlighting the concatenated
62
+ * text through `highlightCode`. Return null when `lang` is unsupported.
63
+ */
64
+ createHighlightStream?: (lang?: string) => HighlightStreamSession | null;
65
+ /**
66
+ * Resolve a mermaid ASCII rendering by fenced block source text.
67
+ * Return null to fall back to fenced code rendering.
68
+ */
69
+ resolveMermaidAscii?: (source: string, maxWidth?: number) => string | null;
70
+ symbols: SymbolTheme;
71
+ }
72
+ export declare class Markdown implements Component {
73
+ #private;
74
+ setIgnoreTight(ignore: boolean): this;
75
+ constructor(text: string, paddingX: number, paddingY: number, theme: MarkdownTheme, defaultTextStyle?: DefaultTextStyle, codeBlockIndent?: number);
76
+ setText(text: string): boolean;
77
+ invalidate(): void;
78
+ get transientRenderCache(): boolean;
79
+ set transientRenderCache(value: boolean);
80
+ render(width: number): readonly string[];
81
+ }
82
+ /**
83
+ * Render inline markdown (bold, italic, code, links, strikethrough) to a styled string.
84
+ * Unlike the full Markdown component, this produces a single line with no block-level elements.
85
+ */
86
+ export declare function renderInlineMarkdown(text: string, mdTheme: MarkdownTheme, baseColor?: (t: string) => string): string;
@@ -0,0 +1,62 @@
1
+ import type { Component } from "../tui.js";
2
+ import { Ellipsis } from "../utils.js";
3
+ type ScrollbarMode = "auto" | "always" | "never";
4
+ export interface ScrollViewTheme {
5
+ track?: (text: string) => string;
6
+ thumb?: (text: string) => string;
7
+ }
8
+ export interface ScrollViewOptions {
9
+ height: number;
10
+ /** Defaults to "auto". "auto" reserves a scrollbar column only when content overflows. */
11
+ scrollbar?: ScrollbarMode | boolean;
12
+ /** Logical row count for pre-windowed line slices. Defaults to lines.length. */
13
+ totalRows?: number;
14
+ theme?: ScrollViewTheme;
15
+ trackChar?: string;
16
+ thumbChar?: string;
17
+ /**
18
+ * Indicator appended when a row overflows `contentWidth`. Defaults to
19
+ * {@link Ellipsis.Unicode}. Pass {@link Ellipsis.Omit} when callers wrap
20
+ * lines to width themselves and only trailing padding can overflow (e.g.
21
+ * the plan-review overlay), so no stray `…` lands on every padded row.
22
+ */
23
+ ellipsis?: Ellipsis;
24
+ /**
25
+ * Rows moved per keystroke when {@link ScrollView.handleScrollKey} sees a
26
+ * Shift+Arrow (the "scroll faster" affordance). Defaults to 5.
27
+ */
28
+ fastScrollLines?: number;
29
+ }
30
+ /**
31
+ * Fixed-height viewport over pre-rendered lines, with optional right-edge scrollbar.
32
+ *
33
+ * ScrollView owns only the row offset. Callers remain responsible for producing
34
+ * already-wrapped logical lines appropriate for the current render width.
35
+ */
36
+ export declare class ScrollView implements Component {
37
+ #private;
38
+ constructor(lines: readonly string[], options: ScrollViewOptions);
39
+ setLines(lines: readonly string[]): void;
40
+ setTotalRows(totalRows: number | undefined): void;
41
+ setHeight(height: number): void;
42
+ setScrollbar(scrollbar: ScrollViewOptions["scrollbar"]): void;
43
+ getScrollOffset(): number;
44
+ getMaxScrollOffset(): number;
45
+ setScrollOffset(offset: number): void;
46
+ scroll(delta: number): void;
47
+ page(delta: number): void;
48
+ scrollToTop(): void;
49
+ scrollToBottom(): void;
50
+ /**
51
+ * Apply a standard navigation key to the viewport. Shift+Arrow scrolls by
52
+ * {@link ScrollViewOptions.fastScrollLines} (the "scroll faster" affordance);
53
+ * plain Arrow by one line; PageUp/PageDown by a page; Home/End to the ends.
54
+ * Returns true when the key was consumed, so callers can fall through to
55
+ * their own (e.g. vim-style) bindings. Generic on purpose: every ScrollView
56
+ * consumer gets the same scroll keys, including Shift-to-go-faster.
57
+ */
58
+ handleScrollKey(data: string): boolean;
59
+ invalidate(): void;
60
+ render(width: number): readonly string[];
61
+ }
62
+ export {};
@@ -0,0 +1,78 @@
1
+ import { type MouseRoutable, type SgrMouseEvent } from "../mouse.js";
2
+ import type { SymbolTheme } from "../symbols.js";
3
+ import type { Component } from "../tui.js";
4
+ export interface SelectItem {
5
+ value: string;
6
+ label: string;
7
+ description?: string;
8
+ /** Optional type-indicator glyph rendered in an aligned column before the label */
9
+ icon?: string;
10
+ /** Dim hint text shown inline after cursor when this item is selected */
11
+ hint?: string;
12
+ }
13
+ export interface SelectListTheme {
14
+ selectedPrefix: (text: string) => string;
15
+ selectedText: (text: string) => string;
16
+ description: (text: string) => string;
17
+ scrollInfo: (text: string) => string;
18
+ noMatch: (text: string) => string;
19
+ symbols: SymbolTheme;
20
+ /** Style for the type-icon column on unselected rows. Defaults to plain text. */
21
+ icon?: (text: string) => string;
22
+ /** Hover band applied to the full row under the mouse pointer. */
23
+ hovered?: (text: string) => string;
24
+ }
25
+ export interface SelectListTruncatePrimaryContext {
26
+ text: string;
27
+ maxWidth: number;
28
+ columnWidth: number;
29
+ item: SelectItem;
30
+ isSelected: boolean;
31
+ }
32
+ export interface SelectListLayoutOptions {
33
+ minPrimaryColumnWidth?: number;
34
+ maxPrimaryColumnWidth?: number;
35
+ truncatePrimary?: (context: SelectListTruncatePrimaryContext) => string;
36
+ /** Enable type-to-filter search when the item count exceeds maxVisible. Defaults to true. */
37
+ overflowSearch?: boolean;
38
+ /**
39
+ * Wrap long descriptions onto continuation rows indented under the
40
+ * description column instead of truncating. Defaults to false so existing
41
+ * single-line consumers are unaffected. Navigation remains item-to-item;
42
+ * the scrollbar tracks visual rows so the thumb stays correct when items
43
+ * wrap unevenly.
44
+ */
45
+ wrapDescription?: boolean;
46
+ /**
47
+ * Cap wrapped descriptions at this many visual rows; the last kept row is
48
+ * ellipsized. Only meaningful with `wrapDescription`.
49
+ */
50
+ maxDescriptionRows?: number;
51
+ }
52
+ export declare class SelectList implements Component, MouseRoutable {
53
+ #private;
54
+ private readonly items;
55
+ private readonly theme;
56
+ private readonly layout;
57
+ onSelect?: (item: SelectItem) => void;
58
+ onCancel?: () => void;
59
+ onSelectionChange?: (item: SelectItem) => void;
60
+ constructor(items: ReadonlyArray<SelectItem>, maxVisible: number, theme: SelectListTheme, layout?: SelectListLayoutOptions);
61
+ /** Refit the visible row budget (hosts clamp the list to available height). */
62
+ setMaxVisible(rows: number): void;
63
+ setFilter(filter: string): void;
64
+ setSelectedIndex(index: number): void;
65
+ /** Resolve a 0-based rendered-line index to a filtered-item index. */
66
+ hitTest(line: number): number | undefined;
67
+ /** Highlight the item under the pointer (null clears). */
68
+ setHoverIndex(index: number | null): void;
69
+ /** Move the selection one step for a wheel notch. */
70
+ handleWheel(delta: -1 | 1): void;
71
+ /** Mouse click: select the item under the pointer and confirm it. */
72
+ clickItem(index: number): void;
73
+ routeMouse(event: SgrMouseEvent, line: number, _col: number): void;
74
+ invalidate(): void;
75
+ render(width: number): readonly string[];
76
+ handleInput(keyData: string): void;
77
+ getSelectedItem(): SelectItem | null;
78
+ }
@@ -0,0 +1,129 @@
1
+ import type { SgrMouseEvent } from "../mouse.js";
2
+ import type { Component } from "../tui.js";
3
+ export interface SettingItem {
4
+ /** Unique identifier for this setting */
5
+ id: string;
6
+ /** Display label (left side) */
7
+ label: string;
8
+ /** Optional description shown when selected */
9
+ description?: string;
10
+ /** Optional risk note shown in warning styling above the description, with a glyph on the row. */
11
+ warning?: string;
12
+ /** Current value to display (right side) */
13
+ currentValue: string;
14
+ /** If provided, Enter/Space cycles through these values */
15
+ values?: string[];
16
+ /** If provided, Enter opens this submenu. Receives current value and done callback. */
17
+ submenu?: (currentValue: string, done: (selectedValue?: string) => void) => Component;
18
+ /** True when the displayed setting differs from its default value. */
19
+ changed?: boolean;
20
+ /** Render as a non-interactive section heading. Skipped by navigation and search. */
21
+ heading?: boolean;
22
+ }
23
+ export interface SettingsListTheme {
24
+ label: (text: string, selected: boolean, changed: boolean) => string;
25
+ value: (text: string, selected: boolean, changed: boolean) => string;
26
+ description: (text: string) => string;
27
+ /** Style for risk notes and the row warning glyph. Falls back to `description` when omitted. */
28
+ warning?: (text: string) => string;
29
+ /** Glyph marking rows that carry a `warning`. Omitted hides the row marker. */
30
+ warningMark?: string;
31
+ cursor: string;
32
+ hint: (text: string) => string;
33
+ /** Style for section heading rows (dimmed when outside the active section). Falls back to `hint` when omitted. */
34
+ heading?: (text: string, dimmed: boolean) => string;
35
+ /** Style for sidebar section names in the split layout. Falls back to label/hint. */
36
+ section?: (text: string, active: boolean) => string;
37
+ /** Hover band applied to the full row under the mouse pointer. */
38
+ hovered?: (text: string) => string;
39
+ }
40
+ /** Optional behavior overrides for {@link SettingsList}. */
41
+ export interface SettingsListOptions {
42
+ /**
43
+ * "auto" (default) renders the section sidebar layout when headings exist
44
+ * and the width allows; "flat" always renders inline heading rows.
45
+ */
46
+ layout?: "auto" | "flat";
47
+ /**
48
+ * When false, printable input is ignored (no internal type-to-filter) and
49
+ * the search status line is never rendered. Use when a parent component
50
+ * owns the query. Default true.
51
+ */
52
+ typeToSearch?: boolean;
53
+ /** Text shown when the list has no items at all. */
54
+ emptyText?: string;
55
+ /**
56
+ * Footer hint line (hint-styled, replaces the default navigation hint).
57
+ * An empty string removes the hint row and its leading blank entirely —
58
+ * use when the host renders its own footer.
59
+ */
60
+ hint?: string;
61
+ /** Fixed split-sidebar width (columns incl. indent+gap); default derives from section names. */
62
+ sidebarWidth?: number;
63
+ }
64
+ /** Searchable text for a setting item: label, id, value, description, warning, and cycle values. */
65
+ export declare function getSettingItemFilterText(item: SettingItem): string;
66
+ export declare class SettingsList implements Component {
67
+ #private;
68
+ /** Fired when the selected item changes (navigation, filtering, or setItems). */
69
+ onSelectionChange?: (item: SettingItem | undefined) => void;
70
+ constructor(items: SettingItem[], maxVisible: number, theme: SettingsListTheme, onChange: (id: string, newValue: string) => void, onCancel: () => void, options?: SettingsListOptions);
71
+ /** The currently selected item, or undefined when empty or on a heading. */
72
+ getSelectedItem(): SettingItem | undefined;
73
+ /** Move selection to the item with `id`. Returns false when it is not visible. */
74
+ selectItem(id: string): boolean;
75
+ /** True while keyboard focus is on the section headings instead of the setting rows. */
76
+ get sectionFocused(): boolean;
77
+ /** Whether section focus has anywhere to go: 2+ derived sections in the current view. */
78
+ hasSectionFocusTargets(): boolean;
79
+ /**
80
+ * Toggle keyboard focus between section headings and setting rows. While
81
+ * focused, Up/Down jump whole sections and Enter/Esc return to the rows.
82
+ * Engages only when {@link hasSectionFocusTargets}; returns the new state.
83
+ */
84
+ toggleSectionFocus(): boolean;
85
+ /** True while an item submenu owns input. */
86
+ hasOpenSubmenu(): boolean;
87
+ /** Resize the visible viewport (fullscreen hosts call this every render). */
88
+ setMaxVisible(rows: number): void;
89
+ /** Move the selection one step for a wheel notch. */
90
+ handleWheel(delta: -1 | 1): void;
91
+ /** Move the selection one step for a wheel notch if the pointer is within the settings pane. */
92
+ handleWheelAt(delta: -1 | 1, _line: number, col: number): boolean;
93
+ /** Highlight the item under the pointer (null clears). */
94
+ setHoverItem(id: string | null): void;
95
+ /**
96
+ * Resolve a pointer position against the last rendered frame. `line` is the
97
+ * 0-based content-line index within this component's render output, `col`
98
+ * the 0-based column. Sidebar rows resolve to the section's first item.
99
+ */
100
+ hitTest(line: number, col: number): string | undefined;
101
+ /**
102
+ * Like {@link hitTest}, but only rows the pointer is visually on: sidebar
103
+ * jump targets are excluded so hovering section names does not light up
104
+ * pane rows.
105
+ */
106
+ hoverTest(line: number, col: number): string | undefined;
107
+ /**
108
+ * Route a mouse event into an open submenu (coordinates are local to this
109
+ * list's rendered lines). Returns false when no submenu is open; submenus
110
+ * that do not implement {@link MouseRoutable} consume the event silently.
111
+ */
112
+ routeSubmenuMouse(event: SgrMouseEvent, line: number, col: number): boolean;
113
+ getSearchQuery(): string;
114
+ hasSearchQuery(): boolean;
115
+ clearSearch(): void;
116
+ /** Update an item's currentValue */
117
+ updateValue(id: string, newValue: string): void;
118
+ /**
119
+ * Replace the entire items array. Selection is preserved by item id when
120
+ * the previous selection still survives the active filter, otherwise
121
+ * clamped to the last filtered item (or 0 if there are no matches).
122
+ * An open submenu is left untouched — its lifetime is bounded by its own
123
+ * done callback, and `#closeSubmenu` re-resolves the restored item on exit.
124
+ */
125
+ setItems(items: SettingItem[]): void;
126
+ invalidate(): void;
127
+ render(width: number): readonly string[];
128
+ handleInput(data: string): void;
129
+ }
@@ -0,0 +1,11 @@
1
+ import type { Component } from "../tui.js";
2
+ /**
3
+ * Spacer component that renders empty lines
4
+ */
5
+ export declare class Spacer implements Component {
6
+ #private;
7
+ constructor(lines?: number);
8
+ setLines(lines: number): void;
9
+ invalidate(): void;
10
+ render(_width: number): readonly string[];
11
+ }
@@ -0,0 +1,89 @@
1
+ import type { Component } from "../tui.js";
2
+ /** Tab definition */
3
+ export interface Tab {
4
+ /** Unique identifier for the tab */
5
+ id: string;
6
+ /** Display label shown in the tab bar */
7
+ label: string;
8
+ /** Compact form (e.g. just the icon) used when the bar must shrink to fit one line. */
9
+ short?: string;
10
+ /** Render with the muted style and skip during keyboard navigation. */
11
+ muted?: boolean;
12
+ }
13
+ /** Theme for styling the tab bar */
14
+ export interface TabBarTheme {
15
+ /** Style for the label prefix (e.g., "Settings:") */
16
+ label: (text: string) => string;
17
+ /** Style for the currently active tab */
18
+ activeTab: (text: string) => string;
19
+ /** Style for inactive tabs */
20
+ inactiveTab: (text: string) => string;
21
+ /** Style for the hint text (e.g., "(tab to cycle)") */
22
+ hint: (text: string) => string;
23
+ /** Style for muted tabs. Falls back to `inactiveTab` when omitted. */
24
+ mutedTab?: (text: string) => string;
25
+ /** Style for the tab under the mouse pointer. Falls back to `inactiveTab` when omitted. */
26
+ hoverTab?: (text: string) => string;
27
+ }
28
+ /**
29
+ * Horizontal tab bar component.
30
+ *
31
+ * @example
32
+ * ```ts
33
+ * const tabs = [
34
+ * { id: "config", label: "Config" },
35
+ * { id: "tools", label: "Tools" },
36
+ * ];
37
+ * const tabBar = new TabBar("Settings", tabs, theme);
38
+ * tabBar.onTabChange = (tab) => console.log(`Switched to ${tab.id}`);
39
+ * ```
40
+ */
41
+ export declare class TabBar implements Component {
42
+ #private;
43
+ /** Callback fired when the active tab changes */
44
+ onTabChange?: (tab: Tab, index: number) => void;
45
+ /** Render the trailing "(tab to cycle)" hint. Disable when the host folds the hint into its own footer. */
46
+ showHint: boolean;
47
+ constructor(label: string, tabs: Tab[], theme: TabBarTheme, initialIndex?: number);
48
+ /** Get the currently active tab */
49
+ getActiveTab(): Tab;
50
+ /** Get the index of the currently active tab */
51
+ getActiveIndex(): number;
52
+ /** Set the active tab by index (clamped to valid range) */
53
+ setActiveIndex(index: number): void;
54
+ /**
55
+ * Replace the tab set without firing onTabChange. The active tab is
56
+ * preserved by id when it survives the swap (or forced via `activeId`);
57
+ * otherwise the index is clamped.
58
+ */
59
+ setTabs(tabs: Tab[], activeId?: string): void;
60
+ /** Set the active tab by id without firing onTabChange. Returns false when the id is unknown. */
61
+ setActiveById(id: string): boolean;
62
+ /** Activate the tab with `id`, firing onTabChange when it changes. Muted tabs are ignored. */
63
+ selectTab(id: string): boolean;
64
+ /** Move to the next non-muted tab (wraps to first tab after last) */
65
+ nextTab(): void;
66
+ /** Move to the previous non-muted tab (wraps to last tab before first) */
67
+ prevTab(): void;
68
+ invalidate(): void;
69
+ /**
70
+ * Handle keyboard input for tab navigation.
71
+ * @returns true if the input was handled, false otherwise
72
+ */
73
+ handleInput(data: string): boolean;
74
+ /**
75
+ * Render the tab bar. When the full labels overflow the width, tabs are
76
+ * collapsed to their `short` form one by one — starting with the tabs
77
+ * farthest from the active one — until the bar fits on a single line.
78
+ * Wrapping to multiple lines is the last resort.
79
+ */
80
+ render(width: number): readonly string[];
81
+ /**
82
+ * Resolve a pointer position against the last rendered frame. `line` is the
83
+ * 0-based line index within this component's render output, `col` the
84
+ * 0-based column.
85
+ */
86
+ tabAt(line: number, col: number): Tab | undefined;
87
+ /** Highlight the tab under the pointer (null clears). */
88
+ setHoverTab(id: string | null): void;
89
+ }