@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.
- package/CHANGELOG.md +10 -1
- package/THIRD-PARTY-NOTICES.txt +22909 -0
- package/dist/types/autocomplete.d.ts +127 -0
- package/dist/types/bracketed-paste.d.ts +51 -0
- package/dist/types/components/box.d.ts +31 -0
- package/dist/types/components/cancellable-loader.d.ts +21 -0
- package/dist/types/components/composer/borderless.d.ts +8 -0
- package/dist/types/components/composer/box.d.ts +2 -0
- package/dist/types/components/composer/claude.d.ts +8 -0
- package/dist/types/components/composer/field.d.ts +3 -0
- package/dist/types/components/composer/index.d.ts +9 -0
- package/dist/types/components/composer/pi.d.ts +2 -0
- package/dist/types/components/composer/rail.d.ts +3 -0
- package/dist/types/components/composer/registry.d.ts +12 -0
- package/dist/types/components/composer/rule.d.ts +7 -0
- package/dist/types/components/composer/types.d.ts +90 -0
- package/dist/types/components/editor.d.ts +213 -0
- package/dist/types/components/image.d.ts +162 -0
- package/dist/types/components/input.d.ts +25 -0
- package/dist/types/components/loader.d.ts +25 -0
- package/dist/types/components/markdown.d.ts +86 -0
- package/dist/types/components/scroll-view.d.ts +62 -0
- package/dist/types/components/select-list.d.ts +78 -0
- package/dist/types/components/settings-list.d.ts +129 -0
- package/dist/types/components/spacer.d.ts +11 -0
- package/dist/types/components/tab-bar.d.ts +89 -0
- package/dist/types/components/text.d.ts +27 -0
- package/dist/types/components/truncated-text.d.ts +10 -0
- package/dist/types/deccara.d.ts +49 -0
- package/dist/types/desktop-notify.d.ts +52 -0
- package/dist/types/editor-component.d.ts +38 -0
- package/dist/types/fuzzy.d.ts +48 -0
- package/dist/types/index.d.ts +33 -0
- package/dist/types/keybindings.d.ts +202 -0
- package/dist/types/keys.d.ts +210 -0
- package/dist/types/kill-ring.d.ts +20 -0
- package/dist/types/kitty-graphics.d.ts +76 -0
- package/dist/types/latex-block.d.ts +8 -0
- package/dist/types/latex-to-unicode.d.ts +50 -0
- package/dist/types/loop-watchdog.d.ts +49 -0
- package/dist/types/mouse.d.ts +67 -0
- package/dist/types/stdin-buffer.d.ts +60 -0
- package/dist/types/symbols.d.ts +25 -0
- package/dist/types/terminal-capabilities.d.ts +338 -0
- package/dist/types/terminal.d.ts +215 -0
- package/dist/types/tmux.d.ts +6 -0
- package/dist/types/ttyid.d.ts +9 -0
- package/dist/types/tui.d.ts +332 -0
- package/dist/types/utils.d.ts +112 -0
- 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
|
+
}
|