Skip to content

Term

The term module creates a WASM-backed renderer that converts UI ops into ANSI escape sequences.

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 });
}
OptionTypeDescription
widthnumberTerminal width in columns
heightnumberTerminal height in rows

Returns a Term with a single render() method.

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.

@sincev0.1.90

@paramencoding Only used when data is string.

write
(
let output: Uint8Array<ArrayBufferLike>
output
);
}
OptionTypeDescription
pointer{ x, y, down }Pointer position and button state for hit testing
deltaTimenumberSeconds since last frame (for transitions). Auto-computed if omitted
mode"line"Render into a line region instead of full screen
rownumberStarting row for line mode (1-based, DSR format)

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" }
}
}
PropertyTypeDescription
outputUint8ArrayANSI bytes to write to stdout
eventsPointerEvent[]Pointer enter/leave/click events
infoRenderInfoElement bounds lookup
errorsClayError[]Layout errors from Clay
animatingbooleantrue when transitions are still running

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
;
}
TypeShape
pointerenter{ type: "pointerenter", id: string }
pointerleave{ type: "pointerleave", id: string }
pointerclick{ type: "pointerclick", id: string }

Layout errors include a type and message. Known error types:

  • TEXT_MEASUREMENT_FUNCTION_NOT_PROVIDED
  • ARENA_CAPACITY_EXCEEDED
  • ELEMENTS_CAPACITY_EXCEEDED
  • TEXT_MEASUREMENT_CAPACITY_EXCEEDED
  • DUPLICATE_ID
  • FLOATING_CONTAINER_PARENT_NOT_FOUND
  • PERCENTAGE_OVER_1
  • INTERNAL_ERROR
  • UNBALANCED_OPEN_CLOSE
  • CLIP_DEPTH_EXCEEDED

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.

@sincev0.1.90

@paramencoding Only used when data is string.

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().

@sincev0.0.1

@paramcallback The function to call when the timer elapses.

@paramdelay The number of milliseconds to wait before calling the callback. Default: 1.

@paramargs Optional arguments to pass when the callback is called.

@returnsfor use with clearTimeout()

setTimeout
(
function (local function) frame(): void
frame
, 16);
}
function (local function) frame(): void
frame
();
}