Term
The term module creates a WASM-backed renderer that converts UI ops into ANSI escape sequences.
createTerm(options)
Section titled “createTerm(options)”Creates a renderer instance. Both dimensions are required.
import { function createTerm(options: TermOptions): Promise<Term>
createTerm } from "@bomb.sh/tty";
async function function main(): Promise<void>
main() { let let term: Term
term = await function createTerm(options: TermOptions): Promise<Term>
createTerm({ TermOptions.width: number
width: 80, TermOptions.height: number
height: 24 });}| Option | Type | Description |
|---|---|---|
width | number | Terminal width in columns |
height | number | Terminal height in rows |
Returns a Term with a single render() method.
term.render(ops, options?)
Section titled “term.render(ops, options?)”Renders a frame and returns the result.
import { function close(): CloseElement
close, function createTerm(options: TermOptions): Promise<Term>
createTerm, function open(id: string, props?: Omit<OpenElement, "directive" | "id">): OpenElement
open, function text(content: string, props?: Omit<Text, "directive" | "content">): Text
text, const grow: (min?: number, max?: number) => SizingAxis
grow } from "@bomb.sh/tty";
async function function main(): Promise<void>
main() { let let term: Term
term = await function createTerm(options: TermOptions): Promise<Term>
createTerm({ TermOptions.width: number
width: 80, TermOptions.height: number
height: 24 });
let { let output: Uint8Array<ArrayBufferLike>
output, let events: PointerEvent[]
events, let info: RenderInfo
info, let errors: ClayError[]
errors, let animating: boolean
animating } = let term: Term
term.Term.render(ops: Op[], options?: RenderOptions): RenderResult
render([ function open(id: string, props?: Omit<OpenElement, "directive" | "id">): OpenElement
open("root", { layout?: { width?: SizingAxis; height?: SizingAxis; padding?: { left?: number; right?: number; top?: number; bottom?: number; }; gap?: number; direction?: "ltr" | "ttb"; alignX?: "left" | "center" | "right"; alignY?: "top" | "center" | "bottom";}
layout: { width?: SizingAxis
width: function grow(min?: number, max?: number): SizingAxis
grow(), height?: SizingAxis
height: function grow(min?: number, max?: number): SizingAxis
grow() } }), function text(content: string, props?: Omit<Text, "directive" | "content">): Text
text("Hello"), function close(): CloseElement
close(), ]);
var process: NodeJS.Process
process.NodeJS.Process.stdout: NodeJS.WriteStream & { fd: 1;}
The process.stdout property returns a stream connected tostdout (fd 1). It is a net.Socket (which is a Duplex stream) unless fd 1 refers to a file, in which case it is
a Writable stream.
For example, to copy process.stdin to process.stdout:
import { stdin, stdout } from 'node:process';
stdin.pipe(stdout);
process.stdout differs from other Node.js streams in important ways. See note on process I/O for more information.
stdout.Socket.write(buffer: Uint8Array | string, cb?: (err?: Error | null) => void): boolean (+1 overload)
Sends data on the socket. The second parameter specifies the encoding in the
case of a string. It defaults to UTF8 encoding.
Returns true if the entire data was flushed successfully to the kernel
buffer. Returns false if all or part of the data was queued in user memory.'drain' will be emitted when the buffer is again free.
The optional callback parameter will be executed when the data is finally
written out, which may not be immediately.
See Writable stream write() method for more
information.
write(let output: Uint8Array<ArrayBufferLike>
output);}RenderOptions
Section titled “RenderOptions”| Option | Type | Description |
|---|---|---|
pointer | { x, y, down } | Pointer position and button state for hit testing |
deltaTime | number | Seconds since last frame (for transitions). Auto-computed if omitted |
mode | "line" | Render into a line region instead of full screen |
row | number | Starting row for line mode (1-based, DSR format) |
Pointer detection
Section titled “Pointer detection”Pass pointer state to get hit-testing events alongside output:
import { function close(): CloseElement
close, function createTerm(options: TermOptions): Promise<Term>
createTerm, const fixed: (value: number) => SizingAxis
fixed, const grow: (min?: number, max?: number) => SizingAxis
grow, function open(id: string, props?: Omit<OpenElement, "directive" | "id">): OpenElement
open, function text(content: string, props?: Omit<Text, "directive" | "content">): Text
text } from "@bomb.sh/tty";
async function function main(): Promise<void>
main() { let let term: Term
term = await function createTerm(options: TermOptions): Promise<Term>
createTerm({ TermOptions.width: number
width: 80, TermOptions.height: number
height: 24 });
let { let output: Uint8Array<ArrayBufferLike>
output, let events: PointerEvent[]
events } = let term: Term
term.Term.render(ops: Op[], options?: RenderOptions): RenderResult
render( [ function open(id: string, props?: Omit<OpenElement, "directive" | "id">): OpenElement
open("root", { layout?: { width?: SizingAxis; height?: SizingAxis; padding?: { left?: number; right?: number; top?: number; bottom?: number; }; gap?: number; direction?: "ltr" | "ttb"; alignX?: "left" | "center" | "right"; alignY?: "top" | "center" | "bottom";}
layout: { width?: SizingAxis
width: function grow(min?: number, max?: number): SizingAxis
grow(), height?: SizingAxis
height: function grow(min?: number, max?: number): SizingAxis
grow(), direction?: "ltr" | "ttb"
direction: "ltr" } }), function open(id: string, props?: Omit<OpenElement, "directive" | "id">): OpenElement
open("sidebar", { layout?: { width?: SizingAxis; height?: SizingAxis; padding?: { left?: number; right?: number; top?: number; bottom?: number; }; gap?: number; direction?: "ltr" | "ttb"; alignX?: "left" | "center" | "right"; alignY?: "top" | "center" | "bottom";}
layout: { width?: SizingAxis
width: function fixed(value: number): SizingAxis
fixed(20), height?: SizingAxis
height: function grow(min?: number, max?: number): SizingAxis
grow() } }), function text(content: string, props?: Omit<Text, "directive" | "content">): Text
text("Sidebar"), function close(): CloseElement
close(), function open(id: string, props?: Omit<OpenElement, "directive" | "id">): OpenElement
open("main", { layout?: { width?: SizingAxis; height?: SizingAxis; padding?: { left?: number; right?: number; top?: number; bottom?: number; }; gap?: number; direction?: "ltr" | "ttb"; alignX?: "left" | "center" | "right"; alignY?: "top" | "center" | "bottom";}
layout: { width?: SizingAxis
width: function grow(min?: number, max?: number): SizingAxis
grow(), height?: SizingAxis
height: function grow(min?: number, max?: number): SizingAxis
grow() } }), function text(content: string, props?: Omit<Text, "directive" | "content">): Text
text("Main content"), function close(): CloseElement
close(), function close(): CloseElement
close(), ], { RenderOptions.pointer?: { x: number; y: number; down: boolean;}
pointer: { x: number
x: 5, y: number
y: 2, down: boolean
down: false } }, );
for (let let event: PointerEvent
event of let events: PointerEvent[]
events) { // { type: "pointerenter", id: "sidebar" } // { type: "pointerleave", id: "sidebar" } // { type: "pointerclick", id: "main" } }}RenderResult
Section titled “RenderResult”| Property | Type | Description |
|---|---|---|
output | Uint8Array | ANSI bytes to write to stdout |
events | PointerEvent[] | Pointer enter/leave/click events |
info | RenderInfo | Element bounds lookup |
errors | ClayError[] | Layout errors from Clay |
animating | boolean | true when transitions are still running |
RenderInfo.get(id)
Section titled “RenderInfo.get(id)”Returns element bounds for a given id, or undefined if not found:
import { function close(): CloseElement
close, function createTerm(options: TermOptions): Promise<Term>
createTerm, function open(id: string, props?: Omit<OpenElement, "directive" | "id">): OpenElement
open, function text(content: string, props?: Omit<Text, "directive" | "content">): Text
text, const grow: (min?: number, max?: number) => SizingAxis
grow } from "@bomb.sh/tty";
async function function main(): Promise<void>
main() { let let term: Term
term = await function createTerm(options: TermOptions): Promise<Term>
createTerm({ TermOptions.width: number
width: 80, TermOptions.height: number
height: 24 }); let { let info: RenderInfo
info } = let term: Term
term.Term.render(ops: Op[], options?: RenderOptions): RenderResult
render([ function open(id: string, props?: Omit<OpenElement, "directive" | "id">): OpenElement
open("root", { layout?: { width?: SizingAxis; height?: SizingAxis; padding?: { left?: number; right?: number; top?: number; bottom?: number; }; gap?: number; direction?: "ltr" | "ttb"; alignX?: "left" | "center" | "right"; alignY?: "top" | "center" | "bottom";}
layout: { width?: SizingAxis
width: function grow(min?: number, max?: number): SizingAxis
grow(), height?: SizingAxis
height: function grow(min?: number, max?: number): SizingAxis
grow() } }), function text(content: string, props?: Omit<Text, "directive" | "content">): Text
text("Hello"), function close(): CloseElement
close(), ]); let let bounds: BoundingBox | undefined
bounds = let info: RenderInfo
info.RenderInfo.get(id: string): ElementInfo | undefined
get("root")?.ElementInfo.bounds: BoundingBox | undefined
bounds;}PointerEvent
Section titled “PointerEvent”| Type | Shape |
|---|---|
pointerenter | { type: "pointerenter", id: string } |
pointerleave | { type: "pointerleave", id: string } |
pointerclick | { type: "pointerclick", id: string } |
ClayError
Section titled “ClayError”Layout errors include a type and message. Known error types:
TEXT_MEASUREMENT_FUNCTION_NOT_PROVIDEDARENA_CAPACITY_EXCEEDEDELEMENTS_CAPACITY_EXCEEDEDTEXT_MEASUREMENT_CAPACITY_EXCEEDEDDUPLICATE_IDFLOATING_CONTAINER_PARENT_NOT_FOUNDPERCENTAGE_OVER_1INTERNAL_ERRORUNBALANCED_OPEN_CLOSECLIP_DEPTH_EXCEEDED
Frame loop with transitions
Section titled “Frame loop with transitions”When using transitions, gate your render loop on animating:
import { function close(): CloseElement
close, function createTerm(options: TermOptions): Promise<Term>
createTerm, function open(id: string, props?: Omit<OpenElement, "directive" | "id">): OpenElement
open, function text(content: string, props?: Omit<Text, "directive" | "content">): Text
text, const grow: (min?: number, max?: number) => SizingAxis
grow } from "@bomb.sh/tty";
async function function main(): Promise<void>
main() { let let term: Term
term = await function createTerm(options: TermOptions): Promise<Term>
createTerm({ TermOptions.width: number
width: 80, TermOptions.height: number
height: 24 });
function function (local function) frame(): void
frame() { let { let output: Uint8Array<ArrayBufferLike>
output, let animating: boolean
animating } = let term: Term
term.Term.render(ops: Op[], options?: RenderOptions): RenderResult
render([ function open(id: string, props?: Omit<OpenElement, "directive" | "id">): OpenElement
open("root", { layout?: { width?: SizingAxis; height?: SizingAxis; padding?: { left?: number; right?: number; top?: number; bottom?: number; }; gap?: number; direction?: "ltr" | "ttb"; alignX?: "left" | "center" | "right"; alignY?: "top" | "center" | "bottom";}
layout: { width?: SizingAxis
width: function grow(min?: number, max?: number): SizingAxis
grow(), height?: SizingAxis
height: function grow(min?: number, max?: number): SizingAxis
grow() } }), function text(content: string, props?: Omit<Text, "directive" | "content">): Text
text("Animating..."), function close(): CloseElement
close(), ]); var process: NodeJS.Process
process.NodeJS.Process.stdout: NodeJS.WriteStream & { fd: 1;}
The process.stdout property returns a stream connected tostdout (fd 1). It is a net.Socket (which is a Duplex stream) unless fd 1 refers to a file, in which case it is
a Writable stream.
For example, to copy process.stdin to process.stdout:
import { stdin, stdout } from 'node:process';
stdin.pipe(stdout);
process.stdout differs from other Node.js streams in important ways. See note on process I/O for more information.
stdout.Socket.write(buffer: Uint8Array | string, cb?: (err?: Error | null) => void): boolean (+1 overload)
Sends data on the socket. The second parameter specifies the encoding in the
case of a string. It defaults to UTF8 encoding.
Returns true if the entire data was flushed successfully to the kernel
buffer. Returns false if all or part of the data was queued in user memory.'drain' will be emitted when the buffer is again free.
The optional callback parameter will be executed when the data is finally
written out, which may not be immediately.
See Writable stream write() method for more
information.
write(let output: Uint8Array<ArrayBufferLike>
output); if (let animating: boolean
animating) function setTimeout<[]>(callback: () => void, delay?: number): NodeJS.Timeout (+1 overload)
Schedules execution of a one-time callback after delay milliseconds.
The callback will likely not be invoked in precisely delay milliseconds.
Node.js makes no guarantees about the exact timing of when callbacks will fire,
nor of their ordering. The callback will be called as close as possible to the
time specified.
When delay is larger than 2147483647 or less than 1 or NaN, the delay
will be set to 1. Non-integer delays are truncated to an integer.
If callback is not a function, a TypeError will be thrown.
This method has a custom variant for promises that is available using
timersPromises.setTimeout().
setTimeout(function (local function) frame(): void
frame, 16); }
function (local function) frame(): void
frame();}