@flyingrobots/bijou-tui 0.1.0 → 0.2.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.
Files changed (60) hide show
  1. package/README.md +221 -30
  2. package/dist/animate.d.ts +60 -0
  3. package/dist/animate.d.ts.map +1 -0
  4. package/dist/animate.js +98 -0
  5. package/dist/animate.js.map +1 -0
  6. package/dist/commands.d.ts.map +1 -1
  7. package/dist/commands.js +2 -2
  8. package/dist/commands.js.map +1 -1
  9. package/dist/eventbus.d.ts +69 -0
  10. package/dist/eventbus.d.ts.map +1 -0
  11. package/dist/eventbus.js +120 -0
  12. package/dist/eventbus.js.map +1 -0
  13. package/dist/flex.d.ts +64 -0
  14. package/dist/flex.d.ts.map +1 -0
  15. package/dist/flex.js +261 -0
  16. package/dist/flex.js.map +1 -0
  17. package/dist/help.d.ts +58 -0
  18. package/dist/help.d.ts.map +1 -0
  19. package/dist/help.js +104 -0
  20. package/dist/help.js.map +1 -0
  21. package/dist/index.d.ts +11 -2
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +18 -1
  24. package/dist/index.js.map +1 -1
  25. package/dist/inputstack.d.ts +79 -0
  26. package/dist/inputstack.d.ts.map +1 -0
  27. package/dist/inputstack.js +81 -0
  28. package/dist/inputstack.js.map +1 -0
  29. package/dist/keybindings.d.ts +83 -0
  30. package/dist/keybindings.d.ts.map +1 -0
  31. package/dist/keybindings.js +184 -0
  32. package/dist/keybindings.js.map +1 -0
  33. package/dist/layout.d.ts +0 -1
  34. package/dist/layout.d.ts.map +1 -1
  35. package/dist/layout.js +3 -4
  36. package/dist/layout.js.map +1 -1
  37. package/dist/runtime.d.ts +3 -0
  38. package/dist/runtime.d.ts.map +1 -1
  39. package/dist/runtime.js +24 -37
  40. package/dist/runtime.js.map +1 -1
  41. package/dist/screen.d.ts +10 -4
  42. package/dist/screen.d.ts.map +1 -1
  43. package/dist/screen.js +13 -11
  44. package/dist/screen.js.map +1 -1
  45. package/dist/spring.d.ts +139 -0
  46. package/dist/spring.d.ts.map +1 -0
  47. package/dist/spring.js +106 -0
  48. package/dist/spring.js.map +1 -0
  49. package/dist/timeline.d.ts +127 -0
  50. package/dist/timeline.d.ts.map +1 -0
  51. package/dist/timeline.js +298 -0
  52. package/dist/timeline.js.map +1 -0
  53. package/dist/types.d.ts +11 -2
  54. package/dist/types.d.ts.map +1 -1
  55. package/dist/types.js.map +1 -1
  56. package/dist/viewport.d.ts +64 -0
  57. package/dist/viewport.d.ts.map +1 -0
  58. package/dist/viewport.js +162 -0
  59. package/dist/viewport.js.map +1 -0
  60. package/package.json +2 -2
