@flyingrobots/bijou-tui 4.4.0 → 5.0.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 +102 -600
- package/dist/app-frame-actions.d.ts.map +1 -1
- package/dist/app-frame-actions.js +6 -1
- package/dist/app-frame-actions.js.map +1 -1
- package/dist/app-frame-i18n.d.ts.map +1 -1
- package/dist/app-frame-i18n.js +53 -0
- package/dist/app-frame-i18n.js.map +1 -1
- package/dist/app-frame-layers.d.ts +11 -1
- package/dist/app-frame-layers.d.ts.map +1 -1
- package/dist/app-frame-layers.js +19 -0
- package/dist/app-frame-layers.js.map +1 -1
- package/dist/app-frame-overlays.d.ts +82 -0
- package/dist/app-frame-overlays.d.ts.map +1 -0
- package/dist/app-frame-overlays.js +480 -0
- package/dist/app-frame-overlays.js.map +1 -0
- package/dist/app-frame-render.d.ts +8 -8
- package/dist/app-frame-render.d.ts.map +1 -1
- package/dist/app-frame-render.js +84 -25
- package/dist/app-frame-render.js.map +1 -1
- package/dist/app-frame-types.d.ts +122 -5
- package/dist/app-frame-types.d.ts.map +1 -1
- package/dist/app-frame-types.js +17 -1
- package/dist/app-frame-types.js.map +1 -1
- package/dist/app-frame-utils.d.ts.map +1 -1
- package/dist/app-frame-utils.js +6 -5
- package/dist/app-frame-utils.js.map +1 -1
- package/dist/app-frame.d.ts +43 -83
- package/dist/app-frame.d.ts.map +1 -1
- package/dist/app-frame.js +499 -575
- package/dist/app-frame.js.map +1 -1
- package/dist/browsable-list.d.ts +12 -0
- package/dist/browsable-list.d.ts.map +1 -1
- package/dist/browsable-list.js +17 -6
- package/dist/browsable-list.js.map +1 -1
- package/dist/canvas.d.ts.map +1 -1
- package/dist/canvas.js +27 -7
- package/dist/canvas.js.map +1 -1
- package/dist/collection-surface.d.ts +8 -0
- package/dist/collection-surface.d.ts.map +1 -1
- package/dist/collection-surface.js +72 -8
- package/dist/collection-surface.js.map +1 -1
- package/dist/command-palette.d.ts +5 -3
- package/dist/command-palette.d.ts.map +1 -1
- package/dist/command-palette.js +5 -3
- package/dist/command-palette.js.map +1 -1
- package/dist/css/text-style.d.ts +2 -0
- package/dist/css/text-style.d.ts.map +1 -1
- package/dist/css/text-style.js +18 -8
- package/dist/css/text-style.js.map +1 -1
- package/dist/debug-overlay.d.ts +19 -0
- package/dist/debug-overlay.d.ts.map +1 -0
- package/dist/debug-overlay.js +25 -0
- package/dist/debug-overlay.js.map +1 -0
- package/dist/design-language.d.ts.map +1 -1
- package/dist/design-language.js +4 -5
- package/dist/design-language.js.map +1 -1
- package/dist/driver.d.ts +102 -0
- package/dist/driver.d.ts.map +1 -1
- package/dist/driver.js +259 -19
- package/dist/driver.js.map +1 -1
- package/dist/file-picker.d.ts.map +1 -1
- package/dist/file-picker.js +2 -2
- package/dist/file-picker.js.map +1 -1
- package/dist/flex.d.ts.map +1 -1
- package/dist/flex.js +6 -6
- package/dist/flex.js.map +1 -1
- package/dist/focus-area.d.ts +9 -1
- package/dist/focus-area.d.ts.map +1 -1
- package/dist/focus-area.js +69 -40
- package/dist/focus-area.js.map +1 -1
- package/dist/grid.d.ts +2 -2
- package/dist/grid.d.ts.map +1 -1
- package/dist/grid.js +11 -148
- package/dist/grid.js.map +1 -1
- package/dist/help.d.ts +2 -0
- package/dist/help.d.ts.map +1 -1
- package/dist/help.js +6 -4
- package/dist/help.js.map +1 -1
- package/dist/icon-presentation.d.ts +10 -0
- package/dist/icon-presentation.d.ts.map +1 -0
- package/dist/icon-presentation.js +12 -0
- package/dist/icon-presentation.js.map +1 -0
- package/dist/index.d.ts +14 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -5
- package/dist/index.js.map +1 -1
- package/dist/layout-preset.d.ts +1 -1
- package/dist/layout-preset.d.ts.map +1 -1
- package/dist/motion/reconciler.d.ts +6 -2
- package/dist/motion/reconciler.d.ts.map +1 -1
- package/dist/motion/reconciler.js +40 -10
- package/dist/motion/reconciler.js.map +1 -1
- package/dist/motion/types.d.ts +3 -1
- package/dist/motion/types.d.ts.map +1 -1
- package/dist/navigable-table.d.ts +9 -10
- package/dist/navigable-table.d.ts.map +1 -1
- package/dist/navigable-table.js +33 -14
- package/dist/navigable-table.js.map +1 -1
- package/dist/notification.d.ts +15 -0
- package/dist/notification.d.ts.map +1 -1
- package/dist/notification.js +98 -29
- package/dist/notification.js.map +1 -1
- package/dist/overlay.d.ts.map +1 -1
- package/dist/overlay.js +50 -19
- package/dist/overlay.js.map +1 -1
- package/dist/pager.d.ts +3 -1
- package/dist/pager.d.ts.map +1 -1
- package/dist/pager.js +4 -0
- package/dist/pager.js.map +1 -1
- package/dist/pipeline/middleware/grayscale.d.ts.map +1 -1
- package/dist/pipeline/middleware/grayscale.js +5 -5
- package/dist/pipeline/middleware/grayscale.js.map +1 -1
- package/dist/pipeline/middleware/motion.js +2 -2
- package/dist/pipeline/middleware/motion.js.map +1 -1
- package/dist/pipeline/middleware/surface-shaders.d.ts +37 -0
- package/dist/pipeline/middleware/surface-shaders.d.ts.map +1 -0
- package/dist/pipeline/middleware/surface-shaders.js +164 -0
- package/dist/pipeline/middleware/surface-shaders.js.map +1 -0
- package/dist/pipeline/pipeline.d.ts +29 -1
- package/dist/pipeline/pipeline.d.ts.map +1 -1
- package/dist/pipeline/pipeline.js +86 -23
- package/dist/pipeline/pipeline.js.map +1 -1
- package/dist/runtime.d.ts +19 -0
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +183 -30
- package/dist/runtime.js.map +1 -1
- package/dist/shell-quit.d.ts +1 -1
- package/dist/shell-quit.d.ts.map +1 -1
- package/dist/shell-quit.js +14 -3
- package/dist/shell-quit.js.map +1 -1
- package/dist/split-pane.d.ts +2 -2
- package/dist/split-pane.d.ts.map +1 -1
- package/dist/split-pane.js +28 -49
- package/dist/split-pane.js.map +1 -1
- package/dist/subapp/mount.d.ts +17 -0
- package/dist/subapp/mount.d.ts.map +1 -1
- package/dist/subapp/mount.js +13 -0
- package/dist/subapp/mount.js.map +1 -1
- package/dist/surface-layout.d.ts +8 -3
- package/dist/surface-layout.d.ts.map +1 -1
- package/dist/surface-layout.js +27 -12
- package/dist/surface-layout.js.map +1 -1
- package/dist/view-output.js +2 -2
- package/dist/view-output.js.map +1 -1
- package/dist/viewport.d.ts +13 -1
- package/dist/viewport.d.ts.map +1 -1
- package/dist/viewport.js +33 -11
- package/dist/viewport.js.map +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,45 +1,37 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @flyingrobots/bijou-tui
|
|
2
2
|
|
|
3
3
|
The high-fidelity TEA runtime for Bijou.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
`@flyingrobots/bijou-tui` provides the application loop, layout primitives, and physics-powered orchestration needed to build complex interactive terminal apps.
|
|
6
6
|
|
|
7
|
-
##
|
|
7
|
+
## Role
|
|
8
8
|
|
|
9
|
-
The
|
|
9
|
+
- **The Elm Architecture (TEA)**: A deterministic state-update-view loop for industrial-strength terminal software.
|
|
10
|
+
- **Fractal TEA**: Compose nested sub-apps with `createSubAppAdapter()`, `initSubApp()`, `updateSubApp()`, and `mount()`.
|
|
11
|
+
- **Declarative Motion**: Interpolate layout changes smoothly with physics-based springs and tween animations.
|
|
12
|
+
- **Surface-First Pipeline**: Programmable rendering middleware for fragments, diffing, and shader-based transitions.
|
|
10
13
|
|
|
11
|
-
|
|
12
|
-
- **Pure view contract:** `App.view` and framed pane renderers now speak `ViewOutput` (`Surface | LayoutNode`).
|
|
13
|
-
- **Programmable Rendering Pipeline:** The TEA `view` output is now processed through a 5-stage middleware pipeline (`Layout -> Paint -> PostProcess -> Diff -> Output`). Add custom fragment shaders or logging middleware effortlessly.
|
|
14
|
-
- **Fractal TEA (Sub-Apps):** Compose nested apps with `initSubApp()`, `updateSubApp()`, `mount()`, and `mapCmds()` instead of flattening everything into one update loop.
|
|
15
|
-
- **Bijou CSS (BCSS):** Style supported runtime surface components and frame shell regions with type/class/id selectors, `var()` token lookups, and terminal-aware media queries (`@media (width < 80)`). This is not yet a global cascade across arbitrary layout nodes.
|
|
16
|
-
- **Declarative Motion:** Wrap any component in `motion({ key: 'id' }, ...)` and watch it smoothly interpolate layout changes (move, resize) using physics-based springs.
|
|
17
|
-
- **Unified Heartbeat:** All animations and physics calculations are now synchronized to a single `PulseMsg`, eliminating timer jitter and saving CPU.
|
|
18
|
-
- **Byte-Packed Rendering:** The surface layer is backed by a `Uint8Array` with a zero-alloc `setRGB()` path. The differ compares cells as 10-byte sequences and emits ANSI directly from buffer bytes. All built-in components use the fast path automatically.
|
|
19
|
-
|
|
20
|
-
## Documentation
|
|
21
|
-
|
|
22
|
-
See the [Bijou repo](https://github.com/flyingrobots/bijou) for the full documentation map, architecture guide, design system, and DOGFOOD proving surface.
|
|
23
|
-
|
|
24
|
-
## Installation
|
|
14
|
+
## Install
|
|
25
15
|
|
|
26
16
|
```bash
|
|
27
17
|
npm install @flyingrobots/bijou @flyingrobots/bijou-node @flyingrobots/bijou-tui
|
|
28
18
|
```
|
|
29
19
|
|
|
30
|
-
If you are upgrading an existing app, see [`../../docs/MIGRATING_TO_V4.md`](../../docs/MIGRATING_TO_V4.md).
|
|
31
|
-
|
|
32
20
|
## Quick Start (Sub-App Composition)
|
|
33
21
|
|
|
34
22
|
```typescript
|
|
35
|
-
import {
|
|
36
|
-
import {
|
|
37
|
-
import { createSurface
|
|
23
|
+
import { startApp } from '@flyingrobots/bijou-node';
|
|
24
|
+
import { createSubAppAdapter, mount, type App } from '@flyingrobots/bijou-tui';
|
|
25
|
+
import { createSurface } from '@flyingrobots/bijou';
|
|
38
26
|
|
|
39
|
-
|
|
27
|
+
type ChildMsg = { type: 'tick' };
|
|
28
|
+
type ParentModel = {
|
|
29
|
+
left: { count: number };
|
|
30
|
+
right: { count: number };
|
|
31
|
+
};
|
|
32
|
+
type ParentMsg = { type: 'left'; msg: ChildMsg } | { type: 'right'; msg: ChildMsg };
|
|
40
33
|
|
|
41
|
-
|
|
42
|
-
const childApp: App<{ count: number }, any> = {
|
|
34
|
+
const childApp: App<{ count: number }, ChildMsg> = {
|
|
43
35
|
init: () => [{ count: 0 }, []],
|
|
44
36
|
update: (msg, model) => [model, []],
|
|
45
37
|
view: (model) => {
|
|
@@ -49,642 +41,152 @@ const childApp: App<{ count: number }, any> = {
|
|
|
49
41
|
}
|
|
50
42
|
};
|
|
51
43
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
44
|
+
const mapLeft = createSubAppAdapter<ParentMsg, ChildMsg>({
|
|
45
|
+
tick: (msg) => ({ type: 'left', msg }),
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const mapRight = createSubAppAdapter<ParentMsg, ChildMsg>({
|
|
49
|
+
tick: (msg) => ({ type: 'right', msg }),
|
|
50
|
+
});
|
|
56
51
|
|
|
57
|
-
|
|
58
|
-
const app: App<Model, any> = {
|
|
52
|
+
const app: App<ParentModel, ParentMsg> = {
|
|
59
53
|
init: () => [{ left: { count: 0 }, right: { count: 0 } }, []],
|
|
60
54
|
update: (msg, model) => [model, []],
|
|
61
55
|
view: (model) => {
|
|
62
|
-
|
|
63
|
-
const [
|
|
64
|
-
const [rightSurface] = mount(childApp, { model: model.right, onMsg: m => m });
|
|
56
|
+
const [left] = mount(childApp, { model: model.left, onMsg: mapLeft });
|
|
57
|
+
const [right] = mount(childApp, { model: model.right, onMsg: mapRight });
|
|
65
58
|
|
|
66
|
-
// Composite them onto the main screen
|
|
67
59
|
const screen = createSurface(80, 24);
|
|
68
|
-
screen.blit(
|
|
69
|
-
screen.blit(
|
|
60
|
+
screen.blit(left, 0, 0);
|
|
61
|
+
screen.blit(right, 40, 0);
|
|
70
62
|
return screen;
|
|
71
63
|
}
|
|
72
64
|
};
|
|
73
65
|
|
|
74
|
-
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
## Quick Start (Basic)
|
|
78
|
-
|
|
79
|
-
```typescript
|
|
80
|
-
import { initDefaultContext } from '@flyingrobots/bijou-node';
|
|
81
|
-
import { stringToSurface } from '@flyingrobots/bijou';
|
|
82
|
-
import { run, quit, type App, isKeyMsg } from '@flyingrobots/bijou-tui';
|
|
83
|
-
|
|
84
|
-
initDefaultContext();
|
|
85
|
-
|
|
86
|
-
type Model = { count: number };
|
|
87
|
-
|
|
88
|
-
const app: App<Model> = {
|
|
89
|
-
init: () => [{ count: 0 }, []],
|
|
90
|
-
|
|
91
|
-
update: (msg, model) => {
|
|
92
|
-
if (isKeyMsg(msg)) {
|
|
93
|
-
if (msg.key === 'q') return [model, [quit()]];
|
|
94
|
-
if (msg.key === '+') return [{ count: model.count + 1 }, []];
|
|
95
|
-
if (msg.key === '-') return [{ count: model.count - 1 }, []];
|
|
96
|
-
}
|
|
97
|
-
return [model, []];
|
|
98
|
-
},
|
|
99
|
-
|
|
100
|
-
view: (model) => {
|
|
101
|
-
const text = `Count: ${model.count}\n\nPress +/- to change, q to quit`;
|
|
102
|
-
const lines = text.split('\n');
|
|
103
|
-
return stringToSurface(text, Math.max(1, ...lines.map((line) => line.length)), lines.length);
|
|
104
|
-
},
|
|
105
|
-
};
|
|
106
|
-
|
|
107
|
-
run(app);
|
|
66
|
+
await startApp(app);
|
|
108
67
|
```
|
|
109
68
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
`run()` behaves differently by output mode:
|
|
113
|
-
|
|
114
|
-
- `interactive`: full TEA loop (event bus, key/resize/mouse handling, command-driven updates).
|
|
115
|
-
- `pipe` / `static` / `accessible`: render `view(initModel)` once and return immediately.
|
|
116
|
-
|
|
117
|
-
In non-interactive modes, there is no normal interactive event loop.
|
|
69
|
+
For Node hosts, prefer `startApp()` for the first-app path. Reach for
|
|
70
|
+
`run(app, { ctx })` when the host owns context creation explicitly.
|
|
118
71
|
|
|
119
|
-
|
|
72
|
+
When you need to mix small string fragments with surface-returning primitives,
|
|
73
|
+
keep composition on the surface side: use `contentSurface()` directly or pass
|
|
74
|
+
strings into `vstackSurface()` / `hstackSurface()`. Raw strings are still not a
|
|
75
|
+
valid `view()` return type.
|
|
120
76
|
|
|
121
|
-
|
|
77
|
+
## Strategy: Choosing Component Families
|
|
122
78
|
|
|
123
|
-
|
|
124
|
-
- `QUIT`
|
|
125
|
-
- a cleanup handle or cleanup function for a long-lived effect
|
|
126
|
-
- `void`
|
|
79
|
+
Select the family based on the interaction semantic.
|
|
127
80
|
|
|
128
|
-
|
|
81
|
+
### Overlays and Interruption
|
|
82
|
+
- **`drawer()`**: Supplemental detail while maintaining main context.
|
|
83
|
+
- **`modal()`**: Required decision that blocks background activity.
|
|
84
|
+
- **`toast()`**: Transient notification for a single event.
|
|
85
|
+
- **`tooltip()`**: Micro-explanation for a local target.
|
|
86
|
+
- **`debugOverlay()`**: Development-only perf HUD composited onto any app surface.
|
|
129
87
|
|
|
130
|
-
|
|
88
|
+
### Collection Interaction
|
|
89
|
+
- **`navigableTable()`**: Keyboard-driven traversal and cell inspection.
|
|
90
|
+
- **`browsableList()`**: Description-led traversal in one dimension.
|
|
91
|
+
- **`commandPalette()`**: Action discovery and navigation.
|
|
131
92
|
|
|
132
|
-
|
|
133
|
-
-
|
|
134
|
-
-
|
|
135
|
-
-
|
|
136
|
-
-
|
|
137
|
-
- **App shell**: `createFramedApp()` for tabs/help/chrome/pane-focus boilerplate with optional command palette.
|
|
138
|
-
- **Stateful building blocks**: navigable table, browsable list, file picker, focus area, and DAG pane with vim-friendly keymaps.
|
|
93
|
+
### Shell and Workspace Layout
|
|
94
|
+
- **`createFramedApp()`**: Batteries-included workspace with tabs, panes, and help.
|
|
95
|
+
- **`splitPane()`**: Dynamic primary/secondary context comparison.
|
|
96
|
+
- **`grid()`**: Stable regions with simultaneous visibility.
|
|
97
|
+
- **`viewport()`**: The canonical scroll mask for rich composition.
|
|
139
98
|
|
|
140
|
-
|
|
99
|
+
For framed shells, Node hosts can still prefer `startApp(app)`: the hosted
|
|
100
|
+
Node bootstrap delegates to self-running framed apps automatically. Use the
|
|
101
|
+
explicit runner path when you want to stay inside `@flyingrobots/bijou-tui`
|
|
102
|
+
or when the host owns `ctx` directly:
|
|
141
103
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
- Use `toast()` when you are composing a single transient overlay directly.
|
|
145
|
-
- Use the notification system when the app needs stacking, placement, actions, routing, or history.
|
|
146
|
-
- Use `drawer()` when the user should keep the main surface visible while working in supplemental detail.
|
|
147
|
-
- Use `modal()` when background shortcuts and pointer actions should be blocked.
|
|
148
|
-
- Use `tooltip()` only for tiny local explanation, not for decisions or scrollable content.
|
|
149
|
-
- If the overlay needs embedded component surfaces or multiple real rows, keep it on the structured `Surface` path with `compositeSurface()`.
|
|
150
|
-
|
|
151
|
-
### Collection interaction
|
|
104
|
+
```typescript
|
|
105
|
+
import { createFramedApp, runFramedApp } from '@flyingrobots/bijou-tui';
|
|
152
106
|
|
|
153
|
-
|
|
154
|
-
- Use `navigableTable()` when row/cell focus and keyboard traversal are the real job.
|
|
155
|
-
- Use `browsableList()` when the content is one-dimensional and description-led rather than grid-oriented.
|
|
156
|
-
- Use `commandPaletteSurface()` when the outcome is an action or navigation target, not a stored form value.
|
|
157
|
-
- If users are really choosing persisted values, keep that work in core `select()` / `filter()` / `multiselect()` instead of turning the palette into a value picker.
|
|
107
|
+
const app = createFramedApp({ pages: [page] });
|
|
158
108
|
|
|
159
|
-
|
|
109
|
+
await app.run({ ctx });
|
|
110
|
+
// or: await runFramedApp({ pages: [page] }, { ctx });
|
|
111
|
+
```
|
|
160
112
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
- Use `helpShortSurface()` or `helpViewSurface()` when shortcut guidance stays inside the rich shell; keep `helpShort()` / `helpView()` for explicit text output.
|
|
166
|
-
- Use `commandPaletteSurface()` for action discovery and navigation inside the shell, not as a substitute value picker.
|
|
167
|
-
- Use notifications for events and follow-up, not as a replacement for the status rail.
|
|
168
|
-
- Keep status rails concise and global; explanatory text belongs in the page, not in shell chrome.
|
|
169
|
-
- Mouse is enhancement, not baseline. Overlay layers should consume pointer input before shell chrome or page content, and every click target should mirror an existing keyboard path.
|
|
113
|
+
This path keeps the shell batteries included: mouse input defaults to `true`,
|
|
114
|
+
the shared runtime loop still does the heavy lifting, and frame timing/budget
|
|
115
|
+
telemetry stays attached to the frame model for shell-owned UI. For Node-hosted
|
|
116
|
+
apps, `startApp(app)` remains the default bootstrap.
|
|
170
117
|
|
|
171
118
|
## Animation
|
|
172
119
|
|
|
173
120
|
### Spring Physics
|
|
174
|
-
|
|
175
121
|
```typescript
|
|
176
122
|
import { animate, SPRING_PRESETS } from '@flyingrobots/bijou-tui';
|
|
177
123
|
|
|
178
|
-
// Physics-based (default) — runs until the spring settles
|
|
179
124
|
const cmd = animate({
|
|
180
125
|
from: 0,
|
|
181
126
|
to: 100,
|
|
182
|
-
spring: 'wobbly',
|
|
183
|
-
onFrame: (v) => ({ type: 'scroll', y: v }),
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
// Duration-based with easing
|
|
187
|
-
const fade = animate({
|
|
188
|
-
type: 'tween',
|
|
189
|
-
from: 0,
|
|
190
|
-
to: 1,
|
|
191
|
-
duration: 300,
|
|
192
|
-
ease: EASINGS.easeOutCubic,
|
|
193
|
-
onFrame: (v) => ({ type: 'fade', opacity: v }),
|
|
194
|
-
});
|
|
195
|
-
|
|
196
|
-
// Skip animation (reduced motion)
|
|
197
|
-
const jump = animate({
|
|
198
|
-
from: 0, to: 100,
|
|
199
|
-
immediate: true,
|
|
127
|
+
spring: 'wobbly',
|
|
200
128
|
onFrame: (v) => ({ type: 'scroll', y: v }),
|
|
201
129
|
});
|
|
202
130
|
```
|
|
203
131
|
|
|
204
|
-
### Timeline
|
|
205
|
-
|
|
206
|
-
GSAP-style orchestration — pure state machine, no timers:
|
|
207
|
-
|
|
132
|
+
### Timeline Orchestration
|
|
208
133
|
```typescript
|
|
209
134
|
import { timeline } from '@flyingrobots/bijou-tui';
|
|
210
135
|
|
|
211
136
|
const tl = timeline()
|
|
212
|
-
.add('slideIn',
|
|
213
|
-
.add('fadeIn', { type: 'tween', from: 0, to: 1, duration: 200 }, '-=100')
|
|
137
|
+
.add('slideIn', { type: 'tween', from: -100, to: 0, duration: 300 })
|
|
214
138
|
.label('settled')
|
|
215
|
-
.add('bounce',
|
|
216
|
-
.call('onReady', 'settled+=50')
|
|
139
|
+
.add('bounce', { from: 0, to: 10, spring: 'wobbly' }, 'settled')
|
|
217
140
|
.build();
|
|
218
|
-
|
|
219
|
-
// Drive from TEA update:
|
|
220
|
-
let tlState = tl.init();
|
|
221
|
-
// on each frame:
|
|
222
|
-
tlState = tl.step(tlState, 1/60);
|
|
223
|
-
const { slideIn, fadeIn, bounce } = tl.values(tlState);
|
|
224
|
-
const fired = tl.firedCallbacks(prev, tlState); // ['onReady']
|
|
225
141
|
```
|
|
226
142
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
## Transition Shaders
|
|
230
|
-
|
|
231
|
-
Custom page transitions are surface-native in v4. Shader functions decide whether each cell shows the previous page or next page, and may optionally provide override data for that cell.
|
|
143
|
+
## Post-Process Shaders
|
|
232
144
|
|
|
233
145
|
```typescript
|
|
234
|
-
import {
|
|
235
|
-
|
|
236
|
-
const shimmer: TransitionShaderFn = ({ progress, x, width }) => {
|
|
237
|
-
const edge = Math.floor(progress * width);
|
|
238
|
-
if (x < edge) return { showNext: true };
|
|
239
|
-
if (x === edge) return { showNext: false, overrideChar: '░', overrideRole: 'marker' };
|
|
240
|
-
return { showNext: false };
|
|
241
|
-
};
|
|
242
|
-
```
|
|
243
|
-
|
|
244
|
-
Use `overrideChar` when the base cell styling should stay intact, `overrideCell` when the shader needs full fg/bg/modifier control, and `overrideRole` to tell combinators whether an override is ambient (`'decoration'`) or positional (`'marker'`).
|
|
245
|
-
|
|
246
|
-
Use transition shaders to reinforce workspace change, not as default spectacle. If the effect makes the new page harder to read or hides state meaning that should remain explicit in static or accessible modes, it is the wrong transition.
|
|
247
|
-
|
|
248
|
-
## Layout
|
|
249
|
-
|
|
250
|
-
### Flexbox
|
|
251
|
-
|
|
252
|
-
```typescript
|
|
253
|
-
import { flex } from '@flyingrobots/bijou-tui';
|
|
254
|
-
|
|
255
|
-
// Sidebar + main content, responsive to terminal width
|
|
256
|
-
flex({ direction: 'row', width: cols, height: rows, gap: 1 },
|
|
257
|
-
{ basis: 20, content: sidebarText },
|
|
258
|
-
{ flex: 1, content: (w, h) => renderMain(w, h) },
|
|
259
|
-
);
|
|
260
|
-
|
|
261
|
-
// Header + body + footer
|
|
262
|
-
flex({ direction: 'column', width: cols, height: rows },
|
|
263
|
-
{ basis: 1, content: headerLine },
|
|
264
|
-
{ flex: 1, content: (w, h) => renderBody(w, h) },
|
|
265
|
-
{ basis: 1, content: statusLine },
|
|
266
|
-
);
|
|
267
|
-
```
|
|
268
|
-
|
|
269
|
-
Children can be **render functions** `(width, height) => string` — they receive their allocated space and reflow automatically when the terminal resizes.
|
|
270
|
-
|
|
271
|
-
### Viewport
|
|
272
|
-
|
|
273
|
-
```typescript
|
|
274
|
-
import { viewportSurface, createScrollStateForContent, scrollBy, pageDown } from '@flyingrobots/bijou-tui';
|
|
275
|
-
import { boxSurface } from '@flyingrobots/bijou';
|
|
276
|
-
|
|
277
|
-
const content = boxSurface(longText, { width: 72 });
|
|
278
|
-
let scroll = createScrollStateForContent(content, viewportHeight);
|
|
279
|
-
|
|
280
|
-
// Mask the content to a visible window with scrollbar
|
|
281
|
-
const view = viewportSurface({ width: 60, height: 20, content, scrollY: scroll.y });
|
|
282
|
-
|
|
283
|
-
// Handle scroll keys
|
|
284
|
-
scroll = scrollBy(scroll, 1); // down one line
|
|
285
|
-
scroll = pageDown(scroll); // down one page
|
|
286
|
-
```
|
|
287
|
-
|
|
288
|
-
Treat `viewportSurface()` as the canonical scroll mask for rich TUI composition. Keep `viewport()` for explicit text-lowering paths.
|
|
289
|
-
|
|
290
|
-
### Basic Layout
|
|
146
|
+
import { run, surfaceShaderFilter, scanlines, vignette } from '@flyingrobots/bijou-tui';
|
|
291
147
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
vstack,
|
|
299
|
-
vstackSurface,
|
|
300
|
-
} from '@flyingrobots/bijou-tui';
|
|
301
|
-
|
|
302
|
-
vstack(header, content, footer); // explicit text-lowering path
|
|
303
|
-
hstack(2, leftPanel, rightPanel); // explicit text-lowering path
|
|
304
|
-
place('Title', { width: 20, height: 3 }); // text placement
|
|
305
|
-
|
|
306
|
-
vstackSurface(headerSurface, bodySurface); // structured surface stack
|
|
307
|
-
hstackSurface(2, navSurface, mainSurface); // structured horizontal stack
|
|
308
|
-
placeSurface(dialogSurface, { // structured placement/alignment
|
|
309
|
-
width: cols,
|
|
310
|
-
height: rows,
|
|
311
|
-
hAlign: 'center',
|
|
312
|
-
vAlign: 'middle',
|
|
313
|
-
});
|
|
314
|
-
```
|
|
315
|
-
|
|
316
|
-
Prefer `vstackSurface()` / `hstackSurface()` / `placeSurface()` when the view is already composed from `Surface` values. Keep `vstack()` / `hstack()` / `place()` for explicit text composition or lowering paths.
|
|
317
|
-
|
|
318
|
-
### Split Pane
|
|
319
|
-
|
|
320
|
-
```typescript
|
|
321
|
-
import {
|
|
322
|
-
createSplitPaneState, splitPaneSurface, splitPaneResizeBy, splitPaneFocusNext,
|
|
323
|
-
} from '@flyingrobots/bijou-tui';
|
|
324
|
-
|
|
325
|
-
let state = createSplitPaneState({ ratio: 0.35 });
|
|
326
|
-
|
|
327
|
-
// in update:
|
|
328
|
-
state = splitPaneResizeBy(state, 2, { total: cols, minA: 16, minB: 16 });
|
|
329
|
-
state = splitPaneFocusNext(state);
|
|
330
|
-
|
|
331
|
-
// in view:
|
|
332
|
-
const output = splitPaneSurface(state, {
|
|
333
|
-
direction: 'row',
|
|
334
|
-
width: cols,
|
|
335
|
-
height: rows,
|
|
336
|
-
minA: 16,
|
|
337
|
-
minB: 16,
|
|
338
|
-
paneA: (w, h) => renderSidebar(w, h),
|
|
339
|
-
paneB: (w, h) => renderMain(w, h),
|
|
340
|
-
});
|
|
341
|
-
```
|
|
342
|
-
|
|
343
|
-
Prefer `splitPaneSurface()` when the panes are already structured `Surface` views. Keep `splitPane()` for explicit text composition or lowering paths.
|
|
344
|
-
|
|
345
|
-
### Grid
|
|
346
|
-
|
|
347
|
-
```typescript
|
|
348
|
-
import { gridSurface } from '@flyingrobots/bijou-tui';
|
|
349
|
-
|
|
350
|
-
const output = gridSurface({
|
|
351
|
-
width: cols,
|
|
352
|
-
height: rows,
|
|
353
|
-
columns: [24, '1fr'],
|
|
354
|
-
rows: [3, '1fr', 8],
|
|
355
|
-
areas: [
|
|
356
|
-
'header header',
|
|
357
|
-
'nav main',
|
|
358
|
-
'logs main',
|
|
359
|
-
],
|
|
360
|
-
gap: 1,
|
|
361
|
-
cells: {
|
|
362
|
-
header: (w, h) => renderHeader(w, h),
|
|
363
|
-
nav: (w, h) => renderNav(w, h),
|
|
364
|
-
logs: (w, h) => renderLogs(w, h),
|
|
365
|
-
main: (w, h) => renderMain(w, h),
|
|
148
|
+
await run(app, {
|
|
149
|
+
configurePipeline(pipeline) {
|
|
150
|
+
pipeline.use('PostProcess', surfaceShaderFilter(
|
|
151
|
+
scanlines({ dimFactor: 0.82 }),
|
|
152
|
+
vignette({ edgeFactor: 0.78 }),
|
|
153
|
+
));
|
|
366
154
|
},
|
|
367
155
|
});
|
|
368
156
|
```
|
|
369
157
|
|
|
370
|
-
|
|
158
|
+
Use `surfaceShaderFilter(...)` to compose built-in post-process passes like
|
|
159
|
+
`scanlines()`, `flicker()`, `noise()`, and `vignette()` over the packed target
|
|
160
|
+
surface before diff/output.
|
|
371
161
|
|
|
372
|
-
##
|
|
162
|
+
## Testing
|
|
373
163
|
|
|
374
|
-
|
|
164
|
+
Use `testRuntime()` when you want an inspectable harness instead of a
|
|
165
|
+
one-shot script result:
|
|
375
166
|
|
|
376
167
|
```typescript
|
|
377
|
-
|
|
378
|
-
if (msg.type === 'resize') {
|
|
379
|
-
return [{ ...model, cols: msg.columns, rows: msg.rows }, []];
|
|
380
|
-
}
|
|
381
|
-
// ...
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
view(model) {
|
|
385
|
-
return flex(
|
|
386
|
-
{ direction: 'row', width: model.cols, height: model.rows },
|
|
387
|
-
{ basis: 20, content: sidebar },
|
|
388
|
-
{ flex: 1, content: (w, h) => mainContent(w, h) },
|
|
389
|
-
);
|
|
390
|
-
}
|
|
391
|
-
```
|
|
168
|
+
import { testRuntime } from '@flyingrobots/bijou-tui';
|
|
392
169
|
|
|
393
|
-
|
|
170
|
+
const harness = await testRuntime(app, { ctx });
|
|
171
|
+
await harness.press('q');
|
|
394
172
|
|
|
395
|
-
|
|
173
|
+
expect(harness.frame).toBeDefined();
|
|
174
|
+
expect(harness.messages).toHaveLength(1);
|
|
175
|
+
expect(harness.commands.every((record) => record.settled)).toBe(true);
|
|
396
176
|
|
|
397
|
-
|
|
398
|
-
import { createEventBus } from '@flyingrobots/bijou-tui';
|
|
399
|
-
|
|
400
|
-
const bus = createEventBus<MyMsg>();
|
|
401
|
-
bus.connectIO(ctx.io); // keyboard + resize
|
|
402
|
-
bus.on((msg) => { /* ... */ }); // single subscription
|
|
403
|
-
bus.emit(customMsg); // synthetic events
|
|
404
|
-
bus.runCmd(someCommand); // final messages re-emitted, cleanup retained
|
|
405
|
-
bus.dispose(); // clean shutdown
|
|
177
|
+
await harness.teardown();
|
|
406
178
|
```
|
|
407
179
|
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
Declarative key binding with modifier support, named groups, and runtime enable/disable:
|
|
180
|
+
Keep `runScript()` for fixture-style interaction playback and GIF/demo
|
|
181
|
+
capture, and use `testRuntime()` when you need direct assertions on
|
|
182
|
+
snapshots, emitted messages, command outcomes, or cleanup disposal.
|
|
413
183
|
|
|
414
|
-
|
|
415
|
-
import { createKeyMap, type KeyMsg } from '@flyingrobots/bijou-tui';
|
|
416
|
-
|
|
417
|
-
type Msg = { type: 'quit' } | { type: 'help' } | { type: 'move'; dir: string };
|
|
418
|
-
|
|
419
|
-
const kb = createKeyMap<Msg>()
|
|
420
|
-
.bind('q', 'Quit', { type: 'quit' })
|
|
421
|
-
.bind('?', 'Help', { type: 'help' })
|
|
422
|
-
.bind('ctrl+c', 'Force quit', { type: 'quit' })
|
|
423
|
-
.group('Navigation', (g) => g
|
|
424
|
-
.bind('j', 'Down', { type: 'move', dir: 'down' })
|
|
425
|
-
.bind('k', 'Up', { type: 'move', dir: 'up' })
|
|
426
|
-
);
|
|
427
|
-
|
|
428
|
-
// In TEA update:
|
|
429
|
-
const action = kb.handle(keyMsg);
|
|
430
|
-
if (action !== undefined) return [model, [/* ... */]];
|
|
431
|
-
|
|
432
|
-
// Runtime enable/disable
|
|
433
|
-
kb.disableGroup('Navigation');
|
|
434
|
-
kb.enable('Quit');
|
|
435
|
-
```
|
|
436
|
-
|
|
437
|
-
### Help Generation
|
|
438
|
-
|
|
439
|
-
Auto-generate help text from registered bindings:
|
|
440
|
-
|
|
441
|
-
```typescript
|
|
442
|
-
import {
|
|
443
|
-
helpView,
|
|
444
|
-
helpViewSurface,
|
|
445
|
-
helpShort,
|
|
446
|
-
helpShortSurface,
|
|
447
|
-
helpFor,
|
|
448
|
-
helpForSurface,
|
|
449
|
-
} from '@flyingrobots/bijou-tui';
|
|
450
|
-
|
|
451
|
-
helpView(kb); // full grouped multi-line help
|
|
452
|
-
helpShort(kb); // "q Quit • ? Help • Ctrl+c Force quit • j Down • k Up"
|
|
453
|
-
helpFor(kb, 'Nav'); // only Navigation group
|
|
454
|
-
helpShortSurface(kb, { width: 48 }); // shell hint that stays on the Surface path
|
|
455
|
-
helpViewSurface(kb, { width: 48 }); // grouped help as a Surface
|
|
456
|
-
helpForSurface(kb, 'Nav', { width: 48 });
|
|
457
|
-
```
|
|
458
|
-
|
|
459
|
-
### Input Stack
|
|
460
|
-
|
|
461
|
-
Layered input dispatch for modal UIs — push/pop handlers with opaque or passthrough behavior:
|
|
462
|
-
|
|
463
|
-
```typescript
|
|
464
|
-
import { createInputStack, type KeyMsg } from '@flyingrobots/bijou-tui';
|
|
465
|
-
|
|
466
|
-
const stack = createInputStack<KeyMsg, Msg>();
|
|
467
|
-
|
|
468
|
-
// Base layer — global keys, lets unmatched events fall through
|
|
469
|
-
stack.push(appKeys, { passthrough: true });
|
|
470
|
-
|
|
471
|
-
// Modal opens — captures all input (opaque by default)
|
|
472
|
-
const modalId = stack.push(modalKeys);
|
|
473
|
-
|
|
474
|
-
// Dispatch returns first matched action, top-down
|
|
475
|
-
const action = stack.dispatch(keyMsg);
|
|
476
|
-
|
|
477
|
-
// Modal closes
|
|
478
|
-
stack.remove(modalId);
|
|
479
|
-
```
|
|
480
|
-
|
|
481
|
-
`KeyMap` implements `InputHandler`, so it plugs directly into the input stack.
|
|
482
|
-
|
|
483
|
-
## Overlay Compositing
|
|
484
|
-
|
|
485
|
-
Paint overlays (modals, toasts) on top of existing content:
|
|
486
|
-
|
|
487
|
-
```typescript
|
|
488
|
-
import { compositeSurface, modal, toast } from '@flyingrobots/bijou-tui';
|
|
489
|
-
|
|
490
|
-
// Create a centered dialog
|
|
491
|
-
const dialog = modal({
|
|
492
|
-
title: 'Confirm',
|
|
493
|
-
body: 'Delete this item?',
|
|
494
|
-
hint: 'y/n',
|
|
495
|
-
screenWidth: 80,
|
|
496
|
-
screenHeight: 24,
|
|
497
|
-
});
|
|
498
|
-
|
|
499
|
-
// Create a toast notification
|
|
500
|
-
const notification = toast({
|
|
501
|
-
message: 'Saved successfully',
|
|
502
|
-
variant: 'success', // 'success' | 'error' | 'info'
|
|
503
|
-
anchor: 'bottom-right', // 'top-right' | 'bottom-right' | 'bottom-left' | 'top-left'
|
|
504
|
-
screenWidth: 80,
|
|
505
|
-
screenHeight: 24,
|
|
506
|
-
});
|
|
507
|
-
|
|
508
|
-
// Paint overlays onto background content
|
|
509
|
-
const output = compositeSurface(backgroundSurface, [dialog, notification], { dim: true });
|
|
510
|
-
```
|
|
511
|
-
|
|
512
|
-
Each overlay now exposes both `surface` and `content` forms. Prefer `compositeSurface()` when your app is already on the surface-native path. Keep the string-oriented `composite()` path for explicit lowering boundaries, not as the default mental model. The `dim` option fades the background with ANSI dim.
|
|
513
|
-
|
|
514
|
-
`modal().body`, `modal().hint`, `drawer().content`, and `tooltip().content` accept either plain strings or structured `Surface` content. Use surfaces when the overlay needs real rows, embedded component surfaces, or richer composition inside the interrupting layer.
|
|
515
|
-
|
|
516
|
-
Reach for `toast()` when the app is composing a one-off overlay directly. Reach for the notification system when stacking, actions, routing, or history matter. The notification lab in `examples/notifications` is the canonical higher-level example.
|
|
517
|
-
|
|
518
|
-
`drawer()` now supports `left`/`right`/`top`/`bottom` anchors and optional `region` mounting for panel-scoped overlays.
|
|
519
|
-
Use `drawer()` when the user should keep the main task visible while consulting or editing supplemental context. Use `modal()` when the user must stop and decide. Use `tooltip()` only for tiny local explanation, not for commands or scrollable content.
|
|
520
|
-
|
|
521
|
-
## App Frame
|
|
522
|
-
|
|
523
|
-
`createFramedApp()` wraps page-level TEA logic in a shared shell:
|
|
524
|
-
|
|
525
|
-
- tabs + page switching
|
|
526
|
-
- pane focus and per-pane scroll isolation
|
|
527
|
-
- frame help (`?`) and optional command palette (`ctrl+p` / `:`)
|
|
528
|
-
- overlay factory with pane rects for panel-scoped drawers/modals
|
|
529
|
-
|
|
530
|
-
Pane renderers return a `Surface` or a `LayoutNode`. The shell normalizes those outputs into the framed scroll/focus path for you.
|
|
531
|
-
|
|
532
|
-
Typed shell notes:
|
|
533
|
-
|
|
534
|
-
- `createFramedApp<PageModel, Msg>()` returns `FramedApp<PageModel, Msg>`
|
|
535
|
-
- page `update()` receives `FramePageMsg<Msg>` so raw `mouse` / `pulse` delivery is explicit
|
|
536
|
-
- wrapped shell commands carry `FramedAppMsg<Msg>` instead of collapsing frame/page wrappers into plain `Msg`
|
|
537
|
-
|
|
538
|
-
See `examples/release-workbench/main.ts` for the canonical shell demo and `examples/app-frame/main.ts` for a compact focused example.
|
|
539
|
-
|
|
540
|
-
Shell role split matters:
|
|
541
|
-
|
|
542
|
-
- `statusBarSurface()` communicates concise global state
|
|
543
|
-
- `helpShortSurface()` / `helpViewSurface()` teach shortcuts and scope
|
|
544
|
-
- `commandPaletteSurface()` handles action discovery and navigation
|
|
545
|
-
- notifications surface events and follow-up
|
|
546
|
-
|
|
547
|
-
## Building Blocks
|
|
548
|
-
|
|
549
|
-
Reusable stateful components that follow the TEA state + pure transformers + sync render + convenience keymap pattern:
|
|
550
|
-
|
|
551
|
-
### Navigable Table
|
|
552
|
-
|
|
553
|
-
```typescript
|
|
554
|
-
import {
|
|
555
|
-
createNavigableTableState, navigableTable, navigableTableSurface, navTableFocusNext,
|
|
556
|
-
navTableKeyMap, helpShort,
|
|
557
|
-
} from '@flyingrobots/bijou-tui';
|
|
558
|
-
|
|
559
|
-
const state = createNavigableTableState({ columns, rows, height: 10 });
|
|
560
|
-
const textOutput = navigableTable(state, { ctx });
|
|
561
|
-
const surfaceOutput = navigableTableSurface(state, { ctx });
|
|
562
|
-
const next = navTableFocusNext(state);
|
|
563
|
-
```
|
|
564
|
-
|
|
565
|
-
Use `navigableTableSurface()` when the user should actively traverse a table inside a rich TUI surface. Keep `navigableTable()` for explicit text lowering. If the job is still passive comparison, prefer core `table()` or `tableSurface()` and keep the interaction layer simpler.
|
|
566
|
-
|
|
567
|
-
### Browsable List
|
|
568
|
-
|
|
569
|
-
```typescript
|
|
570
|
-
import {
|
|
571
|
-
createBrowsableListState, browsableList, browsableListSurface, listFocusNext,
|
|
572
|
-
browsableListKeyMap,
|
|
573
|
-
} from '@flyingrobots/bijou-tui';
|
|
574
|
-
|
|
575
|
-
const state = createBrowsableListState({ items, height: 10 });
|
|
576
|
-
const textOutput = browsableList(state);
|
|
577
|
-
const surfaceOutput = browsableListSurface(state, { width: 40 });
|
|
578
|
-
```
|
|
579
|
-
|
|
580
|
-
Use `browsableListSurface()` when the list belongs inside a rich TUI region and should share viewport masking semantics with pagers and focus areas. Keep `browsableList()` for explicit text lowering.
|
|
581
|
-
|
|
582
|
-
### File Picker
|
|
583
|
-
|
|
584
|
-
```typescript
|
|
585
|
-
import {
|
|
586
|
-
createFilePickerState, filePicker, filePickerSurface, fpFocusNext, fpEnter, fpBack,
|
|
587
|
-
filePickerKeyMap,
|
|
588
|
-
} from '@flyingrobots/bijou-tui';
|
|
589
|
-
import { nodeIO } from '@flyingrobots/bijou-node';
|
|
590
|
-
|
|
591
|
-
const io = nodeIO();
|
|
592
|
-
const state = createFilePickerState({ cwd: process.cwd(), io, height: 15 });
|
|
593
|
-
const textOutput = filePicker(state);
|
|
594
|
-
const surfaceOutput = filePickerSurface(state, { width: 60 });
|
|
595
|
-
```
|
|
596
|
-
|
|
597
|
-
Use `filePickerSurface()` when the browser lives inside a rich TUI pane and should inherit shared viewport masking semantics. Keep `filePicker()` for explicit text lowering.
|
|
598
|
-
|
|
599
|
-
### Command Palette
|
|
600
|
-
|
|
601
|
-
```typescript
|
|
602
|
-
import {
|
|
603
|
-
createCommandPaletteState, commandPalette, commandPaletteSurface,
|
|
604
|
-
cpFilter, commandPaletteKeyMap,
|
|
605
|
-
} from '@flyingrobots/bijou-tui';
|
|
606
|
-
|
|
607
|
-
const state = createCommandPaletteState(items, 8);
|
|
608
|
-
const textOutput = commandPalette(state, { width: 60 });
|
|
609
|
-
const surfaceOutput = commandPaletteSurface(state, { width: 60, ctx });
|
|
610
|
-
```
|
|
611
|
-
|
|
612
|
-
Use `commandPaletteSurface()` when the palette is part of a structured shell or overlay and should share viewport masking semantics. Keep `commandPalette()` for explicit text lowering.
|
|
613
|
-
|
|
614
|
-
### Pager
|
|
615
|
-
|
|
616
|
-
```typescript
|
|
617
|
-
import {
|
|
618
|
-
createPagerStateForSurface,
|
|
619
|
-
pagerSurface,
|
|
620
|
-
pagerScrollBy,
|
|
621
|
-
} from '@flyingrobots/bijou-tui';
|
|
622
|
-
|
|
623
|
-
const state = createPagerStateForSurface(contentSurface, { width: 60, height: 20 });
|
|
624
|
-
const output = pagerSurface(contentSurface, state);
|
|
625
|
-
```
|
|
626
|
-
|
|
627
|
-
### Focus Area
|
|
628
|
-
|
|
629
|
-
```typescript
|
|
630
|
-
import {
|
|
631
|
-
createFocusAreaStateForSurface, focusAreaScrollBy, focusAreaSurface,
|
|
632
|
-
focusAreaKeyMap,
|
|
633
|
-
} from '@flyingrobots/bijou-tui';
|
|
634
|
-
|
|
635
|
-
const state = createFocusAreaStateForSurface(contentSurface, {
|
|
636
|
-
width: 60,
|
|
637
|
-
height: 20,
|
|
638
|
-
overflowX: 'scroll',
|
|
639
|
-
});
|
|
640
|
-
const output = focusAreaSurface(contentSurface, state, { focused: true, ctx });
|
|
641
|
-
```
|
|
642
|
-
|
|
643
|
-
If the pane is still intentionally text-composed, `createFocusAreaState()` + `focusArea()` remain the explicit lowering path.
|
|
644
|
-
|
|
645
|
-
### DAG Pane
|
|
646
|
-
|
|
647
|
-
Use `dagPane()` when graph inspection is an active task and the user needs keyboard-owned selection, path highlighting, and scroll control. Keep plain `dag()` in `@flyingrobots/bijou` for passive graph explanation, and move to `dagSlice()` or `dagStats()` when a focused fragment or structural summary would be more honest than a full interactive graph.
|
|
648
|
-
|
|
649
|
-
```typescript
|
|
650
|
-
import {
|
|
651
|
-
createDagPaneState, dagPane, dagPaneSelectChild,
|
|
652
|
-
dagPaneSelectParent, dagPaneKeyMap,
|
|
653
|
-
} from '@flyingrobots/bijou-tui';
|
|
654
|
-
|
|
655
|
-
const state = createDagPaneState({ source: nodes, width: 80, height: 24, ctx });
|
|
656
|
-
const output = dagPane(state, { focused: true, ctx });
|
|
657
|
-
const next = dagPaneSelectChild(state, ctx); // arrow-key navigation
|
|
658
|
-
```
|
|
659
|
-
|
|
660
|
-
All building blocks include `*KeyMap()` factories for preconfigured vim-style keybindings.
|
|
661
|
-
|
|
662
|
-
## Related Packages
|
|
663
|
-
|
|
664
|
-
- [`@flyingrobots/bijou`](https://www.npmjs.com/package/@flyingrobots/bijou) — Zero-dependency core with all components and theme engine
|
|
665
|
-
- [`@flyingrobots/bijou-node`](https://www.npmjs.com/package/@flyingrobots/bijou-node) — Node.js runtime adapter (chalk, readline, process)
|
|
666
|
-
|
|
667
|
-
## License
|
|
184
|
+
## Documentation
|
|
668
185
|
|
|
669
|
-
|
|
186
|
+
- **[GUIDE.md](./GUIDE.md)**: Productive-fast path for building apps.
|
|
187
|
+
- **[ADVANCED_GUIDE.md](./ADVANCED_GUIDE.md)**: Shell doctrine, shaders, and motion internals.
|
|
188
|
+
- **[Render Pipeline Guide](../../docs/guides/render-pipeline.md)**: Stage order, `RenderState`, and `configurePipeline()` truth.
|
|
189
|
+
- **[Design System](../../docs/design-system/README.md)**: Semantic guidance and patterns.
|
|
670
190
|
|
|
671
191
|
---
|
|
672
|
-
|
|
673
|
-
<p align="center">
|
|
674
|
-
Built with 💎 by <a href="https://github.com/flyingrobots">FLYING ROBOTS</a>
|
|
675
|
-
</p>
|
|
676
|
-
|
|
677
|
-
```rust
|
|
678
|
-
.-:::::'::: .-:. ::-.::::::. :::. .,-:::::/
|
|
679
|
-
;;;'''' ;;; ';;. ;;;;';;;`;;;;, `;;;,;;-'````'
|
|
680
|
-
[[[,,== [[[ '[[,[[[' [[[ [[[[[. '[[[[[ [[[[[[/
|
|
681
|
-
`$$$"`` $$' c$$" $$$ $$$ "Y$c$$"$$c. "$$
|
|
682
|
-
888 o88oo,.__ ,8P"` 888 888 Y88 `Y8bo,,,o88o
|
|
683
|
-
"MM, """"YUMMMmM" MMM MMM YM `'YMUP"YMM
|
|
684
|
-
:::::::.. ... :::::::. ... :::::::::::: .::::::.
|
|
685
|
-
;;;;``;;;; .;;;;;;;. ;;;'';;' .;;;;;;;.;;;;;;;;'''';;;` `
|
|
686
|
-
[[[,/[[[' ,[[ \[[, [[[__[[\.,[[ \[[, [[ '[==/[[[[,
|
|
687
|
-
$$$$$$c $$$, $$$ $$""""Y$$$$$, $$$ $$ ''' $
|
|
688
|
-
888b "88bo,"888,_ _,88P_88o,,od8P"888,_ _,88P 88, 88b dP
|
|
689
|
-
MMMM "W" "YMMMMMP" ""YUMMMP" "YMMMMMP" MMM "YMmMY"
|
|
690
|
-
```
|
|
192
|
+
Built with 💎 by [FLYING ROBOTS](https://github.com/flyingrobots)
|