take4-console 0.25.0 → 0.31.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 +253 -0
- package/README.md +3 -2
- package/dist/Screen/ErrorHolder.d.mts +10 -0
- package/dist/Screen/ErrorHolder.d.mts.map +1 -0
- package/dist/Screen/ErrorHolder.mjs +14 -0
- package/dist/Screen/ErrorHolder.mjs.map +1 -0
- package/dist/Screen/InterfaceBuilder.d.mts.map +1 -1
- package/dist/Screen/InterfaceBuilder.mjs +7 -0
- package/dist/Screen/InterfaceBuilder.mjs.map +1 -1
- package/dist/Screen/Screen.d.mts +90 -1
- package/dist/Screen/Screen.d.mts.map +1 -1
- package/dist/Screen/Screen.mjs +300 -1
- package/dist/Screen/Screen.mjs.map +1 -1
- package/dist/Screen/StyleRegistry.d.mts.map +1 -1
- package/dist/Screen/StyleRegistry.mjs +3 -1
- package/dist/Screen/StyleRegistry.mjs.map +1 -1
- package/dist/Screen/VirtualCursor.d.mts +57 -0
- package/dist/Screen/VirtualCursor.d.mts.map +1 -0
- package/dist/Screen/VirtualCursor.mjs +148 -0
- package/dist/Screen/VirtualCursor.mjs.map +1 -0
- package/dist/Screen/Window.d.mts +116 -6
- package/dist/Screen/Window.d.mts.map +1 -1
- package/dist/Screen/Window.mjs +359 -34
- package/dist/Screen/Window.mjs.map +1 -1
- package/dist/Screen/WindowManager.d.mts +78 -2
- package/dist/Screen/WindowManager.d.mts.map +1 -1
- package/dist/Screen/WindowManager.mjs +197 -3
- package/dist/Screen/WindowManager.mjs.map +1 -1
- package/dist/Screen/controls/BarChart.d.mts.map +1 -1
- package/dist/Screen/controls/BarChart.mjs +3 -0
- package/dist/Screen/controls/BarChart.mjs.map +1 -1
- package/dist/Screen/controls/Checkbox.d.mts.map +1 -1
- package/dist/Screen/controls/Checkbox.mjs +4 -0
- package/dist/Screen/controls/Checkbox.mjs.map +1 -1
- package/dist/Screen/controls/LineChart.d.mts.map +1 -1
- package/dist/Screen/controls/LineChart.mjs +3 -0
- package/dist/Screen/controls/LineChart.mjs.map +1 -1
- package/dist/Screen/controls/ListBox.d.mts.map +1 -1
- package/dist/Screen/controls/ListBox.mjs +9 -0
- package/dist/Screen/controls/ListBox.mjs.map +1 -1
- package/dist/Screen/controls/ProgressBar.d.mts.map +1 -1
- package/dist/Screen/controls/ProgressBar.mjs +2 -0
- package/dist/Screen/controls/ProgressBar.mjs.map +1 -1
- package/dist/Screen/controls/ProgressBarV.d.mts.map +1 -1
- package/dist/Screen/controls/ProgressBarV.mjs +2 -0
- package/dist/Screen/controls/ProgressBarV.mjs.map +1 -1
- package/dist/Screen/controls/Radio.d.mts.map +1 -1
- package/dist/Screen/controls/Radio.mjs +7 -1
- package/dist/Screen/controls/Radio.mjs.map +1 -1
- package/dist/Screen/controls/Sparkline.d.mts.map +1 -1
- package/dist/Screen/controls/Sparkline.mjs +3 -0
- package/dist/Screen/controls/Sparkline.mjs.map +1 -1
- package/dist/Screen/controls/Spinner.d.mts.map +1 -1
- package/dist/Screen/controls/Spinner.mjs +8 -0
- package/dist/Screen/controls/Spinner.mjs.map +1 -1
- package/dist/Screen/controls/StatusLED.d.mts.map +1 -1
- package/dist/Screen/controls/StatusLED.mjs +3 -0
- package/dist/Screen/controls/StatusLED.mjs.map +1 -1
- package/dist/Screen/controls/Tabs.d.mts.map +1 -1
- package/dist/Screen/controls/Tabs.mjs +2 -0
- package/dist/Screen/controls/Tabs.mjs.map +1 -1
- package/dist/Screen/controls/TextArea.d.mts +68 -2
- package/dist/Screen/controls/TextArea.d.mts.map +1 -1
- package/dist/Screen/controls/TextArea.mjs +291 -46
- package/dist/Screen/controls/TextArea.mjs.map +1 -1
- package/dist/Screen/controls/TextBox.d.mts +52 -5
- package/dist/Screen/controls/TextBox.d.mts.map +1 -1
- package/dist/Screen/controls/TextBox.mjs +192 -10
- package/dist/Screen/controls/TextBox.mjs.map +1 -1
- package/dist/Screen/controls/Toast.d.mts +72 -0
- package/dist/Screen/controls/Toast.d.mts.map +1 -0
- package/dist/Screen/controls/Toast.mjs +112 -0
- package/dist/Screen/controls/Toast.mjs.map +1 -0
- package/dist/Screen/types.d.mts +169 -0
- package/dist/Screen/types.d.mts.map +1 -1
- package/dist/Screen/types.mjs +8 -0
- package/dist/Screen/types.mjs.map +1 -1
- package/dist/index.d.mts +4 -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 +1 -1
- package/src/Screen/ErrorHolder.mts +22 -0
- package/src/Screen/InterfaceBuilder.mts +12 -5
- package/src/Screen/Screen.mts +313 -2
- package/src/Screen/StyleRegistry.mts +4 -0
- package/src/Screen/VirtualCursor.mts +175 -0
- package/src/Screen/Window.mts +352 -34
- package/src/Screen/WindowManager.mts +203 -3
- package/src/Screen/controls/BarChart.mts +3 -0
- package/src/Screen/controls/Checkbox.mts +3 -0
- package/src/Screen/controls/LineChart.mts +3 -0
- package/src/Screen/controls/ListBox.mts +8 -0
- package/src/Screen/controls/ProgressBar.mts +2 -0
- package/src/Screen/controls/ProgressBarV.mts +2 -0
- package/src/Screen/controls/Radio.mts +6 -1
- package/src/Screen/controls/Sparkline.mts +3 -0
- package/src/Screen/controls/Spinner.mts +6 -0
- package/src/Screen/controls/StatusLED.mts +2 -0
- package/src/Screen/controls/Tabs.mts +2 -0
- package/src/Screen/controls/TextArea.mts +290 -41
- package/src/Screen/controls/TextBox.mts +193 -10
- package/src/Screen/controls/Toast.mts +138 -0
- package/src/Screen/types.mts +167 -0
- package/src/demo.mts +131 -0
- package/src/index.mts +13 -0
- package/src/layout.yaml +16 -0
package/src/Screen/types.mts
CHANGED
|
@@ -28,6 +28,41 @@ export const BUILTIN_TEXT_PLACEHOLDER = 'builtin:text-placeholder';
|
|
|
28
28
|
export const BUILTIN_TEXT_CHECKED = 'builtin:text-checked';
|
|
29
29
|
/** Name of the cursor highlight style (inverse) used by text input controls. */
|
|
30
30
|
export const BUILTIN_CURSOR = 'builtin:cursor';
|
|
31
|
+
/** Name of the selection highlight style merged over selected cells in
|
|
32
|
+
* `TextBox` / `TextArea` (backlog P1-20). */
|
|
33
|
+
export const BUILTIN_TEXT_SELECTION = 'builtin:text-selection';
|
|
34
|
+
/** Name of the default background + text style used by `Screen.toast()`
|
|
35
|
+
* overlay windows (backlog P1-27). Consumers can override it via
|
|
36
|
+
* `Screen.setBuiltinStyle(BUILTIN_TOAST, …)` to recolour every toast at
|
|
37
|
+
* once without threading a per-call `style` option. */
|
|
38
|
+
export const BUILTIN_TOAST = 'builtin:toast';
|
|
39
|
+
|
|
40
|
+
// ── Virtual cursor ────────────────────────────────────────────────────────────
|
|
41
|
+
|
|
42
|
+
/** Blink configuration for a `VirtualCursor`.
|
|
43
|
+
* - `off` — cursor is never drawn (hide software cursor entirely).
|
|
44
|
+
* - `steady` — cursor is always drawn (no blink).
|
|
45
|
+
* - `slow` — 600 ms on / 600 ms off.
|
|
46
|
+
* - `fast` — 250 ms on / 250 ms off.
|
|
47
|
+
* - `irregular` — each on/off phase picks a randomised duration so the
|
|
48
|
+
* cursor pulses in a non-metronomic "alive" rhythm.
|
|
49
|
+
* - `custom` — caller-supplied on / off durations in milliseconds. */
|
|
50
|
+
export type CursorBlink =
|
|
51
|
+
| { mode: 'off' }
|
|
52
|
+
| { mode: 'steady' }
|
|
53
|
+
| { mode: 'slow' }
|
|
54
|
+
| { mode: 'fast' }
|
|
55
|
+
| { mode: 'irregular' }
|
|
56
|
+
| { mode: 'custom'; onMs: number; offMs: number };
|
|
57
|
+
|
|
58
|
+
/** Construction options for `VirtualCursor`. Both fields are optional;
|
|
59
|
+
* defaults: `symbol = '▎'`, `blink = { mode: 'steady' }`. */
|
|
60
|
+
export interface VirtualCursorOptions {
|
|
61
|
+
/** Glyph rendered for the cursor when visible. Default: `'▎'`. */
|
|
62
|
+
symbol?: string;
|
|
63
|
+
/** Blink schedule. Default: `{ mode: 'steady' }` (no blink). */
|
|
64
|
+
blink?: CursorBlink;
|
|
65
|
+
}
|
|
31
66
|
|
|
32
67
|
/** Integer handle returned by StyleRegistry.register(). ID 0 always means no style (empty {}). */
|
|
33
68
|
export type StyleId = number;
|
|
@@ -75,12 +110,39 @@ export interface ScreenOptions {
|
|
|
75
110
|
* for inspection; full enforcement (frame coalescing) lands with backlog
|
|
76
111
|
* item P2-47. Default: undefined (uncapped). */
|
|
77
112
|
targetFps?: number;
|
|
113
|
+
/** Enables damage-region tracking: `Screen.render()` collects dirty rects
|
|
114
|
+
* propagated bottom-up from each mutated window and emits ANSI sequences
|
|
115
|
+
* only for the changed cells. A frame with no dirty rects is skipped
|
|
116
|
+
* entirely (no stdout traffic). The first frame and every frame after
|
|
117
|
+
* `resize()` / `invalidate()` fall back to a full repaint. Default:
|
|
118
|
+
* `true`. Set to `false` for the pre-0.31 behaviour (always emit the
|
|
119
|
+
* full buffer), useful for debugging or terminals that mis-handle
|
|
120
|
+
* partial cursor jumps. */
|
|
121
|
+
damageTracking?: boolean;
|
|
78
122
|
}
|
|
79
123
|
|
|
80
124
|
/** Statistics emitted by the Screen 'frame' event after each render() call. */
|
|
81
125
|
export interface ScreenFrameStats {
|
|
82
126
|
/** Wall-clock duration of the render() call in milliseconds. */
|
|
83
127
|
ms: number;
|
|
128
|
+
/** Number of cells emitted to stdout during this frame (0 when the frame
|
|
129
|
+
* was skipped because nothing was dirty). Populated only when damage
|
|
130
|
+
* tracking is enabled; undefined otherwise. */
|
|
131
|
+
cellsEmitted?: number;
|
|
132
|
+
/** True when the emit path was a full-screen repaint (first frame,
|
|
133
|
+
* post-resize, or damage tracking disabled); false when only dirty runs
|
|
134
|
+
* were emitted. Undefined when the frame was skipped entirely. */
|
|
135
|
+
fullRepaint?: boolean;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** Rectangle expressed in the owning Window's local coordinate space (for
|
|
139
|
+
* dirty regions on a Window) or in Screen coordinates (when collected by
|
|
140
|
+
* `Screen.render()` during damage tracking). */
|
|
141
|
+
export interface DirtyRect {
|
|
142
|
+
x: number;
|
|
143
|
+
y: number;
|
|
144
|
+
w: number;
|
|
145
|
+
h: number;
|
|
84
146
|
}
|
|
85
147
|
|
|
86
148
|
/** Box-drawing character style for window borders.
|
|
@@ -167,6 +229,16 @@ export interface WindowProperties {
|
|
|
167
229
|
* padded inner area, and `getInnerSize()` / `getInnerOffset()` reflect the
|
|
168
230
|
* padding as well as the border. Default: 0 on every side. */
|
|
169
231
|
padding?: PaddingSpec;
|
|
232
|
+
/** Outer spacing reserved around the window inside its parent's layout.
|
|
233
|
+
* - In `row` / `column` flex layouts margin stacks with `gap`: each child
|
|
234
|
+
* claims `intrinsic + marginMain` cells on the main axis, and its cross
|
|
235
|
+
* extent is reduced by the cross margin.
|
|
236
|
+
* - In `grid` layout the child fits inside `cell − margin` and is offset by
|
|
237
|
+
* `marginTop` / `marginLeft` within the cell.
|
|
238
|
+
* - In `absolute` layout margin shifts the child's resolved position by
|
|
239
|
+
* `(marginLeft, marginTop)` without changing its size.
|
|
240
|
+
* Default: 0 on every side. */
|
|
241
|
+
margin?: MarginSpec;
|
|
170
242
|
/** Number of columns for `layout: 'grid'`. Children are placed row-major,
|
|
171
243
|
* so `gridColumns` implicitly determines the number of rows from the child
|
|
172
244
|
* count. Default: 1. */
|
|
@@ -177,6 +249,21 @@ export interface WindowProperties {
|
|
|
177
249
|
/** Main-axis distribution of leftover space for `layout: 'row'` / `'column'`
|
|
178
250
|
* when no child consumes it via flex-grow. Default: 'start'. */
|
|
179
251
|
justifyContent?: JustifyContent;
|
|
252
|
+
/** Optional string identifier used by `WindowManager.focusById` and for
|
|
253
|
+
* diagnostics. InterfaceBuilder copies the YAML `id:` into this field so
|
|
254
|
+
* the runtime can cross-reference programmatic focus with the builder map. */
|
|
255
|
+
id?: string;
|
|
256
|
+
/** Stacking order among siblings — windows with a higher `zIndex` render
|
|
257
|
+
* on top of windows with a lower value. Ties are broken by `addChild`
|
|
258
|
+
* insertion order, so the pre-0.26 behaviour (everything at `zIndex: 0`)
|
|
259
|
+
* is preserved for callers that don't opt in. Default: 0. */
|
|
260
|
+
zIndex?: number;
|
|
261
|
+
/** Fires once when this window transitions from unfocused to focused, via
|
|
262
|
+
* any code path (WindowManager, explicit `setFocused(true)`, focus
|
|
263
|
+
* inheritance on dialog open). Not fired when the state does not change. */
|
|
264
|
+
onFocus?: () => void;
|
|
265
|
+
/** Fires once when this window transitions from focused to unfocused. */
|
|
266
|
+
onBlur?: () => void;
|
|
180
267
|
}
|
|
181
268
|
|
|
182
269
|
/** Internal per-axis position spec used by Pos.
|
|
@@ -240,6 +327,20 @@ export interface Padding {
|
|
|
240
327
|
* a partial per-side record (missing sides default to 0). */
|
|
241
328
|
export type PaddingSpec = number | [number, number] | Partial<Padding>;
|
|
242
329
|
|
|
330
|
+
/** Resolved per-side margin values (in cells). Margin is the outer counterpart
|
|
331
|
+
* of padding — it pushes the window away from its parent's inner edges /
|
|
332
|
+
* siblings without reserving space inside the window itself. */
|
|
333
|
+
export interface Margin {
|
|
334
|
+
top: number;
|
|
335
|
+
right: number;
|
|
336
|
+
bottom: number;
|
|
337
|
+
left: number;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/** User-supplied margin: a uniform number, a [vertical, horizontal] tuple, or
|
|
341
|
+
* a partial per-side record (missing sides default to 0). Mirrors `PaddingSpec`. */
|
|
342
|
+
export type MarginSpec = number | [number, number] | Partial<Margin>;
|
|
343
|
+
|
|
243
344
|
/** Options for Window.writeText() – position defaults to (0, 0). */
|
|
244
345
|
export interface WriteTextOptions {
|
|
245
346
|
/** Column to start writing at. Default: 0. */
|
|
@@ -298,6 +399,15 @@ export interface TextBoxProperties {
|
|
|
298
399
|
* `true` to mark the key as handled — the default behaviour (inserting,
|
|
299
400
|
* moving cursor, deleting, …) is then skipped. */
|
|
300
401
|
onKeyDown?: (key: string) => boolean | void;
|
|
402
|
+
/** Virtual-cursor glyph. Overrides the default inverse-block look. When
|
|
403
|
+
* set, the character under the cursor is replaced by this symbol (unless
|
|
404
|
+
* the symbol is a zero-width string, in which case the underlying
|
|
405
|
+
* character stays). Default: undefined (inverse block). */
|
|
406
|
+
cursorSymbol?: string;
|
|
407
|
+
/** Blink schedule for the virtual cursor. Default: `{ mode: 'steady' }`
|
|
408
|
+
* (no blink). Requires `WindowManager.enableCursorBlink()` for timed
|
|
409
|
+
* modes to actually animate. */
|
|
410
|
+
cursorBlink?: CursorBlink;
|
|
301
411
|
}
|
|
302
412
|
|
|
303
413
|
/** Control-specific properties for the TextArea control. */
|
|
@@ -324,6 +434,10 @@ export interface TextAreaProperties {
|
|
|
324
434
|
/** When true, Ctrl+D deletes the character to the right of the cursor
|
|
325
435
|
* (or joins with the next line at end of line). Default: false. */
|
|
326
436
|
ctrlDDeletesForward?: boolean;
|
|
437
|
+
/** Virtual-cursor glyph. Overrides the default inverse-block look. */
|
|
438
|
+
cursorSymbol?: string;
|
|
439
|
+
/** Blink schedule for the virtual cursor. Default: `{ mode: 'steady' }`. */
|
|
440
|
+
cursorBlink?: CursorBlink;
|
|
327
441
|
}
|
|
328
442
|
|
|
329
443
|
/** Control-specific properties for the Checkbox control. */
|
|
@@ -465,6 +579,43 @@ export interface SpinnerProperties {
|
|
|
465
579
|
color?: number;
|
|
466
580
|
}
|
|
467
581
|
|
|
582
|
+
/** Corner (or top/bottom centre) where `Screen.toast()` anchors its overlay
|
|
583
|
+
* windows. Toasts launched at the same position stack vertically in the
|
|
584
|
+
* order they were created; a dismissal shifts the remaining stack towards
|
|
585
|
+
* the anchor edge so the UI stays compact. */
|
|
586
|
+
export type ToastPosition =
|
|
587
|
+
| 'top-left' | 'top-center' | 'top-right'
|
|
588
|
+
| 'bottom-left' | 'bottom-center' | 'bottom-right';
|
|
589
|
+
|
|
590
|
+
/** Options accepted by `Screen.toast()`. All fields are optional; sensible
|
|
591
|
+
* defaults make `screen.toast('Saved!')` a single-call notification. */
|
|
592
|
+
export interface ToastOptions {
|
|
593
|
+
/** Auto-dismiss delay in milliseconds. Pass `0` to keep the toast sticky
|
|
594
|
+
* until `toast.dismiss()` is called manually. Default: `2000`. */
|
|
595
|
+
duration?: number;
|
|
596
|
+
/** Pre-registered style ID used as both the window background and the
|
|
597
|
+
* text style. Default: `BUILTIN_TOAST` (falls back to a high-contrast
|
|
598
|
+
* style registered on first use). */
|
|
599
|
+
style?: StyleId;
|
|
600
|
+
/** Corner (or top/bottom centre) the toast anchors to. Default:
|
|
601
|
+
* `'top-right'`. */
|
|
602
|
+
position?: ToastPosition;
|
|
603
|
+
/** Border configuration. Pass `false` for a borderless toast. Default:
|
|
604
|
+
* a rounded single-line border on all four sides. */
|
|
605
|
+
border?: WindowBorder | boolean;
|
|
606
|
+
/** Stacking order among Screen children. Default: `10_000` so toasts
|
|
607
|
+
* draw on top of every regular window without callers having to raise
|
|
608
|
+
* other `zIndex` values by hand. */
|
|
609
|
+
zIndex?: number;
|
|
610
|
+
/** Override the automatic text-based width (in cells, not counting the
|
|
611
|
+
* border). When the text is wider than this value it overflows
|
|
612
|
+
* normally — toasts never wrap. Default: `undefined` (auto-size). */
|
|
613
|
+
width?: number;
|
|
614
|
+
/** Fires once after the toast has been removed from the Screen
|
|
615
|
+
* (auto-dismiss or manual). */
|
|
616
|
+
onDismiss?: () => void;
|
|
617
|
+
}
|
|
618
|
+
|
|
468
619
|
/** Control-specific properties for the BarChart control. */
|
|
469
620
|
export interface BarChartProperties {
|
|
470
621
|
/** Data values for each bar. Default: []. */
|
|
@@ -575,6 +726,10 @@ export interface YamlWindowDef {
|
|
|
575
726
|
insertTabAsSpaces?: number;
|
|
576
727
|
/** TextArea: when true, Ctrl+D deletes the character to the right of the cursor. */
|
|
577
728
|
ctrlDDeletesForward?: boolean;
|
|
729
|
+
/** TextBox / TextArea: virtual-cursor glyph (overrides inverse-block look). */
|
|
730
|
+
cursorSymbol?: string;
|
|
731
|
+
/** TextBox / TextArea: virtual-cursor blink schedule. */
|
|
732
|
+
cursorBlink?: CursorBlink;
|
|
578
733
|
/** LED state ('ok' | 'warn' | 'error' | 'off') — used by statusled. */
|
|
579
734
|
state?: 'ok' | 'warn' | 'error' | 'off';
|
|
580
735
|
/** Whether to show a percentage label over a progress bar. Default: true. */
|
|
@@ -620,6 +775,9 @@ export interface YamlWindowDef {
|
|
|
620
775
|
/** Padding inside the border: uniform number, [vertical, horizontal] tuple,
|
|
621
776
|
* or a partial per-side record. */
|
|
622
777
|
padding?: number | [number, number] | { top?: number; right?: number; bottom?: number; left?: number };
|
|
778
|
+
/** Outer spacing reserved around this window inside its parent's layout.
|
|
779
|
+
* Same shape as `padding`. */
|
|
780
|
+
margin?: number | [number, number] | { top?: number; right?: number; bottom?: number; left?: number };
|
|
623
781
|
/** Number of columns for `layout: grid`. */
|
|
624
782
|
gridColumns?: number;
|
|
625
783
|
/** Cross-axis alignment for `layout: row|column`. */
|
|
@@ -629,6 +787,9 @@ export interface YamlWindowDef {
|
|
|
629
787
|
/** Free-form property bag for user-registered custom types. Built-in types
|
|
630
788
|
* ignore this field; custom factories read it via `node.props`. */
|
|
631
789
|
props?: Record<string, unknown>;
|
|
790
|
+
/** Stacking order among siblings. Higher values render on top; ties keep
|
|
791
|
+
* YAML declaration order. Default: 0. */
|
|
792
|
+
zIndex?: number;
|
|
632
793
|
}
|
|
633
794
|
|
|
634
795
|
/** Context passed to factories registered via `InterfaceBuilder.registerType`.
|
|
@@ -718,4 +879,10 @@ export interface WindowManagerOptions {
|
|
|
718
879
|
onMouse?: (event: TerminalMouseEvent) => void;
|
|
719
880
|
/** Enable mouse click tracking (SGR protocol). Default: false. */
|
|
720
881
|
mouse?: boolean;
|
|
882
|
+
/** Fires when a descendant Window throws during its `render()` call (or its
|
|
883
|
+
* blit onto the parent). The offending subtree is replaced in-place with a
|
|
884
|
+
* single-line error placeholder so the rest of the frame still paints;
|
|
885
|
+
* the handler is invoked once per error with the thrown value and the
|
|
886
|
+
* Window that produced it. Default: no-op. */
|
|
887
|
+
onError?: (err: unknown, control: Window) => void;
|
|
721
888
|
}
|
package/src/demo.mts
CHANGED
|
@@ -13,7 +13,10 @@ import { Spinner } from './Screen/controls/Spinner.mjs';
|
|
|
13
13
|
import { Sparkline } from './Screen/controls/Sparkline.mjs';
|
|
14
14
|
import { ListBox } from './Screen/controls/ListBox.mjs';
|
|
15
15
|
import { Tabs } from './Screen/controls/Tabs.mjs';
|
|
16
|
+
import { TextBox } from './Screen/controls/TextBox.mjs';
|
|
16
17
|
import { Window } from './Screen/Window.mjs';
|
|
18
|
+
import { Pos } from './Screen/Pos.mjs';
|
|
19
|
+
import { Size } from './Screen/Size.mjs';
|
|
17
20
|
import type { ListBoxRowSegments, WindowProperties, StyleId } from './Screen/types.mjs';
|
|
18
21
|
|
|
19
22
|
/** Custom Window subclass registered with InterfaceBuilder via
|
|
@@ -73,6 +76,18 @@ const main = async (): Promise<void> => {
|
|
|
73
76
|
process.exit(0);
|
|
74
77
|
},
|
|
75
78
|
mouse: true,
|
|
79
|
+
// P1-23 demo: whenever a descendant Window.render() raises, append a
|
|
80
|
+
// red row to the events list instead of tearing down the TUI.
|
|
81
|
+
onError: (err, control) => {
|
|
82
|
+
const list = result.get('eventsList') as ListBox<EventRow>;
|
|
83
|
+
const entry: EventRow = {
|
|
84
|
+
timestamp: timestamp(),
|
|
85
|
+
level: 'error',
|
|
86
|
+
message: `render fail (${control.getId() ?? 'unnamed'}): ${err instanceof Error ? err.message : String(err)}`,
|
|
87
|
+
count: 1,
|
|
88
|
+
};
|
|
89
|
+
list.setItems([entry, ...list.getItems()].slice(0, 20));
|
|
90
|
+
},
|
|
76
91
|
});
|
|
77
92
|
|
|
78
93
|
// 0.19.0: SIGWINCH autoresize. The Screen reflows percentage-based children
|
|
@@ -369,6 +384,55 @@ const main = async (): Promise<void> => {
|
|
|
369
384
|
// buffer, re-hides the cursor, re-enables mouse tracking, and re-renders
|
|
370
385
|
// the frame. The focus / dialog stack / key bindings stay intact across
|
|
371
386
|
// the cycle.
|
|
387
|
+
// P1-18 demo: Ctrl+G jumps focus to the Save button via its YAML id,
|
|
388
|
+
// without the user having to Tab through every field in between.
|
|
389
|
+
wm.bindKey('ctrl+g', () => {
|
|
390
|
+
wm.focusById('btnSave');
|
|
391
|
+
screen.render();
|
|
392
|
+
return true;
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
// P1-22 demo: observe focus transitions on btnSave without subclassing —
|
|
396
|
+
// the hooks append a short trail to the events list so the user can see
|
|
397
|
+
// exactly when setFocus fires them.
|
|
398
|
+
const btnSave = result.get('btnSave')!;
|
|
399
|
+
btnSave.setOnFocus(() => {
|
|
400
|
+
const list = result.get('eventsList') as ListBox<EventRow>;
|
|
401
|
+
const entry: EventRow = { timestamp: timestamp(), level: 'ok', message: 'btnSave → focus', count: 1 };
|
|
402
|
+
list.setItems([entry, ...list.getItems()].slice(0, 20));
|
|
403
|
+
});
|
|
404
|
+
btnSave.setOnBlur(() => {
|
|
405
|
+
const list = result.get('eventsList') as ListBox<EventRow>;
|
|
406
|
+
const entry: EventRow = { timestamp: timestamp(), level: 'warn', message: 'btnSave → blur', count: 1 };
|
|
407
|
+
list.setItems([entry, ...list.getItems()].slice(0, 20));
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
// P1-27 demo: Ctrl+T pops a short-lived top-right toast so the overlay
|
|
411
|
+
// subsystem is visible at runtime. Repeated presses stack vertically and
|
|
412
|
+
// auto-dismiss in the same order. Ctrl+Y fires a sticky bottom-right toast
|
|
413
|
+
// that can only be cleared via its own dismiss button (`Enter` on focus)
|
|
414
|
+
// — here we close it on the next press to keep the demo self-contained.
|
|
415
|
+
let toastCounter = 0;
|
|
416
|
+
let stickyToast: ReturnType<typeof screen.toast> | null = null;
|
|
417
|
+
wm.bindKey('ctrl+t', () => {
|
|
418
|
+
toastCounter += 1;
|
|
419
|
+
screen.toast(` Saved #${toastCounter} at ${timestamp()} `, { duration: 2500 });
|
|
420
|
+
return true;
|
|
421
|
+
});
|
|
422
|
+
wm.bindKey('ctrl+y', () => {
|
|
423
|
+
if (stickyToast) {
|
|
424
|
+
stickyToast.dismiss();
|
|
425
|
+
stickyToast = null;
|
|
426
|
+
} else {
|
|
427
|
+
stickyToast = screen.toast(' Sticky: press Ctrl+Y again to dismiss ', {
|
|
428
|
+
position: 'bottom-right',
|
|
429
|
+
duration: 0,
|
|
430
|
+
onDismiss: () => { stickyToast = null; },
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
return true;
|
|
434
|
+
});
|
|
435
|
+
|
|
372
436
|
wm.bindKey('ctrl+e', () => {
|
|
373
437
|
wm.pause({ leaveAltScreen: true });
|
|
374
438
|
process.stdout.write('\n--- paused take4_console TUI. Press Enter to return ---\n');
|
|
@@ -377,6 +441,73 @@ const main = async (): Promise<void> => {
|
|
|
377
441
|
return true;
|
|
378
442
|
});
|
|
379
443
|
|
|
444
|
+
// P2-59 demo: Ctrl+D toggles damage tracking at runtime and posts a
|
|
445
|
+
// sticky toast with the new state so the effect is visible. When
|
|
446
|
+
// tracking is on, the 'frame' event reports only the dirty cells each
|
|
447
|
+
// frame (watch the toast subtitle); when it's off, every frame is a
|
|
448
|
+
// full repaint as before.
|
|
449
|
+
wm.bindKey('ctrl+d', () => {
|
|
450
|
+
const next = !screen.isDamageTrackingEnabled();
|
|
451
|
+
screen.setDamageTracking(next);
|
|
452
|
+
screen.toast(
|
|
453
|
+
next ? ' Damage tracking ON — only dirty cells are emitted '
|
|
454
|
+
: ' Damage tracking OFF — every frame is a full repaint ',
|
|
455
|
+
{ position: 'top-center', duration: 2500 },
|
|
456
|
+
);
|
|
457
|
+
return true;
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
// Virtual cursor: animated caret in TextBox/TextArea. The `tbEmail`
|
|
461
|
+
// TextBox opts into slow blink via layout.yaml; here we flip the
|
|
462
|
+
// `tbUsername` TextBox into the `irregular` mode so the two
|
|
463
|
+
// side-by-side inputs show off two different rhythms. The
|
|
464
|
+
// `enableCursorBlink()` call boots the periodic rerender that makes
|
|
465
|
+
// the blink phases actually reach the terminal.
|
|
466
|
+
const tbUsername = result.get('tbUsername') as TextBox | undefined;
|
|
467
|
+
if (tbUsername) {
|
|
468
|
+
tbUsername.getVirtualCursor().setSymbol('▏');
|
|
469
|
+
tbUsername.getVirtualCursor().setBlink({ mode: 'irregular' });
|
|
470
|
+
// P1-20 showcase: pre-select the initial value so the merged
|
|
471
|
+
// selection style is visible on boot. Shift+Left/Right, Ctrl+A,
|
|
472
|
+
// and plain arrows collapse it the moment the user interacts.
|
|
473
|
+
const text = tbUsername.getValue();
|
|
474
|
+
if (text.length > 0) tbUsername.setSelection(0, Math.min(3, text.length));
|
|
475
|
+
}
|
|
476
|
+
wm.enableCursorBlink(80);
|
|
477
|
+
|
|
478
|
+
// P2-59 showcase: five independent spinners with different styles,
|
|
479
|
+
// cadences, and positions, each driven by its own `setInterval`.
|
|
480
|
+
// With damage tracking ON, every tick emits only that spinner's 1–2
|
|
481
|
+
// cells (check the 'frame' event `cellsEmitted`); with tracking OFF
|
|
482
|
+
// (Ctrl+D) each tick repaints the whole screen. Placed along the
|
|
483
|
+
// bottom edge so they don't overlap existing widgets.
|
|
484
|
+
const spinnerStyles: Array<'braille' | 'dots' | 'line' | 'circle' | 'arrow'> =
|
|
485
|
+
['braille', 'dots', 'line', 'circle', 'arrow'];
|
|
486
|
+
const spinnerCadences = [120, 180, 240, 320, 420];
|
|
487
|
+
const spinnerTimers: NodeJS.Timeout[] = [];
|
|
488
|
+
const { width: screenW, height: screenH } = screen.getSize();
|
|
489
|
+
for (let i = 0; i < spinnerStyles.length; i++) {
|
|
490
|
+
const sp = new Spinner(
|
|
491
|
+
{ pos: new Pos(screenW - 12 - i * 3, screenH - 2), size: new Size(2, 1) },
|
|
492
|
+
{ style: spinnerStyles[i], color: 75 + i * 10 },
|
|
493
|
+
);
|
|
494
|
+
screen.addChild(sp);
|
|
495
|
+
const timer = setInterval(() => {
|
|
496
|
+
sp.step();
|
|
497
|
+
screen.render();
|
|
498
|
+
}, spinnerCadences[i]!);
|
|
499
|
+
if (typeof timer === 'object' && timer !== null && 'unref' in timer
|
|
500
|
+
&& typeof timer.unref === 'function') timer.unref();
|
|
501
|
+
spinnerTimers.push(timer);
|
|
502
|
+
}
|
|
503
|
+
// Ensure the spinner timers are torn down alongside the main demo
|
|
504
|
+
// timer when the user quits (the existing onExit handler already
|
|
505
|
+
// clears `demoTimer`, so extend it with ours).
|
|
506
|
+
const originalOnExit = () => {
|
|
507
|
+
for (const t of spinnerTimers) clearInterval(t);
|
|
508
|
+
};
|
|
509
|
+
process.once('exit', originalOnExit);
|
|
510
|
+
|
|
380
511
|
wm.run();
|
|
381
512
|
};
|
|
382
513
|
|
package/src/index.mts
CHANGED
|
@@ -15,6 +15,7 @@ export { Window } from './Screen/Window.mjs';
|
|
|
15
15
|
export { Region } from './Screen/Region.mjs';
|
|
16
16
|
export { StyleRegistry } from './Screen/StyleRegistry.mjs';
|
|
17
17
|
export { WindowManager } from './Screen/WindowManager.mjs';
|
|
18
|
+
export { VirtualCursor, DEFAULT_CURSOR_SYMBOL } from './Screen/VirtualCursor.mjs';
|
|
18
19
|
|
|
19
20
|
// ── Geometry ──────────────────────────────────────────────────────────────────
|
|
20
21
|
export { Pos } from './Screen/Pos.mjs';
|
|
@@ -39,6 +40,7 @@ export { LineChart } from './Screen/controls/LineChart.mjs';
|
|
|
39
40
|
export { BarChart } from './Screen/controls/BarChart.mjs';
|
|
40
41
|
export { Sparkline } from './Screen/controls/Sparkline.mjs';
|
|
41
42
|
export { Spinner } from './Screen/controls/Spinner.mjs';
|
|
43
|
+
export { Toast } from './Screen/controls/Toast.mjs';
|
|
42
44
|
|
|
43
45
|
// ── YAML layout builder ───────────────────────────────────────────────────────
|
|
44
46
|
export { InterfaceBuilder } from './Screen/InterfaceBuilder.mjs';
|
|
@@ -58,6 +60,8 @@ export {
|
|
|
58
60
|
BUILTIN_TEXT_PLACEHOLDER,
|
|
59
61
|
BUILTIN_TEXT_CHECKED,
|
|
60
62
|
BUILTIN_CURSOR,
|
|
63
|
+
BUILTIN_TEXT_SELECTION,
|
|
64
|
+
BUILTIN_TOAST,
|
|
61
65
|
} from './Screen/types.mjs';
|
|
62
66
|
|
|
63
67
|
// ── Public type exports ───────────────────────────────────────────────────────
|
|
@@ -70,6 +74,7 @@ export type {
|
|
|
70
74
|
TerminalSize,
|
|
71
75
|
ScreenOptions,
|
|
72
76
|
ScreenFrameStats,
|
|
77
|
+
DirtyRect,
|
|
73
78
|
AxisSpec,
|
|
74
79
|
DimSpec,
|
|
75
80
|
FlexBasis,
|
|
@@ -78,6 +83,8 @@ export type {
|
|
|
78
83
|
JustifyContent,
|
|
79
84
|
Padding,
|
|
80
85
|
PaddingSpec,
|
|
86
|
+
Margin,
|
|
87
|
+
MarginSpec,
|
|
81
88
|
|
|
82
89
|
// Window / border
|
|
83
90
|
BorderStyle,
|
|
@@ -106,12 +113,18 @@ export type {
|
|
|
106
113
|
TabsProperties,
|
|
107
114
|
SparklineProperties,
|
|
108
115
|
SpinnerProperties,
|
|
116
|
+
ToastPosition,
|
|
117
|
+
ToastOptions,
|
|
109
118
|
|
|
110
119
|
// Focus & input
|
|
111
120
|
Focusable,
|
|
112
121
|
TerminalMouseEvent,
|
|
113
122
|
WindowManagerOptions,
|
|
114
123
|
|
|
124
|
+
// Virtual cursor
|
|
125
|
+
CursorBlink,
|
|
126
|
+
VirtualCursorOptions,
|
|
127
|
+
|
|
115
128
|
// InterfaceBuilder YAML schema
|
|
116
129
|
YamlAxisValue,
|
|
117
130
|
YamlPosSpec,
|
package/src/layout.yaml
CHANGED
|
@@ -17,9 +17,19 @@ windows:
|
|
|
17
17
|
type: badge
|
|
18
18
|
pos: { x: 18, y: 0 }
|
|
19
19
|
size: { width: 10, height: 1 }
|
|
20
|
+
# P1-17 demo: painted after its siblings thanks to the higher zIndex,
|
|
21
|
+
# so any overlapping overlay child below zIndex 10 stays underneath.
|
|
22
|
+
zIndex: 10
|
|
20
23
|
props:
|
|
21
24
|
text: "P0-10 "
|
|
22
25
|
color: 220
|
|
26
|
+
- id: headerOverlay
|
|
27
|
+
pos: { x: 15, y: 0 }
|
|
28
|
+
size: { width: 16, height: 1 }
|
|
29
|
+
background: 52
|
|
30
|
+
# P1-17 demo: sits behind buildBadge even though declared later — the
|
|
31
|
+
# Window.render loop sorts siblings by (zIndex asc, insertion order).
|
|
32
|
+
zIndex: 1
|
|
23
33
|
|
|
24
34
|
- id: statusBar
|
|
25
35
|
pos: { preset: bottom }
|
|
@@ -67,6 +77,9 @@ windows:
|
|
|
67
77
|
size: { fillWidth: 3 }
|
|
68
78
|
value: "jan@example.com"
|
|
69
79
|
placeholder: "e-mail"
|
|
80
|
+
# Virtual cursor: custom caret glyph with a slow blink.
|
|
81
|
+
cursorSymbol: "▎"
|
|
82
|
+
cursorBlink: { mode: "slow" }
|
|
70
83
|
- id: cb1
|
|
71
84
|
type: checkbox
|
|
72
85
|
pos: { x: 1, y: 9 }
|
|
@@ -265,4 +278,7 @@ windows:
|
|
|
265
278
|
type: button
|
|
266
279
|
pos: flex
|
|
267
280
|
size: { width: 11, height: 3 }
|
|
281
|
+
# Extra breathing room between "Cancel" and "Save" via P1-16 margin
|
|
282
|
+
# (horizontal cells reserved on top of layout gap).
|
|
283
|
+
margin: { left: 2 }
|
|
268
284
|
label: " Save"
|