@@ -0,0 +1,120 @@
1
+ /**
2
+ * Centralized event bus for TUI applications.
3
+ *
4
+ * Unifies all input sources (keyboard, resize, commands, custom) into
5
+ * a single typed event stream. The TEA runtime subscribes to the bus
6
+ * instead of manually wiring callbacks.
7
+ *
8
+ * ```ts
9
+ * const bus = createEventBus<Msg>();
10
+ *
11
+ * // Connect I/O sources (keyboard + resize)
12
+ * bus.connectIO(ctx.io);
13
+ *
14
+ * // Subscribe to all events
15
+ * bus.on((msg) => {
16
+ * const [model, cmds] = app.update(msg, model);
17
+ * cmds.forEach(cmd => bus.runCmd(cmd));
18
+ * });
19
+ *
20
+ * // Emit custom events
21
+ * bus.emit({ type: 'tick' });
22
+ *
23
+ * // In tests — emit directly, no I/O needed
24
+ * bus.emit({ type: 'key', key: 'a', ctrl: false, alt: false, shift: false });
25
+ * ```
26
+ */
27
+ import { QUIT } from './types.js';
28
+ import { parseKey } from './keys.js';
29
+ // ---------------------------------------------------------------------------
30
+ // Implementation
31
+ // ---------------------------------------------------------------------------
32
+ export function createEventBus() {
33
+ const subscribers = new Set();
34
+ const quitHandlers = new Set();
35
+ const disposables = [];
36
+ let disposed = false;
37
+ function emit(msg) {
38
+ if (disposed)
39
+ return;
40
+ for (const handler of subscribers) {
41
+ handler(msg);
42
+ }
43
+ }
44
+ return {
45
+ on(handler) {
46
+ subscribers.add(handler);
47
+ return {
48
+ dispose() {
49
+ subscribers.delete(handler);
50
+ },
51
+ };
52
+ },
53
+ emit,
54
+ connectIO(io) {
55
+ // Keyboard input
56
+ const inputHandle = io.rawInput((raw) => {
57
+ if (disposed)
58
+ return;
59
+ const keyMsg = parseKey(raw);
60
+ // Skip unknown sequences (mouse events, etc.)
61
+ if (keyMsg.key === 'unknown')
62
+ return;
63
+ emit(keyMsg);
64
+ });
65
+ // Resize events
66
+ const resizeHandle = io.onResize((columns, rows) => {
67
+ if (disposed)
68
+ return;
69
+ const msg = { type: 'resize', columns, rows };
70
+ emit(msg);
71
+ });
72
+ const handle = {
73
+ dispose() {
74
+ inputHandle.dispose();
75
+ resizeHandle.dispose();
76
+ },
77
+ };
78
+ disposables.push(handle);
79
+ return handle;
80
+ },
81
+ runCmd(cmd) {
82
+ if (disposed)
83
+ return;
84
+ void cmd(emit).then((result) => {
85
+ if (disposed)
86
+ return;
87
+ if (result === QUIT) {
88
+ for (const handler of quitHandlers) {
89
+ handler();
90
+ }
91
+ return;
92
+ }
93
+ if (result !== undefined) {
94
+ emit(result);
95
+ }
96
+ }).catch((err) => {
97
+ // Surface command rejections instead of leaving unhandled promise rejections.
98
+ console.error('[EventBus] Command rejected:', err);
99
+ });
100
+ },
101
+ onQuit(handler) {
102
+ quitHandlers.add(handler);
103
+ return {
104
+ dispose() {
105
+ quitHandlers.delete(handler);
106
+ },
107
+ };
108
+ },
109
+ dispose() {
110
+ disposed = true;
111
+ for (const d of disposables) {
112
+ d.dispose();
113
+ }
114
+ disposables.length = 0;
115
+ subscribers.clear();
116
+ quitHandlers.clear();
117
+ },
118
+ };
119
+ }
120
+ //# sourceMappingURL=eventbus.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"eventbus.js","sourceRoot":"","sources":["../src/eventbus.ts"],"names":[],"mappings":"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"}
package/dist/flex.d.ts ADDED
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Flexbox-style layout for terminal UIs.
3
+ *
4
+ * Distributes available space among children using flex-grow factors,
5
+ * fixed basis sizes, and min/max constraints. Children can be static
6
+ * strings or render functions that receive their allocated dimensions.
7
+ *
8
+ * ```ts
9
+ * flex({ direction: 'row', width: 80, height: 24 },
10
+ * { basis: 20, content: sidebar },
11
+ * { flex: 1, content: (w, h) => viewport({ width: w, height: h, content: body }) },
12
+ * )
13
+ * ```
14
+ */
15
+ export interface FlexOptions {
16
+ /** Layout direction. Default: 'row'. */
17
+ readonly direction?: 'row' | 'column';
18
+ /** Available width in columns. */
19
+ readonly width: number;
20
+ /** Available height in rows. */
21
+ readonly height: number;
22
+ /** Gap between children (in the main axis). Default: 0. */
23
+ readonly gap?: number;
24
+ }
25
+ export interface FlexChild {
26
+ /**
27
+ * Content to render. Either a static string, or a function that
28
+ * receives the allocated (width, height) and returns a string.
29
+ */
30
+ readonly content: string | ((width: number, height: number) => string);
31
+ /** Flex-grow factor. Children with flex > 0 share remaining space. Default: 0. */
32
+ readonly flex?: number;
33
+ /** Fixed size along the main axis (columns for row, rows for column). */
34
+ readonly basis?: number;
35
+ /** Minimum size along the main axis. */
36
+ readonly minSize?: number;
37
+ /** Maximum size along the main axis. */
38
+ readonly maxSize?: number;
39
+ /** Cross-axis alignment. Default: 'start'. */
40
+ readonly align?: 'start' | 'center' | 'end';
41
+ }
42
+ /**
43
+ * Lay out children using flexbox-style rules.
44
+ *
45
+ * Row direction: children are placed side-by-side horizontally.
46
+ * Column direction: children are stacked vertically.
47
+ *
48
+ * ```ts
49
+ * // Sidebar + main content
50
+ * flex({ direction: 'row', width: 80, height: 24, gap: 1 },
51
+ * { basis: 20, content: sidebarContent },
52
+ * { flex: 1, content: (w, h) => renderMain(w, h) },
53
+ * )
54
+ *
55
+ * // Header + body + footer
56
+ * flex({ direction: 'column', width: 80, height: 24 },
57
+ * { basis: 1, content: headerLine },
58
+ * { flex: 1, content: (w, h) => renderBody(w, h) },
59
+ * { basis: 1, content: statusLine },
60
+ * )
61
+ * ```
62
+ */
63
+ export declare function flex(options: FlexOptions, ...children: FlexChild[]): string;
64
+ //# sourceMappingURL=flex.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"flex.d.ts","sourceRoot":"","sources":["../src/flex.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAMH,MAAM,WAAW,WAAW;IAC1B,wCAAwC;IACxC,QAAQ,CAAC,SAAS,CAAC,EAAE,KAAK,GAAG,QAAQ,CAAC;IACtC,kCAAkC;IAClC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,gCAAgC;IAChC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,2DAA2D;IAC3D,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,SAAS;IACxB;;;OAGG;IACH,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,MAAM,CAAC,CAAC;IACvE,kFAAkF;IAClF,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,yEAAyE;IACzE,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,wCAAwC;IACxC,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,wCAAwC;IACxC,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,8CAA8C;IAC9C,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,GAAG,QAAQ,GAAG,KAAK,CAAC;CAC7C;AAoND;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,IAAI,CAAC,OAAO,EAAE,WAAW,EAAE,GAAG,QAAQ,EAAE,SAAS,EAAE,GAAG,MAAM,CAkB3E"}
package/dist/flex.js ADDED
@@ -0,0 +1,261 @@
1
+ /**
2
+ * Flexbox-style layout for terminal UIs.
3
+ *
4
+ * Distributes available space among children using flex-grow factors,
5
+ * fixed basis sizes, and min/max constraints. Children can be static
6
+ * strings or render functions that receive their allocated dimensions.
7
+ *
8
+ * ```ts
9
+ * flex({ direction: 'row', width: 80, height: 24 },
10
+ * { basis: 20, content: sidebar },
11
+ * { flex: 1, content: (w, h) => viewport({ width: w, height: h, content: body }) },
12
+ * )
13
+ * ```
14
+ */
15
+ // ---------------------------------------------------------------------------
16
+ // ANSI helpers
17
+ // ---------------------------------------------------------------------------
18
+ const ANSI_RE = /\x1b\[[0-9;]*m/g;
19
+ function visualWidth(s) {
20
+ return s.replace(ANSI_RE, '').length;
21
+ }
22
+ function computeSizes(children, mainAxisTotal, crossAxisTotal, gap, isRow) {
23
+ if (children.length === 0)
24
+ return [];
25
+ const totalGaps = gap * (children.length - 1);
26
+ const available = Math.max(0, mainAxisTotal - totalGaps);
27
+ // First pass: allocate fixed-size children (basis or content measurement)
28
+ const sizes = [];
29
+ let usedByFixed = 0;
30
+ let totalFlex = 0;
31
+ for (const child of children) {
32
+ const flexGrow = child.flex ?? 0;
33
+ if (flexGrow > 0) {
34
+ sizes.push(0); // placeholder
35
+ totalFlex += flexGrow;
36
+ }
37
+ else if (child.basis !== undefined) {
38
+ const clamped = clampSize(child.basis, child.minSize, child.maxSize);
39
+ sizes.push(clamped);
40
+ usedByFixed += clamped;
41
+ }
42
+ else {
43
+ // Auto-size from content
44
+ const measured = measureContent(child.content, isRow);
45
+ const clamped = clampSize(measured, child.minSize, child.maxSize);
46
+ sizes.push(clamped);
47
+ usedByFixed += clamped;
48
+ }
49
+ }
50
+ // Second pass: distribute remaining space to flex children
51
+ const remaining = Math.max(0, available - usedByFixed);
52
+ for (let i = 0; i < children.length; i++) {
53
+ const flexGrow = children[i].flex ?? 0;
54
+ if (flexGrow > 0) {
55
+ const raw = totalFlex > 0 ? Math.floor((flexGrow / totalFlex) * remaining) : 0;
56
+ sizes[i] = clampSize(raw, children[i].minSize, children[i].maxSize);
57
+ }
58
+ }
59
+ return children.map((child, i) => ({
60
+ allocatedSize: sizes[i],
61
+ crossSize: crossAxisTotal,
62
+ child,
63
+ }));
64
+ }
65
+ function clampSize(size, min, max) {
66
+ let result = size;
67
+ if (min !== undefined)
68
+ result = Math.max(result, min);
69
+ if (max !== undefined)
70
+ result = Math.min(result, max);
71
+ return Math.max(0, result);
72
+ }
73
+ function measureContent(content, isRow) {
74
+ if (typeof content === 'function') {
75
+ // Can't measure a render function — treat as 0 (must use flex or basis)
76
+ return 0;
77
+ }
78
+ const lines = content.split('\n');
79
+ if (isRow) {
80
+ // Width = max visible line width
81
+ return Math.max(0, ...lines.map(visualWidth));
82
+ }
83
+ // Height = number of lines
84
+ return lines.length;
85
+ }
86
+ // ---------------------------------------------------------------------------
87
+ // Rendering
88
+ // ---------------------------------------------------------------------------
89
+ function renderContent(child, width, height) {
90
+ if (typeof child.content === 'function') {
91
+ return child.content(width, height);
92
+ }
93
+ return child.content;
94
+ }
95
+ /**
96
+ * Clip/pad each content line to exact width. Does NOT pad height —
97
+ * that's handled by alignCross.
98
+ */
99
+ function fitWidth(content, width, align = 'start') {
100
+ const lines = content.split('\n');
101
+ return lines.map((line) => {
102
+ const vis = visualWidth(line);
103
+ if (vis > width) {
104
+ return clipToWidth(line, width);
105
+ }
106
+ const padding = Math.max(0, width - vis);
107
+ switch (align) {
108
+ case 'start':
109
+ return line + ' '.repeat(padding);
110
+ case 'end':
111
+ return ' '.repeat(padding) + line;
112
+ case 'center': {
113
+ const before = Math.floor(padding / 2);
114
+ const after = padding - before;
115
+ return ' '.repeat(before) + line + ' '.repeat(after);
116
+ }
117
+ default: {
118
+ const _exhaustive = align;
119
+ throw new Error(`Unknown alignment: ${_exhaustive}`);
120
+ }
121
+ }
122
+ });
123
+ }
124
+ function clipToWidth(str, maxWidth) {
125
+ let visible = 0;
126
+ let result = '';
127
+ let inEscape = false;
128
+ for (let i = 0; i < str.length; i++) {
129
+ const ch = str[i];
130
+ if (ch === '\x1b') {
131
+ inEscape = true;
132
+ result += ch;
133
+ continue;
134
+ }
135
+ if (inEscape) {
136
+ result += ch;
137
+ if (ch === 'm')
138
+ inEscape = false;
139
+ continue;
140
+ }
141
+ if (visible >= maxWidth) {
142
+ result += '\x1b[0m';
143
+ break;
144
+ }
145
+ result += ch;
146
+ visible++;
147
+ }
148
+ return result;
149
+ }
150
+ /**
151
+ * Align content lines along the cross axis.
152
+ */
153
+ function alignCross(lines, totalCrossSize, align, width) {
154
+ if (lines.length >= totalCrossSize)
155
+ return lines.slice(0, totalCrossSize);
156
+ const emptyLine = ' '.repeat(Math.max(0, width));
157
+ const padding = totalCrossSize - lines.length;
158
+ switch (align) {
159
+ case 'start':
160
+ return [...lines, ...Array.from({ length: padding }).fill(emptyLine)];
161
+ case 'end':
162
+ return [...Array.from({ length: padding }).fill(emptyLine), ...lines];
163
+ case 'center': {
164
+ const before = Math.floor(padding / 2);
165
+ const after = padding - before;
166
+ return [
167
+ ...Array.from({ length: before }).fill(emptyLine),
168
+ ...lines,
169
+ ...Array.from({ length: after }).fill(emptyLine),
170
+ ];
171
+ }
172
+ }
173
+ }
174
+ // ---------------------------------------------------------------------------
175
+ // flex() — main API
176
+ // ---------------------------------------------------------------------------
177
+ /**
178
+ * Lay out children using flexbox-style rules.
179
+ *
180
+ * Row direction: children are placed side-by-side horizontally.
181
+ * Column direction: children are stacked vertically.
182
+ *
183
+ * ```ts
184
+ * // Sidebar + main content
185
+ * flex({ direction: 'row', width: 80, height: 24, gap: 1 },
186
+ * { basis: 20, content: sidebarContent },
187
+ * { flex: 1, content: (w, h) => renderMain(w, h) },
188
+ * )
189
+ *
190
+ * // Header + body + footer
191
+ * flex({ direction: 'column', width: 80, height: 24 },
192
+ * { basis: 1, content: headerLine },
193
+ * { flex: 1, content: (w, h) => renderBody(w, h) },
194
+ * { basis: 1, content: statusLine },
195
+ * )
196
+ * ```
197
+ */
198
+ export function flex(options, ...children) {
199
+ const { direction = 'row' } = options;
200
+ const width = Math.max(0, Math.floor(options.width));
201
+ const height = Math.max(0, Math.floor(options.height));
202
+ const gap = Math.max(0, Math.floor(options.gap ?? 0));
203
+ const isRow = direction === 'row';
204
+ const mainAxisTotal = isRow ? width : height;
205
+ const crossAxisTotal = isRow ? height : width;
206
+ if (children.length === 0)
207
+ return '';
208
+ const resolved = computeSizes(children, mainAxisTotal, crossAxisTotal, gap, isRow);
209
+ if (isRow) {
210
+ return renderRow(resolved, height, gap);
211
+ }
212
+ return renderColumn(resolved, width, height, gap);
213
+ }
214
+ function renderRow(items, totalHeight, gap) {
215
+ // Render each child into a column of lines
216
+ const columns = [];
217
+ for (const item of items) {
218
+ const childWidth = item.allocatedSize;
219
+ const rendered = renderContent(item.child, childWidth, totalHeight);
220
+ // In row mode, fitWidth always uses 'start' — align controls cross-axis (vertical) only
221
+ const widthFitted = fitWidth(rendered, childWidth);
222
+ const aligned = alignCross(widthFitted, totalHeight, item.child.align ?? 'start', childWidth);
223
+ columns.push(aligned);
224
+ }
225
+ // Compose columns side-by-side
226
+ const spacer = ' '.repeat(Math.max(0, gap));
227
+ const rows = [];
228
+ for (let r = 0; r < totalHeight; r++) {
229
+ const parts = [];
230
+ for (let c = 0; c < columns.length; c++) {
231
+ parts.push(columns[c][r]);
232
+ }
233
+ rows.push(parts.join(spacer));
234
+ }
235
+ return rows.join('\n');
236
+ }
237
+ function renderColumn(items, totalWidth, totalHeight, gap) {
238
+ const lines = [];
239
+ for (let i = 0; i < items.length; i++) {
240
+ const item = items[i];
241
+ const childHeight = item.allocatedSize;
242
+ const rendered = renderContent(item.child, totalWidth, childHeight);
243
+ const widthFitted = fitWidth(rendered, totalWidth, item.child.align ?? 'start');
244
+ const aligned = alignCross(widthFitted, childHeight, 'start', totalWidth);
245
+ lines.push(...aligned);
246
+ // Add gap between items
247
+ if (i < items.length - 1 && gap > 0) {
248
+ const spacer = ' '.repeat(Math.max(0, totalWidth));
249
+ for (let g = 0; g < gap; g++) {
250
+ lines.push(spacer);
251
+ }
252
+ }
253
+ }
254
+ // Pad to fill totalHeight if needed
255
+ const emptyLine = ' '.repeat(Math.max(0, totalWidth));
256
+ while (lines.length < totalHeight) {
257
+ lines.push(emptyLine);
258
+ }
259
+ return lines.slice(0, totalHeight).join('\n');
260
+ }
261
+ //# sourceMappingURL=flex.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"flex.js","sourceRoot":"","sources":["../src/flex.ts"],"names":[],"mappings":"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"}
package/dist/help.d.ts ADDED
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Auto-generate help views from registered keybindings.
3
+ *
4
+ * ```ts
5
+ * const kb = createKeyMap<Msg>()
6
+ * .bind('q', 'Quit', quitMsg)
7
+ * .group('Navigation', g => g
8
+ * .bind('j', 'Down', downMsg)
9
+ * .bind('k', 'Up', upMsg)
10
+ * );
11
+ *
12
+ * helpView(kb) // full grouped help
13
+ * helpShort(kb) // single-line summary
14
+ * helpFor(kb, 'Nav') // filter by group prefix
15
+ * ```
16
+ */
17
+ import type { BindingInfo } from './keybindings.js';
18
+ /** Anything that can list its bindings (satisfied by KeyMap). */
19
+ export interface BindingSource {
20
+ bindings(): readonly BindingInfo[];
21
+ }
22
+ export interface HelpOptions {
23
+ /** Only show enabled bindings (default: true). */
24
+ enabledOnly?: boolean;
25
+ /** Filter to bindings in groups matching this prefix. */
26
+ groupFilter?: string;
27
+ /** Separator between key and description (default: " "). */
28
+ separator?: string;
29
+ /** Title shown at the top (default: none). */
30
+ title?: string;
31
+ }
32
+ /**
33
+ * Full help view — grouped, multi-line.
34
+ *
35
+ * ```
36
+ * Navigation
37
+ * j Down
38
+ * k Up
39
+ *
40
+ * General
41
+ * q Quit
42
+ * ? Toggle help
43
+ * ```
44
+ */
45
+ export declare function helpView(keymap: BindingSource, options?: HelpOptions): string;
46
+ /**
47
+ * Short, single-line help — keys only, no groups.
48
+ *
49
+ * ```
50
+ * q Quit • j Down • k Up • ? Help
51
+ * ```
52
+ */
53
+ export declare function helpShort(keymap: BindingSource, options?: Pick<HelpOptions, 'enabledOnly' | 'groupFilter'>): string;
54
+ /**
55
+ * Filter help to a specific group (convenience wrapper).
56
+ */
57
+ export declare function helpFor(keymap: BindingSource, groupPrefix: string, options?: HelpOptions): string;
58
+ //# sourceMappingURL=help.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"help.d.ts","sourceRoot":"","sources":["../src/help.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAGpD,iEAAiE;AACjE,MAAM,WAAW,aAAa;IAC5B,QAAQ,IAAI,SAAS,WAAW,EAAE,CAAC;CACpC;AAMD,MAAM,WAAW,WAAW;IAC1B,kDAAkD;IAClD,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,yDAAyD;IACzD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,6DAA6D;IAC7D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,8CAA8C;IAC9C,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAMD;;;;;;;;;;;;GAYG;AACH,wBAAgB,QAAQ,CAAC,MAAM,EAAE,aAAa,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,MAAM,CAoD7E;AAED;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE,aAAa,EAAE,OAAO,CAAC,EAAE,IAAI,CAAC,WAAW,EAAE,aAAa,GAAG,aAAa,CAAC,GAAG,MAAM,CAenH;AAED;;GAEG;AACH,wBAAgB,OAAO,CAAC,MAAM,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,MAAM,CAEjG"}
package/dist/help.js ADDED
@@ -0,0 +1,104 @@
1
+ /**
2
+ * Auto-generate help views from registered keybindings.
3
+ *
4
+ * ```ts
5
+ * const kb = createKeyMap<Msg>()
6
+ * .bind('q', 'Quit', quitMsg)
7
+ * .group('Navigation', g => g
8
+ * .bind('j', 'Down', downMsg)
9
+ * .bind('k', 'Up', upMsg)
10
+ * );
11
+ *
12
+ * helpView(kb) // full grouped help
13
+ * helpShort(kb) // single-line summary
14
+ * helpFor(kb, 'Nav') // filter by group prefix
15
+ * ```
16
+ */
17
+ import { formatKeyCombo } from './keybindings.js';
18
+ // ---------------------------------------------------------------------------
19
+ // Help views
20
+ // ---------------------------------------------------------------------------
21
+ /**
22
+ * Full help view — grouped, multi-line.
23
+ *
24
+ * ```
25
+ * Navigation
26
+ * j Down
27
+ * k Up
28
+ *
29
+ * General
30
+ * q Quit
31
+ * ? Toggle help
32
+ * ```
33
+ */
34
+ export function helpView(keymap, options) {
35
+ const enabledOnly = options?.enabledOnly ?? true;
36
+ const sep = options?.separator ?? ' ';
37
+ const groupFilter = options?.groupFilter;
38
+ let all = keymap.bindings();
39
+ if (enabledOnly)
40
+ all = all.filter((b) => b.enabled);
41
+ if (groupFilter !== undefined) {
42
+ all = all.filter((b) => b.group.toLowerCase().startsWith(groupFilter.toLowerCase()));
43
+ }
44
+ if (all.length === 0)
45
+ return '';
46
+ // Group by group name
47
+ const groups = new Map();
48
+ for (const b of all) {
49
+ const group = b.group || 'General';
50
+ let list = groups.get(group);
51
+ if (!list) {
52
+ list = [];
53
+ groups.set(group, list);
54
+ }
55
+ list.push(b);
56
+ }
57
+ // Find max key width for alignment
58
+ const allFormatted = all.map((b) => formatKeyCombo(b.combo));
59
+ const maxKeyLen = Math.max(...allFormatted.map((k) => k.length));
60
+ const lines = [];
61
+ if (options?.title) {
62
+ lines.push(options.title);
63
+ lines.push('');
64
+ }
65
+ let first = true;
66
+ for (const [groupName, bindings] of groups) {
67
+ if (!first)
68
+ lines.push('');
69
+ first = false;
70
+ lines.push(groupName);
71
+ for (const b of bindings) {
72
+ const key = formatKeyCombo(b.combo).padEnd(maxKeyLen);
73
+ lines.push(` ${key}${sep}${b.description}`);
74
+ }
75
+ }
76
+ return lines.join('\n');
77
+ }
78
+ /**
79
+ * Short, single-line help — keys only, no groups.
80
+ *
81
+ * ```
82
+ * q Quit • j Down • k Up • ? Help
83
+ * ```
84
+ */
85
+ export function helpShort(keymap, options) {
86
+ const enabledOnly = options?.enabledOnly ?? true;
87
+ const groupFilter = options?.groupFilter;
88
+ let all = keymap.bindings();
89
+ if (enabledOnly)
90
+ all = all.filter((b) => b.enabled);
91
+ if (groupFilter !== undefined) {
92
+ all = all.filter((b) => b.group.toLowerCase().startsWith(groupFilter.toLowerCase()));
93
+ }
94
+ return all
95
+ .map((b) => `${formatKeyCombo(b.combo)} ${b.description}`)
96
+ .join(' • ');
97
+ }
98
+ /**
99
+ * Filter help to a specific group (convenience wrapper).
100
+ */
101
+ export function helpFor(keymap, groupPrefix, options) {
102
+ return helpView(keymap, { ...options, groupFilter: groupPrefix });
103
+ }
104
+ //# sourceMappingURL=help.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"help.js","sourceRoot":"","sources":["../src/help.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -1,8 +1,17 @@
1
- export type { App, Cmd, KeyMsg, QuitSignal, RunOptions } from './types.js';
1
+ export type { App, Cmd, KeyMsg, ResizeMsg, QuitSignal, RunOptions } from './types.js';
2
2
  export { QUIT } from './types.js';
3
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';
4
+ export { enterScreen, exitScreen, clearAndHome, renderFrame, ENTER_ALT_SCREEN, EXIT_ALT_SCREEN, HIDE_CURSOR, SHOW_CURSOR, WRAP_DISABLE, WRAP_ENABLE, CLEAR_SCREEN, CLEAR_TO_END, CLEAR_LINE_TO_END, CLEAR_LINE, HOME, } from './screen.js';
5
5
  export { quit, tick, batch } from './commands.js';
6
6
  export { run } from './runtime.js';
7
+ export { type BusMsg, type EventBus, createEventBus, } from './eventbus.js';
7
8
  export { vstack, hstack } from './layout.js';
9
+ export { type SpringConfig, type SpringPreset, type SpringState, SPRING_PRESETS, springStep, createSpringState, resolveSpringConfig, type EasingFn, EASINGS, type TweenConfig, type TweenState, tweenStep, createTweenState, resolveTweenConfig, } from './spring.js';
10
+ export { type SpringAnimateOptions, type TweenAnimateOptions, type AnimateOptions, animate, sequence, } from './animate.js';
11
+ export { type ViewportOptions, type ScrollState, viewport, createScrollState, scrollBy, scrollTo, scrollToTop, scrollToBottom, pageDown, pageUp, } from './viewport.js';
12
+ export { type Position, type SpringTrackDef, type TweenTrackDef, type TrackDef, type TimelineState, type TimelineBuilder, type Timeline, timeline, } from './timeline.js';
13
+ export { type FlexOptions, type FlexChild, flex, } from './flex.js';
14
+ export { type KeyCombo, type BindingInfo, type KeyMap, type KeyMapGroup, createKeyMap, parseKeyCombo, formatKeyCombo, } from './keybindings.js';
15
+ export { type BindingSource, type HelpOptions, helpView, helpShort, helpFor, } from './help.js';
16
+ export { type InputHandler, type LayerOptions, type LayerInfo, type InputStack, createInputStack, } from './inputstack.js';
8
17
  //# sourceMappingURL=index.d.ts.map