take4-console 0.15.0 → 0.25.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/CHANGELOG.md +365 -0
- package/README.md +1 -1
- package/dist/Screen/InterfaceBuilder.d.mts +15 -4
- package/dist/Screen/InterfaceBuilder.d.mts.map +1 -1
- package/dist/Screen/InterfaceBuilder.mjs +104 -8
- package/dist/Screen/InterfaceBuilder.mjs.map +1 -1
- package/dist/Screen/Pos.d.mts +12 -0
- package/dist/Screen/Pos.d.mts.map +1 -1
- package/dist/Screen/Pos.mjs +23 -1
- package/dist/Screen/Pos.mjs.map +1 -1
- package/dist/Screen/Screen.d.mts +77 -3
- package/dist/Screen/Screen.d.mts.map +1 -1
- package/dist/Screen/Screen.mjs +168 -3
- package/dist/Screen/Screen.mjs.map +1 -1
- package/dist/Screen/Size.d.mts +49 -6
- package/dist/Screen/Size.d.mts.map +1 -1
- package/dist/Screen/Size.mjs +81 -7
- package/dist/Screen/Size.mjs.map +1 -1
- package/dist/Screen/Window.d.mts +131 -20
- package/dist/Screen/Window.d.mts.map +1 -1
- package/dist/Screen/Window.mjs +474 -57
- package/dist/Screen/Window.mjs.map +1 -1
- package/dist/Screen/WindowManager.d.mts +85 -5
- package/dist/Screen/WindowManager.d.mts.map +1 -1
- package/dist/Screen/WindowManager.mjs +279 -26
- package/dist/Screen/WindowManager.mjs.map +1 -1
- package/dist/Screen/controls/ListBox.d.mts +34 -12
- package/dist/Screen/controls/ListBox.d.mts.map +1 -1
- package/dist/Screen/controls/ListBox.mjs +127 -25
- package/dist/Screen/controls/ListBox.mjs.map +1 -1
- package/dist/Screen/controls/TextArea.d.mts +15 -1
- package/dist/Screen/controls/TextArea.d.mts.map +1 -1
- package/dist/Screen/controls/TextArea.mjs +74 -1
- package/dist/Screen/controls/TextArea.mjs.map +1 -1
- package/dist/Screen/controls/TextBox.d.mts +13 -1
- package/dist/Screen/controls/TextBox.d.mts.map +1 -1
- package/dist/Screen/controls/TextBox.mjs +36 -1
- package/dist/Screen/controls/TextBox.mjs.map +1 -1
- package/dist/Screen/textWidth.d.mts +13 -0
- package/dist/Screen/textWidth.d.mts.map +1 -0
- package/dist/Screen/textWidth.mjs +188 -0
- package/dist/Screen/textWidth.mjs.map +1 -0
- package/dist/Screen/types.d.mts +336 -20
- package/dist/Screen/types.d.mts.map +1 -1
- package/dist/Screen/types.mjs.map +1 -1
- package/dist/index.d.mts +3 -2
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +3 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -4
- package/src/Screen/InterfaceBuilder.mts +116 -20
- package/src/Screen/Pos.mts +24 -1
- package/src/Screen/Screen.mts +192 -4
- package/src/Screen/Size.mts +97 -12
- package/src/Screen/Window.mts +463 -63
- package/src/Screen/WindowManager.mts +301 -29
- package/src/Screen/controls/ListBox.mts +151 -32
- package/src/Screen/controls/TextArea.mts +82 -1
- package/src/Screen/controls/TextBox.mts +40 -1
- package/src/Screen/textWidth.mts +186 -0
- package/src/Screen/types.mts +328 -23
- package/src/demo.mts +232 -20
- package/src/index.mts +23 -3
- package/src/layout.yaml +56 -24
package/src/demo.mts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { fileURLToPath } from 'node:url';
|
|
2
2
|
import { join, dirname } from 'node:path';
|
|
3
|
+
import { spawnSync } from 'node:child_process';
|
|
3
4
|
import { Screen } from './Screen/Screen.mjs';
|
|
4
5
|
import { WindowManager } from './Screen/WindowManager.mjs';
|
|
5
6
|
import { InterfaceBuilder } from './Screen/InterfaceBuilder.mjs';
|
|
@@ -12,15 +13,53 @@ import { Spinner } from './Screen/controls/Spinner.mjs';
|
|
|
12
13
|
import { Sparkline } from './Screen/controls/Sparkline.mjs';
|
|
13
14
|
import { ListBox } from './Screen/controls/ListBox.mjs';
|
|
14
15
|
import { Tabs } from './Screen/controls/Tabs.mjs';
|
|
16
|
+
import { Window } from './Screen/Window.mjs';
|
|
17
|
+
import type { ListBoxRowSegments, WindowProperties, StyleId } from './Screen/types.mjs';
|
|
18
|
+
|
|
19
|
+
/** Custom Window subclass registered with InterfaceBuilder via
|
|
20
|
+
* `registerType('badge', …)` to demonstrate P0-10 — the factory resolves
|
|
21
|
+
* `props.text` / `props.color` from the YAML node and stashes them so the
|
|
22
|
+
* overridden render() can repaint the label every frame after the size is
|
|
23
|
+
* resolved by the parent layout pass. */
|
|
24
|
+
class Badge extends Window {
|
|
25
|
+
private msg: string;
|
|
26
|
+
private styleId: StyleId;
|
|
27
|
+
|
|
28
|
+
/** Constructs a Badge with its visible text and pre-registered style id. */
|
|
29
|
+
public constructor(wp: WindowProperties, msg: string, styleId: StyleId) {
|
|
30
|
+
super(wp);
|
|
31
|
+
this.msg = msg;
|
|
32
|
+
this.styleId = styleId;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Repaints the badge text before delegating to the default compositing pipeline. */
|
|
36
|
+
public override render(): void {
|
|
37
|
+
this.clear();
|
|
38
|
+
this.writeText(this.msg, { x: 0, y: 0, style: this.styleId });
|
|
39
|
+
super.render();
|
|
40
|
+
}
|
|
41
|
+
}
|
|
15
42
|
|
|
16
43
|
type LedState = 'ok' | 'warn' | 'error' | 'off';
|
|
44
|
+
type EventLevel = 'ok' | 'warn' | 'error';
|
|
45
|
+
|
|
46
|
+
interface EventRow {
|
|
47
|
+
timestamp: string;
|
|
48
|
+
level: EventLevel;
|
|
49
|
+
message: string;
|
|
50
|
+
count: number;
|
|
51
|
+
}
|
|
17
52
|
|
|
18
53
|
// ── Entry point ───────────────────────────────────────────────────────────────
|
|
19
54
|
|
|
20
55
|
/** Renders the controls demo. */
|
|
21
56
|
const main = async (): Promise<void> => {
|
|
22
|
-
|
|
23
|
-
|
|
57
|
+
// 0.19.0: Screen owns the alt-screen + cursor toggles, so WindowManager won't
|
|
58
|
+
// re-enter them. dispose() runs on exit (via process.on('exit', …)) so the
|
|
59
|
+
// terminal state is restored even when the user kills the process with a
|
|
60
|
+
// signal that bypasses WindowManager.stop().
|
|
61
|
+
const screen = new Screen({ altScreen: true, hideCursor: true });
|
|
62
|
+
let { width, height } = screen.getSize();
|
|
24
63
|
|
|
25
64
|
screen.fill(' ', screen.registerStyle({ background: 234 }));
|
|
26
65
|
|
|
@@ -30,26 +69,123 @@ const main = async (): Promise<void> => {
|
|
|
30
69
|
exitKeys: ['q', '\x03'],
|
|
31
70
|
onExit: () => {
|
|
32
71
|
if (demoTimer) clearInterval(demoTimer);
|
|
72
|
+
screen.dispose();
|
|
33
73
|
process.exit(0);
|
|
34
74
|
},
|
|
35
75
|
mouse: true,
|
|
36
76
|
});
|
|
37
77
|
|
|
78
|
+
// 0.19.0: SIGWINCH autoresize. The Screen reflows percentage-based children
|
|
79
|
+
// for us; the demo only needs to redraw the current-size label in the
|
|
80
|
+
// status bar so the new geometry is visible at runtime.
|
|
81
|
+
screen.on('resize', size => {
|
|
82
|
+
width = size.width;
|
|
83
|
+
height = size.height;
|
|
84
|
+
redrawStatusBar();
|
|
85
|
+
screen.render();
|
|
86
|
+
});
|
|
87
|
+
|
|
38
88
|
const layoutPath = join(dirname(fileURLToPath(import.meta.url)), 'layout.yaml');
|
|
39
|
-
const
|
|
89
|
+
const ib = new InterfaceBuilder();
|
|
90
|
+
|
|
91
|
+
// P0-10 demo: register a user-defined 'badge' control so the header can
|
|
92
|
+
// host a yellow "P0-10" label built from a class that never needed to be
|
|
93
|
+
// baked into InterfaceBuilder's built-in switch.
|
|
94
|
+
ib.registerType('badge', (node, ctx) => {
|
|
95
|
+
const props = (node.props ?? {}) as { text?: string; color?: number };
|
|
96
|
+
const styleId = screen.registerStyle({
|
|
97
|
+
background: 24,
|
|
98
|
+
foreground: props.color ?? 220,
|
|
99
|
+
bold: true,
|
|
100
|
+
});
|
|
101
|
+
return new Badge(ctx.wp, props.text ?? '', styleId);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// P0-6 demo: when the user presses Enter in the username field, push an
|
|
105
|
+
// event into the log so the new onSubmit callback is visible at runtime.
|
|
106
|
+
ib.registerCallback('usernameSubmitted', (...args: unknown[]) => {
|
|
107
|
+
const value = String(args[0] ?? '');
|
|
108
|
+
const list = result.get('eventsList') as ListBox<EventRow>;
|
|
109
|
+
const entry: EventRow = {
|
|
110
|
+
timestamp: timestamp(),
|
|
111
|
+
level: 'ok',
|
|
112
|
+
message: `user submit: ${value}`,
|
|
113
|
+
count: 1,
|
|
114
|
+
};
|
|
115
|
+
list.setItems([entry, ...list.getItems()].slice(0, 20));
|
|
116
|
+
screen.render();
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
const result = await ib.buildFromFile(layoutPath, screen, wm);
|
|
40
120
|
|
|
41
121
|
// ── Header & status bar ───────────────────────────────────────────────────
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
{
|
|
122
|
+
// Header is rendered via writeMarkup() so the named styles in the registry drive
|
|
123
|
+
// the per-word colouring. `hdr:*` names are registered for the demo and then
|
|
124
|
+
// referenced from the template string — no manual StyleId threading.
|
|
125
|
+
screen.setBuiltinStyle('hdr:app', { background: 24, foreground: 231, bold: true });
|
|
126
|
+
screen.setBuiltinStyle('hdr:mode', { background: 24, foreground: 153 });
|
|
127
|
+
screen.setBuiltinStyle('hdr:sep', { background: 24, foreground: 110 });
|
|
128
|
+
const headerBase = screen.registerStyle({ background: 24 });
|
|
129
|
+
result.get('header')!.writeMarkup(
|
|
130
|
+
' {hdr:app}take4_console{/} {hdr:sep}│{/} {hdr:mode}controls demo{/} ',
|
|
131
|
+
{ style: headerBase },
|
|
51
132
|
);
|
|
52
133
|
|
|
134
|
+
// Status bar demonstrates the new segmented writeText() — each shortcut label is
|
|
135
|
+
// drawn in a distinct style while the cursor flows across segments without a
|
|
136
|
+
// separate writeText() call per piece.
|
|
137
|
+
const statusBase = screen.registerStyle({ background: 24, foreground: 250 });
|
|
138
|
+
const shortcutId = screen.registerStyle({ background: 24, foreground: 220, bold: true });
|
|
139
|
+
const sepId = screen.registerStyle({ background: 24, foreground: 110 });
|
|
140
|
+
const dimId = screen.registerStyle({ background: 24, foreground: 244, dim: true });
|
|
141
|
+
const helpId = screen.registerStyle({ background: 24, foreground: 118, bold: true });
|
|
142
|
+
const statusBar = result.get('statusBar')!;
|
|
143
|
+
|
|
144
|
+
// Toggled by the '?' global shortcut to flip the status bar into "help mode".
|
|
145
|
+
let helpMode = false;
|
|
146
|
+
|
|
147
|
+
/** Repaints the status bar so the live width × height label tracks SIGWINCH. */
|
|
148
|
+
const redrawStatusBar = (): void => {
|
|
149
|
+
statusBar.clear();
|
|
150
|
+
if (helpMode) {
|
|
151
|
+
statusBar.writeText([
|
|
152
|
+
{ text: ' HELP ', style: helpId },
|
|
153
|
+
{ text: '│', style: sepId },
|
|
154
|
+
{ text: ' ' },
|
|
155
|
+
{ text: '?', style: shortcutId }, { text: ' toggle help ' },
|
|
156
|
+
{ text: 'Ctrl+R', style: shortcutId }, { text: ' random jiggle ' },
|
|
157
|
+
{ text: 'Ctrl+E', style: shortcutId }, { text: ' pause TUI ' },
|
|
158
|
+
{ text: 'v', style: shortcutId }, { text: ' hide charts ' },
|
|
159
|
+
{ text: 'q', style: shortcutId }, { text: ' quit ' },
|
|
160
|
+
], { style: statusBase });
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
statusBar.writeText([
|
|
164
|
+
{ text: ` ${width}×${height} ` },
|
|
165
|
+
{ text: '│', style: sepId },
|
|
166
|
+
{ text: ' ' },
|
|
167
|
+
{ text: 'Tab', style: shortcutId }, { text: ' focus ' },
|
|
168
|
+
{ text: '←/→', style: shortcutId }, { text: ' tabs ' },
|
|
169
|
+
{ text: 'Space', style: shortcutId }, { text: ' toggle ' },
|
|
170
|
+
{ text: 'v', style: shortcutId }, { text: ' charts ' },
|
|
171
|
+
{ text: 'Ctrl+E', style: shortcutId }, { text: ' pause ' },
|
|
172
|
+
{ text: '?', style: shortcutId }, { text: ' help ' },
|
|
173
|
+
{ text: 'q', style: shortcutId }, { text: ' quit ' },
|
|
174
|
+
{ text: ' · emoji 🚀 CJK 日本語 · ', style: dimId },
|
|
175
|
+
], { style: statusBase });
|
|
176
|
+
};
|
|
177
|
+
redrawStatusBar();
|
|
178
|
+
|
|
179
|
+
// P0-4 demo: '?' toggles the help line in the status bar (consumed, so it
|
|
180
|
+
// never reaches the focused control). Ctrl+R re-jiggles the chart data
|
|
181
|
+
// without waiting for the timer — shows that bindKey co-exists with the
|
|
182
|
+
// regular key dispatch path as long as handlers return true.
|
|
183
|
+
wm.bindKey('?', () => {
|
|
184
|
+
helpMode = !helpMode;
|
|
185
|
+
redrawStatusBar();
|
|
186
|
+
return true;
|
|
187
|
+
});
|
|
188
|
+
|
|
53
189
|
// ── Resource labels in monitorPanel ──────────────────────────────────────
|
|
54
190
|
const labelStyle = screen.registerStyle({ foreground: 245 });
|
|
55
191
|
const monitor = result.get('monitorPanel')!;
|
|
@@ -86,12 +222,47 @@ const main = async (): Promise<void> => {
|
|
|
86
222
|
tabs.writeText('MEM', { x: 1, y: 3, style: sparkLblStyle });
|
|
87
223
|
tabs.writeText('DSK', { x: 1, y: 4, style: sparkLblStyle });
|
|
88
224
|
|
|
89
|
-
// ── Events list (
|
|
90
|
-
const events = result.get('eventsList') as ListBox
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
225
|
+
// ── Events list (with custom per-row renderer: icon + dimmed timestamp + right-aligned count) ─
|
|
226
|
+
const events = result.get('eventsList') as ListBox<EventRow>;
|
|
227
|
+
|
|
228
|
+
// Register the accent styles used by the custom row renderer.
|
|
229
|
+
const iconOkStyle = screen.registerStyle({ foreground: 82, bold: true });
|
|
230
|
+
const iconWarnStyle = screen.registerStyle({ foreground: 214, bold: true });
|
|
231
|
+
const iconErrorStyle = screen.registerStyle({ foreground: 196, bold: true });
|
|
232
|
+
const tsStyle = screen.registerStyle({ foreground: 244, dim: true });
|
|
233
|
+
const countStyle = screen.registerStyle({ foreground: 220 });
|
|
234
|
+
|
|
235
|
+
const levelIcon = (lvl: EventLevel): { glyph: string; style: number } => {
|
|
236
|
+
if (lvl === 'ok') return { glyph: '✓', style: iconOkStyle };
|
|
237
|
+
if (lvl === 'warn') return { glyph: '⚠', style: iconWarnStyle };
|
|
238
|
+
return { glyph: '✖', style: iconErrorStyle };
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
events.setRenderItem((row): ListBoxRowSegments => {
|
|
242
|
+
const { glyph, style: iconSt } = levelIcon(row.level);
|
|
243
|
+
return [
|
|
244
|
+
{ text: `${glyph} `, align: 'left', style: iconSt },
|
|
245
|
+
{ text: `[${row.timestamp}] `, align: 'left', style: tsStyle },
|
|
246
|
+
{ text: row.message, align: 'left' },
|
|
247
|
+
{ text: `×${row.count}`, align: 'right', style: countStyle },
|
|
248
|
+
];
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
// A few entries embed double-width emoji (UTF-16 surrogate pairs) so the
|
|
252
|
+
// East-Asian-width-aware writeText() introduced in 0.17.0 is exercised
|
|
253
|
+
// at runtime — wide glyphs occupy two consecutive buffer cells with a ''
|
|
254
|
+
// continuation sentinel that Screen.render() skips.
|
|
255
|
+
const EVENT_TEMPLATES: Array<{ level: EventLevel; message: string }> = [
|
|
256
|
+
{ level: 'ok', message: 'user logged in' },
|
|
257
|
+
{ level: 'ok', message: 'cache warmed 🚀' },
|
|
258
|
+
{ level: 'ok', message: 'metric collected' },
|
|
259
|
+
{ level: 'ok', message: 'job completed 💾' },
|
|
260
|
+
{ level: 'warn', message: 'config reloaded' },
|
|
261
|
+
{ level: 'ok', message: 'heartbeat ok' },
|
|
262
|
+
{ level: 'ok', message: 'worker spawned' },
|
|
263
|
+
{ level: 'warn', message: 'session expired' },
|
|
264
|
+
{ level: 'ok', message: 'gc finished' },
|
|
265
|
+
{ level: 'error', message: 'index rebuild fail' },
|
|
95
266
|
];
|
|
96
267
|
|
|
97
268
|
// ── Live-updating handles ─────────────────────────────────────────────────
|
|
@@ -156,8 +327,14 @@ const main = async (): Promise<void> => {
|
|
|
156
327
|
headerSpinner.step();
|
|
157
328
|
|
|
158
329
|
// prepend a new event to the log; keep the list bounded
|
|
159
|
-
const
|
|
160
|
-
const
|
|
330
|
+
const tpl = EVENT_TEMPLATES[rand(EVENT_TEMPLATES.length)]!;
|
|
331
|
+
const entry: EventRow = {
|
|
332
|
+
timestamp: timestamp(),
|
|
333
|
+
level: tpl.level,
|
|
334
|
+
message: tpl.message,
|
|
335
|
+
count: 1 + rand(9),
|
|
336
|
+
};
|
|
337
|
+
const next = [entry, ...events.getItems()];
|
|
161
338
|
events.setItems(next.slice(0, 20));
|
|
162
339
|
|
|
163
340
|
screen.render();
|
|
@@ -165,6 +342,41 @@ const main = async (): Promise<void> => {
|
|
|
165
342
|
|
|
166
343
|
demoTimer = setInterval(tick, 1000);
|
|
167
344
|
|
|
345
|
+
// P0-4 demo (cont'd): Ctrl+R force-refreshes the charts out-of-band so the
|
|
346
|
+
// key handler ordering is easy to observe — the TextBox in the layout never
|
|
347
|
+
// receives a '\x12' control code.
|
|
348
|
+
wm.bindKey('ctrl+r', () => {
|
|
349
|
+
tick();
|
|
350
|
+
return true;
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
// P0-8 demo: 'v' toggles the visibility of the charts panel. Hidden children
|
|
354
|
+
// are fully skipped by render() (neither painted nor blitted), so the panel
|
|
355
|
+
// vanishes leaving the dialog background in its place — and reappears
|
|
356
|
+
// unchanged when shown again. The Tab focus cycle also skips focusables
|
|
357
|
+
// inside a hidden subtree.
|
|
358
|
+
const chartsPanel = result.get('chartsPanel') as Tabs;
|
|
359
|
+
wm.bindKey('v', () => {
|
|
360
|
+
chartsPanel.setVisible(!chartsPanel.isVisible());
|
|
361
|
+
screen.render();
|
|
362
|
+
return true;
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
// P0-5 demo: Ctrl+E pauses the WindowManager (drops the TUI), exits the
|
|
366
|
+
// alternate screen buffer, and spawns a short inline shell so you can see
|
|
367
|
+
// that the terminal is fully reusable for an external process. Pressing
|
|
368
|
+
// Enter at the prompt returns to the TUI — resume() re-enters the alt
|
|
369
|
+
// buffer, re-hides the cursor, re-enables mouse tracking, and re-renders
|
|
370
|
+
// the frame. The focus / dialog stack / key bindings stay intact across
|
|
371
|
+
// the cycle.
|
|
372
|
+
wm.bindKey('ctrl+e', () => {
|
|
373
|
+
wm.pause({ leaveAltScreen: true });
|
|
374
|
+
process.stdout.write('\n--- paused take4_console TUI. Press Enter to return ---\n');
|
|
375
|
+
spawnSync('bash', ['-c', 'read -r _'], { stdio: 'inherit' });
|
|
376
|
+
wm.resume();
|
|
377
|
+
return true;
|
|
378
|
+
});
|
|
379
|
+
|
|
168
380
|
wm.run();
|
|
169
381
|
};
|
|
170
382
|
|
package/src/index.mts
CHANGED
|
@@ -17,9 +17,10 @@ export { StyleRegistry } from './Screen/StyleRegistry.mjs';
|
|
|
17
17
|
export { WindowManager } from './Screen/WindowManager.mjs';
|
|
18
18
|
|
|
19
19
|
// ── Geometry ──────────────────────────────────────────────────────────────────
|
|
20
|
-
export { Pos }
|
|
21
|
-
export { Pct, pct }
|
|
22
|
-
export { Size
|
|
20
|
+
export { Pos } from './Screen/Pos.mjs';
|
|
21
|
+
export { Pct, pct } from './Screen/Pos.mjs';
|
|
22
|
+
export { Size, FlexDim, ContentDim,
|
|
23
|
+
flex, content } from './Screen/Size.mjs';
|
|
23
24
|
|
|
24
25
|
// ── Interactive controls ──────────────────────────────────────────────────────
|
|
25
26
|
export { Button } from './Screen/controls/Button.mjs';
|
|
@@ -42,6 +43,9 @@ export { Spinner } from './Screen/controls/Spinner.mjs';
|
|
|
42
43
|
// ── YAML layout builder ───────────────────────────────────────────────────────
|
|
43
44
|
export { InterfaceBuilder } from './Screen/InterfaceBuilder.mjs';
|
|
44
45
|
|
|
46
|
+
// ── Unicode text width helpers ────────────────────────────────────────────────
|
|
47
|
+
export { charWidth, stringWidth, setPuaWidth, getPuaWidth } from './Screen/textWidth.mjs';
|
|
48
|
+
|
|
45
49
|
// ── Built-in style name constants ─────────────────────────────────────────────
|
|
46
50
|
export {
|
|
47
51
|
BUILTIN_WINDOW_BG,
|
|
@@ -64,14 +68,25 @@ export type {
|
|
|
64
68
|
Cell,
|
|
65
69
|
CellAttributes,
|
|
66
70
|
TerminalSize,
|
|
71
|
+
ScreenOptions,
|
|
72
|
+
ScreenFrameStats,
|
|
67
73
|
AxisSpec,
|
|
68
74
|
DimSpec,
|
|
75
|
+
FlexBasis,
|
|
76
|
+
LayoutMode,
|
|
77
|
+
AlignItems,
|
|
78
|
+
JustifyContent,
|
|
79
|
+
Padding,
|
|
80
|
+
PaddingSpec,
|
|
69
81
|
|
|
70
82
|
// Window / border
|
|
71
83
|
BorderStyle,
|
|
84
|
+
BorderChars,
|
|
72
85
|
WindowBorder,
|
|
73
86
|
WindowProperties,
|
|
74
87
|
WriteTextOptions,
|
|
88
|
+
WriteTextSegment,
|
|
89
|
+
WriteTextInput,
|
|
75
90
|
|
|
76
91
|
// Control properties
|
|
77
92
|
ButtonProperties,
|
|
@@ -85,6 +100,9 @@ export type {
|
|
|
85
100
|
LineChartProperties,
|
|
86
101
|
BarChartProperties,
|
|
87
102
|
ListBoxProperties,
|
|
103
|
+
ListBoxRenderContext,
|
|
104
|
+
ListBoxRowSegment,
|
|
105
|
+
ListBoxRowSegments,
|
|
88
106
|
TabsProperties,
|
|
89
107
|
SparklineProperties,
|
|
90
108
|
SpinnerProperties,
|
|
@@ -102,4 +120,6 @@ export type {
|
|
|
102
120
|
YamlStyleDef,
|
|
103
121
|
YamlWindowDef,
|
|
104
122
|
YamlLayout,
|
|
123
|
+
CustomTypeContext,
|
|
124
|
+
CustomTypeFactory,
|
|
105
125
|
} from './Screen/types.mjs';
|
package/src/layout.yaml
CHANGED
|
@@ -10,6 +10,16 @@ windows:
|
|
|
10
10
|
spinnerStyle: braille
|
|
11
11
|
label: "LIVE"
|
|
12
12
|
chartColor: 231
|
|
13
|
+
# P0-10: custom control registered in demo.mts via
|
|
14
|
+
# InterfaceBuilder.registerType('badge', …). Fields under `props:` are
|
|
15
|
+
# forwarded to the factory untouched.
|
|
16
|
+
- id: buildBadge
|
|
17
|
+
type: badge
|
|
18
|
+
pos: { x: 18, y: 0 }
|
|
19
|
+
size: { width: 10, height: 1 }
|
|
20
|
+
props:
|
|
21
|
+
text: "P0-10 "
|
|
22
|
+
color: 220
|
|
13
23
|
|
|
14
24
|
- id: statusBar
|
|
15
25
|
pos: { preset: bottom }
|
|
@@ -35,7 +45,10 @@ windows:
|
|
|
35
45
|
pos: { x: 1, y: 2 }
|
|
36
46
|
size: { width: "46%", height: "43%" }
|
|
37
47
|
background: 235
|
|
38
|
-
|
|
48
|
+
# Demonstrates the per-character override: the corners are swapped for diamond glyphs
|
|
49
|
+
# while the rest of the 'single' style is kept untouched.
|
|
50
|
+
border: { top: true, right: true, bottom: true, left: true, style: single, color: 238,
|
|
51
|
+
chars: { topLeft: '◆', topRight: '◆' } }
|
|
39
52
|
content: "Personal info"
|
|
40
53
|
children:
|
|
41
54
|
- id: tbUsername
|
|
@@ -45,6 +58,9 @@ windows:
|
|
|
45
58
|
value: "jan_kowalski"
|
|
46
59
|
placeholder: "username"
|
|
47
60
|
focused: true
|
|
61
|
+
# P0-6: YAML-wired onSubmit — pressing Enter while this TextBox is
|
|
62
|
+
# focused fires `usernameSubmitted`, registered in demo.mts.
|
|
63
|
+
onSubmit: usernameSubmitted
|
|
48
64
|
- id: tbEmail
|
|
49
65
|
type: textbox
|
|
50
66
|
pos: { x: 0, y: 5 }
|
|
@@ -124,7 +140,8 @@ windows:
|
|
|
124
140
|
pos: { x: 1, y: "46%" }
|
|
125
141
|
size: { width: "46%", height: "43%" }
|
|
126
142
|
background: 235
|
|
127
|
-
|
|
143
|
+
# Demonstrates the new 'thick' (heavy) box-drawing style.
|
|
144
|
+
border: { top: true, right: true, bottom: true, left: true, style: thick, color: 38 }
|
|
128
145
|
content: "Resources"
|
|
129
146
|
children:
|
|
130
147
|
- id: pbCpu
|
|
@@ -204,33 +221,48 @@ windows:
|
|
|
204
221
|
pos: { x: "81%", y: "46%" }
|
|
205
222
|
size: { width: "17%", height: "43%" }
|
|
206
223
|
background: 235
|
|
207
|
-
|
|
224
|
+
# Demonstrates the new 'dashed' style.
|
|
225
|
+
border: { top: true, right: true, bottom: true, left: true, style: dashed, color: 33 }
|
|
208
226
|
content: "Events"
|
|
209
227
|
children:
|
|
210
228
|
- id: eventsList
|
|
211
229
|
type: listbox
|
|
212
230
|
pos: { x: 0, y: 1 }
|
|
213
231
|
size: { width: "100%", height: "85%" }
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
- "[12:04:33] cache warmed"
|
|
217
|
-
- "[12:05:01] metric collected"
|
|
218
|
-
- "[12:05:18] job completed"
|
|
232
|
+
# Items populated at runtime via ListBox.setItems() with structured EventRow objects;
|
|
233
|
+
# a demo.mts renderItem() draws icon + dimmed timestamp + right-aligned count.
|
|
219
234
|
|
|
220
235
|
# ── Buttons ───────────────────────────────────────────────────────────────
|
|
221
|
-
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
pos: { x:
|
|
230
|
-
size: {
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
236
|
+
# Bottom action bar — demonstrates P0-3 flex layout. The container uses
|
|
237
|
+
# `layout: row` with `gap: 1`; the middle `spacer` child has `size: flex`
|
|
238
|
+
# (grow=1 by default), so it soaks up leftover inner width between the
|
|
239
|
+
# left-aligned "Delete" and the right-aligned "Cancel" / " Save" group.
|
|
240
|
+
# alignItems: stretch pins children to the row height. This is the same
|
|
241
|
+
# visual outcome as the previous absolute positioning but authored
|
|
242
|
+
# declaratively, and it adapts automatically if the dialog is resized.
|
|
243
|
+
- id: buttonRow
|
|
244
|
+
pos: { x: 0, y: -4 }
|
|
245
|
+
size: { fillWidth: 3 }
|
|
246
|
+
layout: row
|
|
247
|
+
gap: 1
|
|
248
|
+
alignItems: stretch
|
|
249
|
+
children:
|
|
250
|
+
- id: btnDisabled
|
|
251
|
+
type: button
|
|
252
|
+
pos: flex
|
|
253
|
+
size: { width: 11, height: 3 }
|
|
254
|
+
label: "Delete"
|
|
255
|
+
disabled: true
|
|
256
|
+
- id: buttonSpacer
|
|
257
|
+
pos: flex
|
|
258
|
+
size: flex
|
|
259
|
+
- id: btnCancel
|
|
260
|
+
type: button
|
|
261
|
+
pos: flex
|
|
262
|
+
size: { width: 11, height: 3 }
|
|
263
|
+
label: "Cancel"
|
|
264
|
+
- id: btnSave
|
|
265
|
+
type: button
|
|
266
|
+
pos: flex
|
|
267
|
+
size: { width: 11, height: 3 }
|
|
268
|
+
label: " Save"
|