Ops
The ops module provides builders for describing terminal UI as a flat array of directives. Each frame is a complete snapshot. Build ops, pass them to term.render(), and write the output bytes.
Building a frame
Section titled “Building a frame”import { function close(): CloseElement
close, function open(id: string, props?: Omit<OpenElement, "directive" | "id">): OpenElement
open, function rgba(r: number, g: number, b: number, a?: number): number
rgba, function text(content: string, props?: Omit<Text, "directive" | "content">): Text
text, const grow: (min?: number, max?: number) => SizingAxis
grow } from "@bomb.sh/tty";
let let ops: (CloseElement | OpenElement | Text)[]
ops = [ 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?: "ttb" | "ltr"
direction: "ttb" }, }), function open(id: string, props?: Omit<OpenElement, "directive" | "id">): OpenElement
open("panel", { 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: { padding?: { left?: number; right?: number; top?: number; bottom?: number;}
padding: { left?: number
left: 1, right?: number
right: 1, top?: number
top: 1, bottom?: number
bottom: 1 } }, border?: { color: number; bg?: number; left?: BorderSide; right?: BorderSide; top?: BorderSide; bottom?: BorderSide;}
border: { color: number
color: function rgba(r: number, g: number, b: number, a?: number): number
rgba(0, 255, 0), left?: BorderSide
left: 1, right?: BorderSide
right: 1, top?: BorderSide
top: 1, bottom?: BorderSide
bottom: 1 }, cornerRadius?: { tl?: number; tr?: number; bl?: number; br?: number;}
cornerRadius: { tl?: number
tl: 1, tr?: number
tr: 1, bl?: number
bl: 1, br?: number
br: 1 }, }), function text(content: string, props?: Omit<Text, "directive" | "content">): Text
text("Hello, World!"), function close(): CloseElement
close(), function close(): CloseElement
close(),];open(id, props?)
Section titled “open(id, props?)”Opens a layout element. Every element needs a unique string id for pointer hit-testing and layout queries.
Layout
Section titled “Layout”| Property | Type | Description |
|---|---|---|
width | SizingAxis | Horizontal sizing (fit, grow, percent, fixed) |
height | SizingAxis | Vertical sizing |
padding | { left?, right?, top?, bottom? } | Inner padding in cells |
gap | number | Gap between children |
direction | "ltr" | "ttb" | Child layout direction |
alignX | "left" | "center" | "right" | Horizontal alignment |
alignY | "top" | "center" | "bottom" | Vertical alignment |
| Property | Type | Description |
|---|---|---|
bg | number | Background color (packed RGBA via rgba()) |
cornerRadius | { tl?, tr?, bl?, br? } | Rounded corners |
border | { color, bg?, left?, right?, top?, bottom? } | Border sides (number or { width, color?, bg? }) |
clip | { horizontal?, vertical? } | Clip overflowing content |
Floating
Section titled “Floating”| Property | Type | Description |
|---|---|---|
floating.x, floating.y | number | Position offset |
floating.expand | { width?, height? } | Expand floating bounds |
floating.attachTo | "none" | "parent" | "element" | "root" | Attachment target |
floating.attachPoints | { element?, parent? } | Anchor points |
floating.pointerCaptureMode | "capture" | "passthrough" | Pointer event capture |
floating.clipTo | "none" | "attached-parent" | Clip to attached parent |
floating.zIndex | number | Stacking order |
Transitions
Section titled “Transitions”import { function open(id: string, props?: Omit<OpenElement, "directive" | "id">): OpenElement
open, function rgba(r: number, g: number, b: number, a?: number): number
rgba } from "@bomb.sh/tty";
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: { type: "fixed"
type: "fixed", value: number
value: 20 } }, bg?: number
bg: function rgba(r: number, g: number, b: number, a?: number): number
rgba(30, 30, 40), transition?: Transition
transition: { Transition.duration: number
duration: 0.3, Transition.easing?: Easing
easing: "easeInOut", Transition.properties: TransitionProperty[]
properties: ["width", "bg"], },});| Property | Type | Description |
|---|---|---|
duration | number | Duration in seconds |
easing | "linear" | "easeIn" | "easeOut" | "easeInOut" | Easing function |
properties | TransitionProperty[] | Properties to animate |
interactive | boolean | Allow interaction during transition |
Transition properties: "x", "y", "position", "width", "height", "size", "bg", "overlay", "borderColor", "borderWidth", "all".
text(content, props?)
Section titled “text(content, props?)”Renders a text node inside the current open element.
| Property | Type | Description |
|---|---|---|
color | number | Foreground color |
bg | number | Background color |
fontSize | number | Font size |
fontId | number | Font identifier |
wrap | number | Wrap width |
attrs | number | Text attributes (bold, italic, etc.) |
close()
Section titled “close()”Closes the current open element. Ops must be properly nested. Every open needs a matching close.
snapshot(ops)
Section titled “snapshot(ops)”Pre-packs an op array into a reusable snapshot op. Useful when part of the UI is static across frames.
import { function close(): CloseElement
close, function open(id: string, props?: Omit<OpenElement, "directive" | "id">): OpenElement
open, function snapshot(ops: Op[]): Op
snapshot, function text(content: string, props?: Omit<Text, "directive" | "content">): Text
text, const grow: (min?: number, max?: number) => SizingAxis
grow } from "@bomb.sh/tty";
let let chrome: Op
chrome = function snapshot(ops: Op[]): Op
snapshot([ function open(id: string, props?: Omit<OpenElement, "directive" | "id">): OpenElement
open("header", { 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: { type: "fixed"
type: "fixed", value: number
value: 1 } } }), function text(content: string, props?: Omit<Text, "directive" | "content">): Text
text("My App"), function close(): CloseElement
close(),]);
// Each frame: [chrome, ...dynamicOps]rgba(r, g, b, a?)
Section titled “rgba(r, g, b, a?)”Packs an RGBA color into a 32-bit integer. Alpha defaults to 255.
import { function rgba(r: number, g: number, b: number, a?: number): number
rgba } from "@bomb.sh/tty";
function rgba(r: number, g: number, b: number, a?: number): number
rgba(255, 0, 0); // redfunction rgba(r: number, g: number, b: number, a?: number): number
rgba(0, 255, 0, 128); // semi-transparent greenSizing helpers
Section titled “Sizing helpers”import { const fit: (min?: number, max?: number) => SizingAxis
fit, const grow: (min?: number, max?: number) => SizingAxis
grow, const percent: (value: number) => SizingAxis
percent, const fixed: (value: number) => SizingAxis
fixed } from "@bomb.sh/tty";
function fit(min?: number, max?: number): SizingAxis
fit(); // shrink to contentfunction fit(min?: number, max?: number): SizingAxis
fit(10, 40); // fit with min/maxfunction grow(min?: number, max?: number): SizingAxis
grow(); // expand to fill available spacefunction grow(min?: number, max?: number): SizingAxis
grow(5, 50); // grow with min/maxfunction percent(value: number): SizingAxis
percent(0.5); // 50% of parentfunction fixed(value: number): SizingAxis
fixed(20); // exactly 20 cells| Type | Description |
|---|---|
Op | Union of all directive types |
OpenElement | Element opened by open() |
Text | Text node |
CloseElement | Element closed by close() |
SizingAxis | { type: "fit" | "grow" | "percent" | "fixed", ... } |
Transition | Transition configuration |
BorderSide | number or { width, color?, bg? } |