@flyingrobots/bijou-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/LICENSE +21 -0
- package/README.md +78 -0
- package/dist/commands.d.ts +8 -0
- package/dist/commands.d.ts.map +1 -0
- package/dist/commands.js +16 -0
- package/dist/commands.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/keys.d.ts +7 -0
- package/dist/keys.d.ts.map +1 -0
- package/dist/keys.js +62 -0
- package/dist/keys.js.map +1 -0
- package/dist/layout.d.ts +11 -0
- package/dist/layout.d.ts.map +1 -0
- package/dist/layout.js +44 -0
- package/dist/layout.js.map +1 -0
- package/dist/runtime.d.ts +10 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +110 -0
- package/dist/runtime.js.map +1 -0
- package/dist/screen.d.ts +21 -0
- package/dist/screen.d.ts.map +1 -0
- package/dist/screen.js +34 -0
- package/dist/screen.js.map +1 -0
- package/dist/types.d.ts +22 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/package.json +59 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Flying Robots
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# @flyingrobots/bijou-tui
|
|
2
|
+
|
|
3
|
+
TEA runtime for terminal UIs — model/update/view with keyboard input, alt screen, and layout helpers.
|
|
4
|
+
|
|
5
|
+
Inspired by [Bubble Tea](https://github.com/charmbracelet/bubbletea) (Go), bijou-tui brings The Elm Architecture to TypeScript terminals.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @flyingrobots/bijou @flyingrobots/bijou-tui
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Quick Start
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
import { run, quit, tick, type App, type KeyMsg } from '@flyingrobots/bijou-tui';
|
|
17
|
+
|
|
18
|
+
type Model = { count: number };
|
|
19
|
+
|
|
20
|
+
const app: App<Model> = {
|
|
21
|
+
init: () => [{ count: 0 }, tick(1000)],
|
|
22
|
+
|
|
23
|
+
update: (msg, model) => {
|
|
24
|
+
if (msg.type === 'key') {
|
|
25
|
+
if (msg.key === 'q') return [model, quit()];
|
|
26
|
+
if (msg.key === '+') return [{ count: model.count + 1 }, null];
|
|
27
|
+
if (msg.key === '-') return [{ count: model.count - 1 }, null];
|
|
28
|
+
}
|
|
29
|
+
if (msg.type === 'tick') {
|
|
30
|
+
return [model, tick(1000)];
|
|
31
|
+
}
|
|
32
|
+
return [model, null];
|
|
33
|
+
},
|
|
34
|
+
|
|
35
|
+
view: (model) => `Count: ${model.count}\n\nPress +/- to change, q to quit`,
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
run(app);
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## API
|
|
42
|
+
|
|
43
|
+
### Core Types
|
|
44
|
+
|
|
45
|
+
- **`App<M>`** — defines `init`, `update(msg, model)`, and `view(model)` functions
|
|
46
|
+
- **`KeyMsg`** — keyboard input message with `key`, `ctrl`, `shift`, `alt` fields
|
|
47
|
+
- **`Cmd`** — side-effect commands returned from `update`
|
|
48
|
+
- **`QUIT`** — sentinel value to signal app termination
|
|
49
|
+
|
|
50
|
+
### Commands
|
|
51
|
+
|
|
52
|
+
- **`quit()`** — exit the app
|
|
53
|
+
- **`tick(ms)`** — schedule a tick after `ms` milliseconds
|
|
54
|
+
- **`batch(...cmds)`** — combine multiple commands
|
|
55
|
+
|
|
56
|
+
### Screen Control
|
|
57
|
+
|
|
58
|
+
- **`enterScreen()` / `exitScreen()`** — alt screen buffer management
|
|
59
|
+
- **`clearAndHome()`** — clear screen and move cursor to top-left
|
|
60
|
+
- **`renderFrame(content)`** — efficient frame rendering
|
|
61
|
+
|
|
62
|
+
### Layout
|
|
63
|
+
|
|
64
|
+
- **`vstack(...lines)`** — vertical stack (join with newlines)
|
|
65
|
+
- **`hstack(...cols)`** — horizontal stack (side-by-side columns)
|
|
66
|
+
|
|
67
|
+
### Key Parsing
|
|
68
|
+
|
|
69
|
+
- **`parseKey(data)`** — parse raw stdin bytes into a `KeyMsg`
|
|
70
|
+
|
|
71
|
+
## Related Packages
|
|
72
|
+
|
|
73
|
+
- [`@flyingrobots/bijou`](https://www.npmjs.com/package/@flyingrobots/bijou) — Zero-dependency core with all components and theme engine
|
|
74
|
+
- [`@flyingrobots/bijou-node`](https://www.npmjs.com/package/@flyingrobots/bijou-node) — Node.js runtime adapter (chalk, readline, process)
|
|
75
|
+
|
|
76
|
+
## License
|
|
77
|
+
|
|
78
|
+
MIT
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type Cmd } from './types.js';
|
|
2
|
+
/** Command that signals the runtime to quit. */
|
|
3
|
+
export declare function quit<M>(): Cmd<M>;
|
|
4
|
+
/** Command that delivers a message after a delay (one-shot). */
|
|
5
|
+
export declare function tick<M>(ms: number, msg: M): Cmd<M>;
|
|
6
|
+
/** Convenience: group multiple commands into an array. */
|
|
7
|
+
export declare function batch<M>(...cmds: Cmd<M>[]): Cmd<M>[];
|
|
8
|
+
//# sourceMappingURL=commands.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../src/commands.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,KAAK,GAAG,EAAmB,MAAM,YAAY,CAAC;AAE7D,gDAAgD;AAChD,wBAAgB,IAAI,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAEhC;AAED,gEAAgE;AAChE,wBAAgB,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAIlD;AAED,0DAA0D;AAC1D,wBAAgB,KAAK,CAAC,CAAC,EAAE,GAAG,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,GAAG,CAAC,CAAC,CAAC,EAAE,CAEpD"}
|
package/dist/commands.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { QUIT } from './types.js';
|
|
2
|
+
/** Command that signals the runtime to quit. */
|
|
3
|
+
export function quit() {
|
|
4
|
+
return () => Promise.resolve(QUIT);
|
|
5
|
+
}
|
|
6
|
+
/** Command that delivers a message after a delay (one-shot). */
|
|
7
|
+
export function tick(ms, msg) {
|
|
8
|
+
return () => new Promise((resolve) => {
|
|
9
|
+
setTimeout(() => resolve(msg), ms);
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
/** Convenience: group multiple commands into an array. */
|
|
13
|
+
export function batch(...cmds) {
|
|
14
|
+
return cmds;
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=commands.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"commands.js","sourceRoot":"","sources":["../src/commands.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAA6B,MAAM,YAAY,CAAC;AAE7D,gDAAgD;AAChD,MAAM,UAAU,IAAI;IAClB,OAAO,GAAG,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,IAAkB,CAAC,CAAC;AACnD,CAAC;AAED,gEAAgE;AAChE,MAAM,UAAU,IAAI,CAAI,EAAU,EAAE,GAAM;IACxC,OAAO,GAAG,EAAE,CAAC,IAAI,OAAO,CAAI,CAAC,OAAO,EAAE,EAAE;QACtC,UAAU,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC;IACrC,CAAC,CAAC,CAAC;AACL,CAAC;AAED,0DAA0D;AAC1D,MAAM,UAAU,KAAK,CAAI,GAAG,IAAc;IACxC,OAAO,IAAI,CAAC;AACd,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export type { App, Cmd, KeyMsg, QuitSignal, RunOptions } from './types.js';
|
|
2
|
+
export { QUIT } from './types.js';
|
|
3
|
+
export { parseKey } from './keys.js';
|
|
4
|
+
export { enterScreen, exitScreen, clearAndHome, renderFrame, ENTER_ALT_SCREEN, EXIT_ALT_SCREEN, HIDE_CURSOR, SHOW_CURSOR, CLEAR_SCREEN, CLEAR_TO_END, CLEAR_LINE, HOME, } from './screen.js';
|
|
5
|
+
export { quit, tick, batch } from './commands.js';
|
|
6
|
+
export { run } from './runtime.js';
|
|
7
|
+
export { vstack, hstack } from './layout.js';
|
|
8
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,YAAY,EAAE,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAC3E,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAGlC,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAGrC,OAAO,EACL,WAAW,EACX,UAAU,EACV,YAAY,EACZ,WAAW,EACX,gBAAgB,EAChB,eAAe,EACf,WAAW,EACX,WAAW,EACX,YAAY,EACZ,YAAY,EACZ,UAAU,EACV,IAAI,GACL,MAAM,aAAa,CAAC;AAGrB,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAGlD,OAAO,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;AAGnC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export { QUIT } from './types.js';
|
|
2
|
+
// Key parsing
|
|
3
|
+
export { parseKey } from './keys.js';
|
|
4
|
+
// Screen control
|
|
5
|
+
export { enterScreen, exitScreen, clearAndHome, renderFrame, ENTER_ALT_SCREEN, EXIT_ALT_SCREEN, HIDE_CURSOR, SHOW_CURSOR, CLEAR_SCREEN, CLEAR_TO_END, CLEAR_LINE, HOME, } from './screen.js';
|
|
6
|
+
// Commands
|
|
7
|
+
export { quit, tick, batch } from './commands.js';
|
|
8
|
+
// Runtime
|
|
9
|
+
export { run } from './runtime.js';
|
|
10
|
+
// Layout
|
|
11
|
+
export { vstack, hstack } from './layout.js';
|
|
12
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAElC,cAAc;AACd,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAErC,iBAAiB;AACjB,OAAO,EACL,WAAW,EACX,UAAU,EACV,YAAY,EACZ,WAAW,EACX,gBAAgB,EAChB,eAAe,EACf,WAAW,EACX,WAAW,EACX,YAAY,EACZ,YAAY,EACZ,UAAU,EACV,IAAI,GACL,MAAM,aAAa,CAAC;AAErB,WAAW;AACX,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAElD,UAAU;AACV,OAAO,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;AAEnC,SAAS;AACT,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC"}
|
package/dist/keys.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { KeyMsg } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Parse a raw ANSI byte string from rawInput() into a structured KeyMsg.
|
|
4
|
+
* Unrecognized sequences produce { key: 'unknown' }.
|
|
5
|
+
*/
|
|
6
|
+
export declare function parseKey(raw: string): KeyMsg;
|
|
7
|
+
//# sourceMappingURL=keys.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keys.d.ts","sourceRoot":"","sources":["../src/keys.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAMzC;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CA2C5C"}
|
package/dist/keys.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
function keyMsg(key, ctrl = false, alt = false, shift = false) {
|
|
2
|
+
return { type: 'key', key, ctrl, alt, shift };
|
|
3
|
+
}
|
|
4
|
+
/**
|
|
5
|
+
* Parse a raw ANSI byte string from rawInput() into a structured KeyMsg.
|
|
6
|
+
* Unrecognized sequences produce { key: 'unknown' }.
|
|
7
|
+
*/
|
|
8
|
+
export function parseKey(raw) {
|
|
9
|
+
// Arrow keys and other CSI sequences
|
|
10
|
+
if (raw === '\x1b[A')
|
|
11
|
+
return keyMsg('up');
|
|
12
|
+
if (raw === '\x1b[B')
|
|
13
|
+
return keyMsg('down');
|
|
14
|
+
if (raw === '\x1b[C')
|
|
15
|
+
return keyMsg('right');
|
|
16
|
+
if (raw === '\x1b[D')
|
|
17
|
+
return keyMsg('left');
|
|
18
|
+
if (raw === '\x1b[H')
|
|
19
|
+
return keyMsg('home');
|
|
20
|
+
if (raw === '\x1b[F')
|
|
21
|
+
return keyMsg('end');
|
|
22
|
+
if (raw === '\x1b[3~')
|
|
23
|
+
return keyMsg('delete');
|
|
24
|
+
if (raw === '\x1b[5~')
|
|
25
|
+
return keyMsg('pageup');
|
|
26
|
+
if (raw === '\x1b[6~')
|
|
27
|
+
return keyMsg('pagedown');
|
|
28
|
+
// Enter, tab, backspace, space, escape
|
|
29
|
+
if (raw === '\r' || raw === '\n')
|
|
30
|
+
return keyMsg('enter');
|
|
31
|
+
if (raw === '\t')
|
|
32
|
+
return keyMsg('tab');
|
|
33
|
+
if (raw === '\x1b[Z')
|
|
34
|
+
return keyMsg('tab', false, false, true); // shift-tab
|
|
35
|
+
if (raw === '\x7f')
|
|
36
|
+
return keyMsg('backspace');
|
|
37
|
+
if (raw === ' ')
|
|
38
|
+
return keyMsg('space');
|
|
39
|
+
// Escape (alone)
|
|
40
|
+
if (raw === '\x1b')
|
|
41
|
+
return keyMsg('escape');
|
|
42
|
+
// Ctrl+C
|
|
43
|
+
if (raw === '\x03')
|
|
44
|
+
return keyMsg('c', true);
|
|
45
|
+
// Ctrl+A through Ctrl+Z (0x01-0x1a), excluding already-handled cases
|
|
46
|
+
if (raw.length === 1) {
|
|
47
|
+
const code = raw.charCodeAt(0);
|
|
48
|
+
if (code >= 0x01 && code <= 0x1a) {
|
|
49
|
+
const letter = String.fromCharCode(code + 0x60); // 0x01 → 'a', 0x02 → 'b', etc.
|
|
50
|
+
return keyMsg(letter, true);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
// Printable single characters
|
|
54
|
+
if (raw.length === 1) {
|
|
55
|
+
const code = raw.charCodeAt(0);
|
|
56
|
+
if (code >= 0x20 && code <= 0x7e) {
|
|
57
|
+
return keyMsg(raw);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return keyMsg('unknown');
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=keys.js.map
|
package/dist/keys.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keys.js","sourceRoot":"","sources":["../src/keys.ts"],"names":[],"mappings":"AAEA,SAAS,MAAM,CAAC,GAAW,EAAE,IAAI,GAAG,KAAK,EAAE,GAAG,GAAG,KAAK,EAAE,KAAK,GAAG,KAAK;IACnE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC;AAChD,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,QAAQ,CAAC,GAAW;IAClC,qCAAqC;IACrC,IAAI,GAAG,KAAK,QAAQ;QAAE,OAAO,MAAM,CAAC,IAAI,CAAC,CAAC;IAC1C,IAAI,GAAG,KAAK,QAAQ;QAAE,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC;IAC5C,IAAI,GAAG,KAAK,QAAQ;QAAE,OAAO,MAAM,CAAC,OAAO,CAAC,CAAC;IAC7C,IAAI,GAAG,KAAK,QAAQ;QAAE,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC;IAC5C,IAAI,GAAG,KAAK,QAAQ;QAAE,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC;IAC5C,IAAI,GAAG,KAAK,QAAQ;QAAE,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;IAC3C,IAAI,GAAG,KAAK,SAAS;QAAE,OAAO,MAAM,CAAC,QAAQ,CAAC,CAAC;IAC/C,IAAI,GAAG,KAAK,SAAS;QAAE,OAAO,MAAM,CAAC,QAAQ,CAAC,CAAC;IAC/C,IAAI,GAAG,KAAK,SAAS;QAAE,OAAO,MAAM,CAAC,UAAU,CAAC,CAAC;IAEjD,uCAAuC;IACvC,IAAI,GAAG,KAAK,IAAI,IAAI,GAAG,KAAK,IAAI;QAAE,OAAO,MAAM,CAAC,OAAO,CAAC,CAAC;IACzD,IAAI,GAAG,KAAK,IAAI;QAAE,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;IACvC,IAAI,GAAG,KAAK,QAAQ;QAAE,OAAO,MAAM,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC,CAAC,YAAY;IAC5E,IAAI,GAAG,KAAK,MAAM;QAAE,OAAO,MAAM,CAAC,WAAW,CAAC,CAAC;IAC/C,IAAI,GAAG,KAAK,GAAG;QAAE,OAAO,MAAM,CAAC,OAAO,CAAC,CAAC;IAExC,iBAAiB;IACjB,IAAI,GAAG,KAAK,MAAM;QAAE,OAAO,MAAM,CAAC,QAAQ,CAAC,CAAC;IAE5C,SAAS;IACT,IAAI,GAAG,KAAK,MAAM;QAAE,OAAO,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;IAE7C,qEAAqE;IACrE,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACrB,MAAM,IAAI,GAAG,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;QAC/B,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,EAAE,CAAC;YACjC,MAAM,MAAM,GAAG,MAAM,CAAC,YAAY,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,+BAA+B;YAChF,OAAO,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;QAC9B,CAAC;IACH,CAAC;IAED,8BAA8B;IAC9B,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACrB,MAAM,IAAI,GAAG,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;QAC/B,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,EAAE,CAAC;YACjC,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC;QACrB,CAAC;IACH,CAAC;IAED,OAAO,MAAM,CAAC,SAAS,CAAC,CAAC;AAC3B,CAAC"}
|
package/dist/layout.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vertical stack — join blocks with newlines.
|
|
3
|
+
* Filters out empty strings.
|
|
4
|
+
*/
|
|
5
|
+
export declare function vstack(...blocks: string[]): string;
|
|
6
|
+
/**
|
|
7
|
+
* Horizontal stack — place blocks side by side with a gap between columns.
|
|
8
|
+
* Pads shorter blocks with empty lines to align rows.
|
|
9
|
+
*/
|
|
10
|
+
export declare function hstack(gap: number, ...blocks: string[]): string;
|
|
11
|
+
//# sourceMappingURL=layout.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"layout.d.ts","sourceRoot":"","sources":["../src/layout.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,wBAAgB,MAAM,CAAC,GAAG,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,CAElD;AAED;;;GAGG;AACH,wBAAgB,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,CAwB/D"}
|
package/dist/layout.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vertical stack — join blocks with newlines.
|
|
3
|
+
* Filters out empty strings.
|
|
4
|
+
*/
|
|
5
|
+
export function vstack(...blocks) {
|
|
6
|
+
return blocks.filter((b) => b !== '').join('\n');
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Horizontal stack — place blocks side by side with a gap between columns.
|
|
10
|
+
* Pads shorter blocks with empty lines to align rows.
|
|
11
|
+
*/
|
|
12
|
+
export function hstack(gap, ...blocks) {
|
|
13
|
+
if (blocks.length === 0)
|
|
14
|
+
return '';
|
|
15
|
+
if (blocks.length === 1)
|
|
16
|
+
return blocks[0];
|
|
17
|
+
const split = blocks.map((b) => b.split('\n'));
|
|
18
|
+
const maxRows = Math.max(...split.map((lines) => lines.length));
|
|
19
|
+
const widths = split.map((lines) => Math.max(...lines.map(visualWidth)));
|
|
20
|
+
const spacer = ' '.repeat(gap);
|
|
21
|
+
const rows = [];
|
|
22
|
+
for (let r = 0; r < maxRows; r++) {
|
|
23
|
+
const parts = [];
|
|
24
|
+
for (let c = 0; c < split.length; c++) {
|
|
25
|
+
const line = split[c][r] ?? '';
|
|
26
|
+
// Last column doesn't need right-padding
|
|
27
|
+
if (c < split.length - 1) {
|
|
28
|
+
parts.push(line + ' '.repeat(widths[c] - visualWidth(line)));
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
parts.push(line);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
rows.push(parts.join(spacer).trimEnd());
|
|
35
|
+
}
|
|
36
|
+
return rows.join('\n');
|
|
37
|
+
}
|
|
38
|
+
/** Width of a string with ANSI escapes stripped. */
|
|
39
|
+
function visualWidth(s) {
|
|
40
|
+
// Strip ANSI escape sequences
|
|
41
|
+
// eslint-disable-next-line no-control-regex
|
|
42
|
+
return s.replace(/\x1b\[[0-9;]*m/g, '').length;
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=layout.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"layout.js","sourceRoot":"","sources":["../src/layout.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,UAAU,MAAM,CAAC,GAAG,MAAgB;IACxC,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACnD,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,MAAM,CAAC,GAAW,EAAE,GAAG,MAAgB;IACrD,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IACnC,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,MAAM,CAAC,CAAC,CAAE,CAAC;IAE3C,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;IAC/C,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC;IAChE,MAAM,MAAM,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;IACzE,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IAE/B,MAAM,IAAI,GAAa,EAAE,CAAC;IAC1B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,OAAO,EAAE,CAAC,EAAE,EAAE,CAAC;QACjC,MAAM,KAAK,GAAa,EAAE,CAAC;QAC3B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YACtC,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;YAChC,yCAAyC;YACzC,IAAI,CAAC,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACzB,KAAK,CAAC,IAAI,CAAC,IAAI,GAAG,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAE,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAChE,CAAC;iBAAM,CAAC;gBACN,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACnB,CAAC;QACH,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;IAC1C,CAAC;IACD,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACzB,CAAC;AAED,oDAAoD;AACpD,SAAS,WAAW,CAAC,CAAS;IAC5B,8BAA8B;IAC9B,4CAA4C;IAC5C,OAAO,CAAC,CAAC,OAAO,CAAC,iBAAiB,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC;AACjD,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { App, RunOptions } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Run a TEA application.
|
|
4
|
+
*
|
|
5
|
+
* In non-interactive modes (pipe/static/accessible), renders the initial view
|
|
6
|
+
* once and exits. In interactive mode, enters the alt screen, starts the
|
|
7
|
+
* keyboard event loop, and drives the model/update/view cycle.
|
|
8
|
+
*/
|
|
9
|
+
export declare function run<Model, M>(app: App<Model, M>, options?: RunOptions): Promise<void>;
|
|
10
|
+
//# sourceMappingURL=runtime.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,GAAG,EAAe,UAAU,EAAE,MAAM,YAAY,CAAC;AAW/D;;;;;;GAMG;AACH,wBAAsB,GAAG,CAAC,KAAK,EAAE,CAAC,EAChC,GAAG,EAAE,GAAG,CAAC,KAAK,EAAE,CAAC,CAAC,EAClB,OAAO,CAAC,EAAE,UAAU,GACnB,OAAO,CAAC,IAAI,CAAC,CAsGf"}
|
package/dist/runtime.js
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { getDefaultContext } from '@flyingrobots/bijou';
|
|
2
|
+
import { QUIT } from './types.js';
|
|
3
|
+
import { parseKey } from './keys.js';
|
|
4
|
+
import { enterScreen, exitScreen, renderFrame } from './screen.js';
|
|
5
|
+
/**
|
|
6
|
+
* Disable mouse reporting sequences that terminals may send.
|
|
7
|
+
* Some terminals auto-enable mouse tracking in alt screen mode.
|
|
8
|
+
*/
|
|
9
|
+
const DISABLE_MOUSE = '\x1b[?1000l\x1b[?1002l\x1b[?1003l\x1b[?1006l';
|
|
10
|
+
/**
|
|
11
|
+
* Run a TEA application.
|
|
12
|
+
*
|
|
13
|
+
* In non-interactive modes (pipe/static/accessible), renders the initial view
|
|
14
|
+
* once and exits. In interactive mode, enters the alt screen, starts the
|
|
15
|
+
* keyboard event loop, and drives the model/update/view cycle.
|
|
16
|
+
*/
|
|
17
|
+
export async function run(app, options) {
|
|
18
|
+
const ctx = options?.ctx ?? getDefaultContext();
|
|
19
|
+
const useAltScreen = options?.altScreen ?? true;
|
|
20
|
+
const useHideCursor = options?.hideCursor ?? true;
|
|
21
|
+
const [initModel, initCmds] = app.init();
|
|
22
|
+
// Non-interactive: render once and return
|
|
23
|
+
if (ctx.mode !== 'interactive') {
|
|
24
|
+
ctx.io.write(app.view(initModel));
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
// Interactive mode
|
|
28
|
+
let model = initModel;
|
|
29
|
+
let running = true;
|
|
30
|
+
let lastCtrlC = 0;
|
|
31
|
+
let resolveQuit = null;
|
|
32
|
+
function shutdown() {
|
|
33
|
+
if (!running)
|
|
34
|
+
return;
|
|
35
|
+
running = false;
|
|
36
|
+
if (resolveQuit)
|
|
37
|
+
resolveQuit();
|
|
38
|
+
}
|
|
39
|
+
// Setup screen
|
|
40
|
+
if (useAltScreen || useHideCursor) {
|
|
41
|
+
enterScreen(ctx.io);
|
|
42
|
+
}
|
|
43
|
+
// Disable mouse reporting to avoid garbage from mouse events
|
|
44
|
+
ctx.io.write(DISABLE_MOUSE);
|
|
45
|
+
// Render helper
|
|
46
|
+
function render() {
|
|
47
|
+
if (!running)
|
|
48
|
+
return;
|
|
49
|
+
renderFrame(ctx.io, app.view(model));
|
|
50
|
+
}
|
|
51
|
+
// Message handler
|
|
52
|
+
function handleMsg(msg) {
|
|
53
|
+
if (!running)
|
|
54
|
+
return;
|
|
55
|
+
const [newModel, cmds] = app.update(msg, model);
|
|
56
|
+
model = newModel;
|
|
57
|
+
render();
|
|
58
|
+
executeCommands(cmds);
|
|
59
|
+
}
|
|
60
|
+
// Execute commands, feeding results back as messages
|
|
61
|
+
function executeCommands(cmds) {
|
|
62
|
+
for (const cmd of cmds) {
|
|
63
|
+
void cmd().then((result) => {
|
|
64
|
+
if (result === QUIT) {
|
|
65
|
+
shutdown();
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
if (result !== undefined) {
|
|
69
|
+
handleMsg(result);
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
// Initial render
|
|
75
|
+
render();
|
|
76
|
+
// Execute startup commands
|
|
77
|
+
executeCommands(initCmds);
|
|
78
|
+
// Start keyboard listener
|
|
79
|
+
const inputHandle = ctx.io.rawInput((raw) => {
|
|
80
|
+
if (!running)
|
|
81
|
+
return;
|
|
82
|
+
const keyMsg = parseKey(raw);
|
|
83
|
+
// Skip unknown sequences (mouse events, etc.)
|
|
84
|
+
if (keyMsg.key === 'unknown')
|
|
85
|
+
return;
|
|
86
|
+
// Double Ctrl+C force-quit
|
|
87
|
+
if (keyMsg.key === 'c' && keyMsg.ctrl) {
|
|
88
|
+
const now = Date.now();
|
|
89
|
+
if (now - lastCtrlC < 1000) {
|
|
90
|
+
shutdown();
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
lastCtrlC = now;
|
|
94
|
+
}
|
|
95
|
+
handleMsg(keyMsg);
|
|
96
|
+
});
|
|
97
|
+
// Wait for quit signal
|
|
98
|
+
await new Promise((resolve) => {
|
|
99
|
+
resolveQuit = resolve;
|
|
100
|
+
// In case shutdown was already called before we got here
|
|
101
|
+
if (!running)
|
|
102
|
+
resolve();
|
|
103
|
+
});
|
|
104
|
+
// Cleanup
|
|
105
|
+
inputHandle.dispose();
|
|
106
|
+
if (useAltScreen || useHideCursor) {
|
|
107
|
+
exitScreen(ctx.io);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=runtime.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtime.js","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"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"}
|
package/dist/screen.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { IOPort } from '@flyingrobots/bijou';
|
|
2
|
+
export declare const ENTER_ALT_SCREEN = "\u001B[?1049h";
|
|
3
|
+
export declare const EXIT_ALT_SCREEN = "\u001B[?1049l";
|
|
4
|
+
export declare const HIDE_CURSOR = "\u001B[?25l";
|
|
5
|
+
export declare const SHOW_CURSOR = "\u001B[?25h";
|
|
6
|
+
export declare const CLEAR_SCREEN = "\u001B[2J";
|
|
7
|
+
export declare const CLEAR_TO_END = "\u001B[J";
|
|
8
|
+
export declare const HOME = "\u001B[H";
|
|
9
|
+
export declare const CLEAR_LINE = "\u001B[2K";
|
|
10
|
+
/** Enter alt screen buffer, hide cursor, and clear screen. */
|
|
11
|
+
export declare function enterScreen(io: IOPort): void;
|
|
12
|
+
/** Show cursor and exit alt screen buffer. */
|
|
13
|
+
export declare function exitScreen(io: IOPort): void;
|
|
14
|
+
/** Clear screen and move cursor to home position. */
|
|
15
|
+
export declare function clearAndHome(io: IOPort): void;
|
|
16
|
+
/**
|
|
17
|
+
* Flicker-free render: move cursor home, write content line-by-line
|
|
18
|
+
* (clearing each line first), then erase everything below.
|
|
19
|
+
*/
|
|
20
|
+
export declare function renderFrame(io: IOPort, content: string): void;
|
|
21
|
+
//# sourceMappingURL=screen.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"screen.d.ts","sourceRoot":"","sources":["../src/screen.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAElD,eAAO,MAAM,gBAAgB,kBAAgB,CAAC;AAC9C,eAAO,MAAM,eAAe,kBAAgB,CAAC;AAC7C,eAAO,MAAM,WAAW,gBAAc,CAAC;AACvC,eAAO,MAAM,WAAW,gBAAc,CAAC;AACvC,eAAO,MAAM,YAAY,cAAY,CAAC;AACtC,eAAO,MAAM,YAAY,aAAW,CAAC;AACrC,eAAO,MAAM,IAAI,aAAW,CAAC;AAC7B,eAAO,MAAM,UAAU,cAAY,CAAC;AAEpC,8DAA8D;AAC9D,wBAAgB,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAE5C;AAED,8CAA8C;AAC9C,wBAAgB,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAE3C;AAED,qDAAqD;AACrD,wBAAgB,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAE7C;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAQ7D"}
|
package/dist/screen.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export const ENTER_ALT_SCREEN = '\x1b[?1049h';
|
|
2
|
+
export const EXIT_ALT_SCREEN = '\x1b[?1049l';
|
|
3
|
+
export const HIDE_CURSOR = '\x1b[?25l';
|
|
4
|
+
export const SHOW_CURSOR = '\x1b[?25h';
|
|
5
|
+
export const CLEAR_SCREEN = '\x1b[2J';
|
|
6
|
+
export const CLEAR_TO_END = '\x1b[J';
|
|
7
|
+
export const HOME = '\x1b[H';
|
|
8
|
+
export const CLEAR_LINE = '\x1b[2K';
|
|
9
|
+
/** Enter alt screen buffer, hide cursor, and clear screen. */
|
|
10
|
+
export function enterScreen(io) {
|
|
11
|
+
io.write(ENTER_ALT_SCREEN + HIDE_CURSOR + CLEAR_SCREEN + HOME);
|
|
12
|
+
}
|
|
13
|
+
/** Show cursor and exit alt screen buffer. */
|
|
14
|
+
export function exitScreen(io) {
|
|
15
|
+
io.write(SHOW_CURSOR + EXIT_ALT_SCREEN);
|
|
16
|
+
}
|
|
17
|
+
/** Clear screen and move cursor to home position. */
|
|
18
|
+
export function clearAndHome(io) {
|
|
19
|
+
io.write(CLEAR_SCREEN + HOME);
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Flicker-free render: move cursor home, write content line-by-line
|
|
23
|
+
* (clearing each line first), then erase everything below.
|
|
24
|
+
*/
|
|
25
|
+
export function renderFrame(io, content) {
|
|
26
|
+
const lines = content.split('\n');
|
|
27
|
+
let frame = HOME;
|
|
28
|
+
for (const line of lines) {
|
|
29
|
+
frame += CLEAR_LINE + line + '\n';
|
|
30
|
+
}
|
|
31
|
+
frame += CLEAR_TO_END;
|
|
32
|
+
io.write(frame);
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=screen.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"screen.js","sourceRoot":"","sources":["../src/screen.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,gBAAgB,GAAG,aAAa,CAAC;AAC9C,MAAM,CAAC,MAAM,eAAe,GAAG,aAAa,CAAC;AAC7C,MAAM,CAAC,MAAM,WAAW,GAAG,WAAW,CAAC;AACvC,MAAM,CAAC,MAAM,WAAW,GAAG,WAAW,CAAC;AACvC,MAAM,CAAC,MAAM,YAAY,GAAG,SAAS,CAAC;AACtC,MAAM,CAAC,MAAM,YAAY,GAAG,QAAQ,CAAC;AACrC,MAAM,CAAC,MAAM,IAAI,GAAG,QAAQ,CAAC;AAC7B,MAAM,CAAC,MAAM,UAAU,GAAG,SAAS,CAAC;AAEpC,8DAA8D;AAC9D,MAAM,UAAU,WAAW,CAAC,EAAU;IACpC,EAAE,CAAC,KAAK,CAAC,gBAAgB,GAAG,WAAW,GAAG,YAAY,GAAG,IAAI,CAAC,CAAC;AACjE,CAAC;AAED,8CAA8C;AAC9C,MAAM,UAAU,UAAU,CAAC,EAAU;IACnC,EAAE,CAAC,KAAK,CAAC,WAAW,GAAG,eAAe,CAAC,CAAC;AAC1C,CAAC;AAED,qDAAqD;AACrD,MAAM,UAAU,YAAY,CAAC,EAAU;IACrC,EAAE,CAAC,KAAK,CAAC,YAAY,GAAG,IAAI,CAAC,CAAC;AAChC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,WAAW,CAAC,EAAU,EAAE,OAAe;IACrD,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAClC,IAAI,KAAK,GAAG,IAAI,CAAC;IACjB,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,KAAK,IAAI,UAAU,GAAG,IAAI,GAAG,IAAI,CAAC;IACpC,CAAC;IACD,KAAK,IAAI,YAAY,CAAC;IACtB,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;AAClB,CAAC"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { BijouContext } from '@flyingrobots/bijou';
|
|
2
|
+
export interface KeyMsg {
|
|
3
|
+
readonly type: 'key';
|
|
4
|
+
readonly key: string;
|
|
5
|
+
readonly ctrl: boolean;
|
|
6
|
+
readonly alt: boolean;
|
|
7
|
+
readonly shift: boolean;
|
|
8
|
+
}
|
|
9
|
+
export declare const QUIT: unique symbol;
|
|
10
|
+
export type QuitSignal = typeof QUIT;
|
|
11
|
+
export type Cmd<M> = () => Promise<M | QuitSignal | void>;
|
|
12
|
+
export interface App<Model, M = never> {
|
|
13
|
+
init(): [Model, Cmd<M>[]];
|
|
14
|
+
update(msg: KeyMsg | M, model: Model): [Model, Cmd<M>[]];
|
|
15
|
+
view(model: Model): string;
|
|
16
|
+
}
|
|
17
|
+
export interface RunOptions {
|
|
18
|
+
altScreen?: boolean;
|
|
19
|
+
hideCursor?: boolean;
|
|
20
|
+
ctx?: BijouContext;
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAIxD,MAAM,WAAW,MAAM;IACrB,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC;IACrB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;CACzB;AAID,eAAO,MAAM,IAAI,EAAE,OAAO,MAAuB,CAAC;AAClD,MAAM,MAAM,UAAU,GAAG,OAAO,IAAI,CAAC;AAErC,MAAM,MAAM,GAAG,CAAC,CAAC,IAAI,MAAM,OAAO,CAAC,CAAC,GAAG,UAAU,GAAG,IAAI,CAAC,CAAC;AAI1D,MAAM,WAAW,GAAG,CAAC,KAAK,EAAE,CAAC,GAAG,KAAK;IACnC,IAAI,IAAI,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IAC1B,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,CAAC,EAAE,KAAK,EAAE,KAAK,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IACzD,IAAI,CAAC,KAAK,EAAE,KAAK,GAAG,MAAM,CAAC;CAC5B;AAID,MAAM,WAAW,UAAU;IACzB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,GAAG,CAAC,EAAE,YAAY,CAAC;CACpB"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAYA,mBAAmB;AAEnB,MAAM,CAAC,MAAM,IAAI,GAAkB,MAAM,CAAC,MAAM,CAAC,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@flyingrobots/bijou-tui",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "TEA runtime for terminal UIs — model/update/view with keyboard input, alt screen, and layout helpers.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"sideEffects": false,
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"default": "./dist/index.js"
|
|
13
|
+
},
|
|
14
|
+
"./package.json": "./package.json"
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist",
|
|
18
|
+
"README.md",
|
|
19
|
+
"LICENSE"
|
|
20
|
+
],
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "tsc -b",
|
|
23
|
+
"prepack": "tsc -b",
|
|
24
|
+
"lint": "tsc --noEmit"
|
|
25
|
+
},
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"@flyingrobots/bijou": "0.1.0"
|
|
28
|
+
},
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"@types/node": "^22.0.0",
|
|
31
|
+
"typescript": "^5.9.3",
|
|
32
|
+
"vitest": "^4.0.18"
|
|
33
|
+
},
|
|
34
|
+
"keywords": [
|
|
35
|
+
"cli",
|
|
36
|
+
"terminal",
|
|
37
|
+
"tui",
|
|
38
|
+
"bijou",
|
|
39
|
+
"tea",
|
|
40
|
+
"elm-architecture",
|
|
41
|
+
"interactive"
|
|
42
|
+
],
|
|
43
|
+
"author": "James Ross <james@flyingrobots.dev>",
|
|
44
|
+
"license": "MIT",
|
|
45
|
+
"repository": {
|
|
46
|
+
"type": "git",
|
|
47
|
+
"url": "git+https://github.com/flyingrobots/bijou.git",
|
|
48
|
+
"directory": "packages/bijou-tui"
|
|
49
|
+
},
|
|
50
|
+
"homepage": "https://github.com/flyingrobots/bijou",
|
|
51
|
+
"bugs": "https://github.com/flyingrobots/bijou/issues",
|
|
52
|
+
"engines": {
|
|
53
|
+
"node": ">=18"
|
|
54
|
+
},
|
|
55
|
+
"publishConfig": {
|
|
56
|
+
"access": "public",
|
|
57
|
+
"provenance": true
|
|
58
|
+
}
|
|
59
|
+
}
|