@rlabs-inc/tui 0.1.0
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/README.md +141 -0
- package/index.ts +45 -0
- package/package.json +59 -0
- package/src/api/index.ts +7 -0
- package/src/api/mount.ts +230 -0
- package/src/engine/arrays/core.ts +60 -0
- package/src/engine/arrays/dimensions.ts +68 -0
- package/src/engine/arrays/index.ts +166 -0
- package/src/engine/arrays/interaction.ts +112 -0
- package/src/engine/arrays/layout.ts +175 -0
- package/src/engine/arrays/spacing.ts +100 -0
- package/src/engine/arrays/text.ts +55 -0
- package/src/engine/arrays/visual.ts +140 -0
- package/src/engine/index.ts +25 -0
- package/src/engine/inheritance.ts +138 -0
- package/src/engine/registry.ts +180 -0
- package/src/pipeline/frameBuffer.ts +473 -0
- package/src/pipeline/layout/index.ts +105 -0
- package/src/pipeline/layout/titan-engine.ts +798 -0
- package/src/pipeline/layout/types.ts +194 -0
- package/src/pipeline/layout/utils/hierarchy.ts +202 -0
- package/src/pipeline/layout/utils/math.ts +134 -0
- package/src/pipeline/layout/utils/text-measure.ts +160 -0
- package/src/pipeline/layout.ts +30 -0
- package/src/primitives/box.ts +312 -0
- package/src/primitives/index.ts +12 -0
- package/src/primitives/text.ts +199 -0
- package/src/primitives/types.ts +222 -0
- package/src/primitives/utils.ts +37 -0
- package/src/renderer/ansi.ts +625 -0
- package/src/renderer/buffer.ts +667 -0
- package/src/renderer/index.ts +40 -0
- package/src/renderer/input.ts +518 -0
- package/src/renderer/output.ts +451 -0
- package/src/state/cursor.ts +176 -0
- package/src/state/focus.ts +241 -0
- package/src/state/index.ts +43 -0
- package/src/state/keyboard.ts +771 -0
- package/src/state/mouse.ts +524 -0
- package/src/state/scroll.ts +341 -0
- package/src/state/theme.ts +687 -0
- package/src/types/color.ts +401 -0
- package/src/types/index.ts +316 -0
- package/src/utils/text.ts +471 -0
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TUI Framework - Core Type Definitions
|
|
3
|
+
*
|
|
4
|
+
* The foundation everything builds on. These types define
|
|
5
|
+
* what the blind renderer understands and what flows through
|
|
6
|
+
* the reactive pipeline.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
// =============================================================================
|
|
10
|
+
// Color
|
|
11
|
+
// =============================================================================
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* RGBA color with 8-bit channels (0-255)
|
|
15
|
+
*
|
|
16
|
+
* Using integers for exact comparison - no floating point epsilon needed.
|
|
17
|
+
* Alpha 255 = fully opaque, 0 = fully transparent.
|
|
18
|
+
*/
|
|
19
|
+
export interface RGBA {
|
|
20
|
+
r: number
|
|
21
|
+
g: number
|
|
22
|
+
b: number
|
|
23
|
+
a: number
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// =============================================================================
|
|
27
|
+
// Dimension - Supports absolute and percentage values
|
|
28
|
+
// =============================================================================
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* A dimension value that can be absolute (number) or percentage (string).
|
|
32
|
+
*
|
|
33
|
+
* - number: Absolute value in terminal cells (e.g., 50 = 50 chars)
|
|
34
|
+
* - string: Percentage of parent (e.g., '50%' = half of parent)
|
|
35
|
+
* - 0 or '0': Auto-size based on content
|
|
36
|
+
*
|
|
37
|
+
* Examples:
|
|
38
|
+
* width: 50 // 50 characters
|
|
39
|
+
* width: '100%' // Full parent width
|
|
40
|
+
* width: '50%' // Half of parent width
|
|
41
|
+
* height: 0 // Auto-height based on content
|
|
42
|
+
*/
|
|
43
|
+
export type Dimension = number | `${number}%`
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Parsed dimension for internal use.
|
|
47
|
+
* TITAN resolves these against parent computed sizes.
|
|
48
|
+
*/
|
|
49
|
+
export interface ParsedDimension {
|
|
50
|
+
value: number
|
|
51
|
+
isPercent: boolean
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Parse a Dimension into value and percent flag.
|
|
56
|
+
* Used by primitives when binding dimensions.
|
|
57
|
+
*/
|
|
58
|
+
export function parseDimension(dim: Dimension | undefined | null): ParsedDimension {
|
|
59
|
+
if (dim === undefined || dim === null) {
|
|
60
|
+
return { value: 0, isPercent: false }
|
|
61
|
+
}
|
|
62
|
+
if (typeof dim === 'number') {
|
|
63
|
+
return { value: dim, isPercent: false }
|
|
64
|
+
}
|
|
65
|
+
// String like '50%'
|
|
66
|
+
const num = parseFloat(dim)
|
|
67
|
+
return { value: isNaN(num) ? 0 : num, isPercent: true }
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// =============================================================================
|
|
71
|
+
// Cell Attributes (bitfield)
|
|
72
|
+
// =============================================================================
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Text attributes as a bitfield for efficient storage and comparison.
|
|
76
|
+
* Combine with bitwise OR: Attr.BOLD | Attr.ITALIC
|
|
77
|
+
*/
|
|
78
|
+
export const Attr = {
|
|
79
|
+
NONE: 0,
|
|
80
|
+
BOLD: 1 << 0,
|
|
81
|
+
DIM: 1 << 1,
|
|
82
|
+
ITALIC: 1 << 2,
|
|
83
|
+
UNDERLINE: 1 << 3,
|
|
84
|
+
BLINK: 1 << 4,
|
|
85
|
+
INVERSE: 1 << 5,
|
|
86
|
+
HIDDEN: 1 << 6,
|
|
87
|
+
STRIKETHROUGH: 1 << 7,
|
|
88
|
+
} as const
|
|
89
|
+
|
|
90
|
+
export type CellAttrs = number
|
|
91
|
+
|
|
92
|
+
// =============================================================================
|
|
93
|
+
// Cell - The atomic unit of terminal rendering
|
|
94
|
+
// =============================================================================
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* A single terminal cell.
|
|
98
|
+
*
|
|
99
|
+
* This is what the renderer deals with. Nothing more complex.
|
|
100
|
+
* The entire pipeline computes these, the renderer outputs them.
|
|
101
|
+
*/
|
|
102
|
+
export interface Cell {
|
|
103
|
+
/** Unicode codepoint (32 for space) */
|
|
104
|
+
char: number
|
|
105
|
+
/** Foreground color */
|
|
106
|
+
fg: RGBA
|
|
107
|
+
/** Background color */
|
|
108
|
+
bg: RGBA
|
|
109
|
+
/** Attribute flags (bold, italic, etc.) */
|
|
110
|
+
attrs: CellAttrs
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// =============================================================================
|
|
114
|
+
// Cursor
|
|
115
|
+
// =============================================================================
|
|
116
|
+
|
|
117
|
+
export type CursorShape = 'block' | 'underline' | 'bar'
|
|
118
|
+
|
|
119
|
+
export interface Cursor {
|
|
120
|
+
x: number
|
|
121
|
+
y: number
|
|
122
|
+
shape: CursorShape
|
|
123
|
+
visible: boolean
|
|
124
|
+
blinking: boolean
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// =============================================================================
|
|
128
|
+
// FrameBuffer - What the renderer receives
|
|
129
|
+
// =============================================================================
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* A 2D buffer of cells representing one frame.
|
|
133
|
+
*
|
|
134
|
+
* Stored as [y][x] for cache-friendly row access during rendering.
|
|
135
|
+
* This is what layoutDerived and frameBufferDerived ultimately produce.
|
|
136
|
+
*/
|
|
137
|
+
export interface FrameBuffer {
|
|
138
|
+
readonly width: number
|
|
139
|
+
readonly height: number
|
|
140
|
+
readonly cells: Cell[][]
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// =============================================================================
|
|
144
|
+
// Input Events - What the terminal reports
|
|
145
|
+
// =============================================================================
|
|
146
|
+
|
|
147
|
+
export interface Modifiers {
|
|
148
|
+
ctrl: boolean
|
|
149
|
+
alt: boolean
|
|
150
|
+
shift: boolean
|
|
151
|
+
meta: boolean
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export type KeyState = 'press' | 'release' | 'repeat'
|
|
155
|
+
|
|
156
|
+
export interface KeyEvent {
|
|
157
|
+
/** The key character or name (e.g., 'a', 'Enter', 'ArrowUp') */
|
|
158
|
+
key: string
|
|
159
|
+
/** Modifier keys held during the event */
|
|
160
|
+
modifiers: Modifiers
|
|
161
|
+
/** Key state (press/release/repeat) */
|
|
162
|
+
state: KeyState
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export type MouseButton = 'left' | 'middle' | 'right' | 'none'
|
|
166
|
+
export type MouseAction = 'down' | 'up' | 'move' | 'scroll'
|
|
167
|
+
|
|
168
|
+
export interface MouseEvent {
|
|
169
|
+
/** Cell X position (0-indexed) */
|
|
170
|
+
x: number
|
|
171
|
+
/** Cell Y position (0-indexed) */
|
|
172
|
+
y: number
|
|
173
|
+
/** Which button */
|
|
174
|
+
button: MouseButton
|
|
175
|
+
/** Event type */
|
|
176
|
+
action: MouseAction
|
|
177
|
+
/** Scroll direction (-1 up, 1 down) when action is 'scroll' */
|
|
178
|
+
scrollDelta?: number
|
|
179
|
+
/** Modifier keys */
|
|
180
|
+
modifiers: Modifiers
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export interface ResizeEvent {
|
|
184
|
+
width: number
|
|
185
|
+
height: number
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export interface FocusEvent {
|
|
189
|
+
focused: boolean
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// =============================================================================
|
|
193
|
+
// Component Types - For parallel arrays
|
|
194
|
+
// =============================================================================
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Component types for the parallel arrays pattern.
|
|
198
|
+
* Each component at index i has componentType[i] set to one of these.
|
|
199
|
+
*/
|
|
200
|
+
export const ComponentType = {
|
|
201
|
+
NONE: 0,
|
|
202
|
+
BOX: 1,
|
|
203
|
+
TEXT: 2,
|
|
204
|
+
INPUT: 3,
|
|
205
|
+
SELECT: 4,
|
|
206
|
+
PROGRESS: 5,
|
|
207
|
+
CANVAS: 6,
|
|
208
|
+
} as const
|
|
209
|
+
|
|
210
|
+
export type ComponentTypeValue = (typeof ComponentType)[keyof typeof ComponentType]
|
|
211
|
+
|
|
212
|
+
// =============================================================================
|
|
213
|
+
// Border Styles
|
|
214
|
+
// =============================================================================
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Border style constants - use numbers in arrays, consistent with ComponentType.
|
|
218
|
+
* All 10 standard terminal border styles.
|
|
219
|
+
*/
|
|
220
|
+
export const BorderStyle = {
|
|
221
|
+
NONE: 0,
|
|
222
|
+
SINGLE: 1, // ─ │ ┌ ┐ └ ┘
|
|
223
|
+
DOUBLE: 2, // ═ ║ ╔ ╗ ╚ ╝
|
|
224
|
+
ROUNDED: 3, // ─ │ ╭ ╮ ╰ ╯
|
|
225
|
+
BOLD: 4, // ━ ┃ ┏ ┓ ┗ ┛
|
|
226
|
+
DASHED: 5, // ┄ ┆ ┌ ┐ └ ┘
|
|
227
|
+
DOTTED: 6, // · · · · · ·
|
|
228
|
+
ASCII: 7, // - | + + + +
|
|
229
|
+
BLOCK: 8, // █ █ █ █ █ █
|
|
230
|
+
DOUBLE_HORZ: 9, // ═ │ ╒ ╕ ╘ ╛ (double horizontal, single vertical)
|
|
231
|
+
DOUBLE_VERT: 10, // ─ ║ ╓ ╖ ╙ ╜ (single horizontal, double vertical)
|
|
232
|
+
} as const
|
|
233
|
+
|
|
234
|
+
export type BorderStyleValue = (typeof BorderStyle)[keyof typeof BorderStyle]
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Border characters for each style.
|
|
238
|
+
* Order: [0]=horizontal, [1]=vertical, [2]=topLeft, [3]=topRight, [4]=bottomRight, [5]=bottomLeft
|
|
239
|
+
* Access as BorderChars[BorderStyle.SINGLE][0] for horizontal char
|
|
240
|
+
*/
|
|
241
|
+
export const BorderChars: Record<number, readonly [string, string, string, string, string, string]> = {
|
|
242
|
+
[BorderStyle.SINGLE]: ['─', '│', '┌', '┐', '┘', '└'],
|
|
243
|
+
[BorderStyle.DOUBLE]: ['═', '║', '╔', '╗', '╝', '╚'],
|
|
244
|
+
[BorderStyle.ROUNDED]: ['─', '│', '╭', '╮', '╯', '╰'],
|
|
245
|
+
[BorderStyle.BOLD]: ['━', '┃', '┏', '┓', '┛', '┗'],
|
|
246
|
+
[BorderStyle.DASHED]: ['┄', '┆', '┌', '┐', '┘', '└'],
|
|
247
|
+
[BorderStyle.DOTTED]: ['·', '·', '·', '·', '·', '·'],
|
|
248
|
+
[BorderStyle.ASCII]: ['-', '|', '+', '+', '+', '+'],
|
|
249
|
+
[BorderStyle.BLOCK]: ['█', '█', '█', '█', '█', '█'],
|
|
250
|
+
[BorderStyle.DOUBLE_HORZ]: ['═', '│', '╒', '╕', '╛', '╘'],
|
|
251
|
+
[BorderStyle.DOUBLE_VERT]: ['─', '║', '╓', '╖', '╜', '╙'],
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// =============================================================================
|
|
255
|
+
// Scroll State
|
|
256
|
+
// =============================================================================
|
|
257
|
+
|
|
258
|
+
export interface ScrollState {
|
|
259
|
+
x: number
|
|
260
|
+
y: number
|
|
261
|
+
maxX: number
|
|
262
|
+
maxY: number
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// =============================================================================
|
|
266
|
+
// Mount Options
|
|
267
|
+
// =============================================================================
|
|
268
|
+
|
|
269
|
+
export type RenderMode = 'fullscreen' | 'inline' | 'append'
|
|
270
|
+
|
|
271
|
+
export interface MountOptions {
|
|
272
|
+
/**
|
|
273
|
+
* Render mode:
|
|
274
|
+
* - 'fullscreen': Alternate screen buffer, full terminal control
|
|
275
|
+
* - 'inline': Renders inline, save/restore cursor, updates in place
|
|
276
|
+
* - 'append': Content flows down, still reactive (can update previous content)
|
|
277
|
+
*/
|
|
278
|
+
mode?: RenderMode
|
|
279
|
+
/** Enable mouse tracking (default: true) */
|
|
280
|
+
mouse?: boolean
|
|
281
|
+
/** Enable Kitty keyboard protocol if available (default: true) */
|
|
282
|
+
kittyKeyboard?: boolean
|
|
283
|
+
/** Initial cursor configuration */
|
|
284
|
+
cursor?: Partial<Cursor>
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// =============================================================================
|
|
288
|
+
// Renderer Interface
|
|
289
|
+
// =============================================================================
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* The blind renderer interface.
|
|
293
|
+
* Knows only about cells, not components.
|
|
294
|
+
*/
|
|
295
|
+
export interface Renderer {
|
|
296
|
+
readonly width: number
|
|
297
|
+
readonly height: number
|
|
298
|
+
|
|
299
|
+
/** Render a frame buffer to the terminal */
|
|
300
|
+
render(buffer: FrameBuffer): void
|
|
301
|
+
|
|
302
|
+
/** Cursor control */
|
|
303
|
+
setCursor(x: number, y: number): void
|
|
304
|
+
setCursorVisible(visible: boolean): void
|
|
305
|
+
setCursorShape(shape: CursorShape): void
|
|
306
|
+
|
|
307
|
+
/** Event handlers - return unsubscribe function */
|
|
308
|
+
onKey(handler: (event: KeyEvent) => void): () => void
|
|
309
|
+
onMouse(handler: (event: MouseEvent) => void): () => void
|
|
310
|
+
onResize(handler: (event: ResizeEvent) => void): () => void
|
|
311
|
+
onFocus(handler: (event: FocusEvent) => void): () => void
|
|
312
|
+
|
|
313
|
+
/** Lifecycle */
|
|
314
|
+
start(options?: MountOptions): Promise<void>
|
|
315
|
+
stop(): Promise<void>
|
|
316
|
+
}
|