@promptctl/rich-js 0.16.0 → 0.18.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 +14 -27
- package/package.json +7 -3
- package/dist/core/box.d.ts +0 -144
- package/dist/core/box.js +0 -391
- package/dist/core/cells.d.ts +0 -148
- package/dist/core/cells.js +0 -266
- package/dist/core/color.d.ts +0 -229
- package/dist/core/color.js +0 -992
- package/dist/core/console.d.ts +0 -142
- package/dist/core/console.js +0 -477
- package/dist/core/emoji.d.ts +0 -24
- package/dist/core/emoji.js +0 -391
- package/dist/core/export-html.d.ts +0 -24
- package/dist/core/export-html.js +0 -106
- package/dist/core/export-lines.d.ts +0 -121
- package/dist/core/export-lines.js +0 -141
- package/dist/core/highlighter.d.ts +0 -33
- package/dist/core/highlighter.js +0 -124
- package/dist/core/json.d.ts +0 -27
- package/dist/core/json.js +0 -53
- package/dist/core/markup.d.ts +0 -97
- package/dist/core/markup.js +0 -619
- package/dist/core/measure.d.ts +0 -27
- package/dist/core/measure.js +0 -61
- package/dist/core/oklch.d.ts +0 -161
- package/dist/core/oklch.js +0 -312
- package/dist/core/osc8.d.ts +0 -68
- package/dist/core/osc8.js +0 -90
- package/dist/core/pretty.d.ts +0 -141
- package/dist/core/pretty.js +0 -472
- package/dist/core/protocol.d.ts +0 -137
- package/dist/core/protocol.js +0 -101
- package/dist/core/render.d.ts +0 -69
- package/dist/core/render.js +0 -140
- package/dist/core/segment.d.ts +0 -142
- package/dist/core/segment.js +0 -433
- package/dist/core/spinnerData.d.ts +0 -10
- package/dist/core/spinnerData.js +0 -115
- package/dist/core/strip.d.ts +0 -139
- package/dist/core/strip.js +0 -302
- package/dist/core/style.d.ts +0 -149
- package/dist/core/style.js +0 -741
- package/dist/core/subscription.d.ts +0 -12
- package/dist/core/subscription.js +0 -12
- package/dist/core/text.d.ts +0 -255
- package/dist/core/text.js +0 -990
- package/dist/core/wrap.d.ts +0 -41
- package/dist/core/wrap.js +0 -127
- package/dist/host/host-stream.d.ts +0 -30
- package/dist/host/host-stream.js +0 -35
- package/dist/host/index.d.ts +0 -3
- package/dist/host/index.js +0 -2
- package/dist/host/terminal-host.d.ts +0 -143
- package/dist/host/terminal-host.js +0 -132
- package/dist/index.d.ts +0 -87
- package/dist/index.js +0 -104
- package/dist/node/prompt.d.ts +0 -21
- package/dist/node/prompt.js +0 -30
- package/dist/node/save.d.ts +0 -17
- package/dist/node/save.js +0 -21
- package/dist/node/terminal-host.d.ts +0 -84
- package/dist/node/terminal-host.js +0 -160
- package/dist/node/traceback.d.ts +0 -27
- package/dist/node/traceback.js +0 -112
- package/dist/renderables/align.d.ts +0 -16
- package/dist/renderables/align.js +0 -46
- package/dist/renderables/columns.d.ts +0 -61
- package/dist/renderables/columns.js +0 -168
- package/dist/renderables/constrain.d.ts +0 -15
- package/dist/renderables/constrain.js +0 -37
- package/dist/renderables/flexStrip.d.ts +0 -41
- package/dist/renderables/flexStrip.js +0 -200
- package/dist/renderables/group.d.ts +0 -11
- package/dist/renderables/group.js +0 -15
- package/dist/renderables/layout.d.ts +0 -92
- package/dist/renderables/layout.js +0 -315
- package/dist/renderables/live.d.ts +0 -47
- package/dist/renderables/live.js +0 -131
- package/dist/renderables/markdown.d.ts +0 -24
- package/dist/renderables/markdown.js +0 -182
- package/dist/renderables/padding.d.ts +0 -38
- package/dist/renderables/padding.js +0 -140
- package/dist/renderables/panel.d.ts +0 -116
- package/dist/renderables/panel.js +0 -322
- package/dist/renderables/progress.d.ts +0 -90
- package/dist/renderables/progress.js +0 -241
- package/dist/renderables/progressBar.d.ts +0 -31
- package/dist/renderables/progressBar.js +0 -52
- package/dist/renderables/prompt.d.ts +0 -49
- package/dist/renderables/prompt.js +0 -125
- package/dist/renderables/rule.d.ts +0 -24
- package/dist/renderables/rule.js +0 -90
- package/dist/renderables/spinner.d.ts +0 -29
- package/dist/renderables/spinner.js +0 -64
- package/dist/renderables/status.d.ts +0 -23
- package/dist/renderables/status.js +0 -60
- package/dist/renderables/syntax.d.ts +0 -35
- package/dist/renderables/syntax.js +0 -114
- package/dist/renderables/table.d.ts +0 -182
- package/dist/renderables/table.js +0 -697
- package/dist/renderables/traceback.d.ts +0 -31
- package/dist/renderables/traceback.js +0 -109
- package/dist/renderables/tree.d.ts +0 -54
- package/dist/renderables/tree.js +0 -159
- package/dist/template-bindings/color-funcs.d.ts +0 -87
- package/dist/template-bindings/color-funcs.js +0 -219
- package/dist/template-bindings/helpers.d.ts +0 -24
- package/dist/template-bindings/helpers.js +0 -48
- package/dist/template-bindings/index.d.ts +0 -107
- package/dist/template-bindings/index.js +0 -144
- package/dist/template-bindings/palette-funcs.d.ts +0 -94
- package/dist/template-bindings/palette-funcs.js +0 -151
- package/dist/template-bindings/style-funcs.d.ts +0 -28
- package/dist/template-bindings/style-funcs.js +0 -151
- package/dist/themes/buildPalette.d.ts +0 -36
- package/dist/themes/buildPalette.js +0 -56
- package/dist/themes/colorMath.d.ts +0 -82
- package/dist/themes/colorMath.js +0 -254
- package/dist/themes/colorRef.d.ts +0 -51
- package/dist/themes/colorRef.js +0 -96
- package/dist/themes/data/atom-one-dark.d.ts +0 -3
- package/dist/themes/data/atom-one-dark.js +0 -158
- package/dist/themes/data/atom-one-light.d.ts +0 -3
- package/dist/themes/data/atom-one-light.js +0 -158
- package/dist/themes/data/catppuccin-frappe.d.ts +0 -3
- package/dist/themes/data/catppuccin-frappe.js +0 -158
- package/dist/themes/data/catppuccin-latte.d.ts +0 -3
- package/dist/themes/data/catppuccin-latte.js +0 -158
- package/dist/themes/data/catppuccin-macchiato.d.ts +0 -3
- package/dist/themes/data/catppuccin-macchiato.js +0 -158
- package/dist/themes/data/catppuccin-mocha.d.ts +0 -3
- package/dist/themes/data/catppuccin-mocha.js +0 -158
- package/dist/themes/data/cyberpunk.d.ts +0 -17
- package/dist/themes/data/cyberpunk.js +0 -191
- package/dist/themes/data/default.d.ts +0 -3
- package/dist/themes/data/default.js +0 -158
- package/dist/themes/data/dracula.d.ts +0 -3
- package/dist/themes/data/dracula.js +0 -158
- package/dist/themes/data/flexoki.d.ts +0 -3
- package/dist/themes/data/flexoki.js +0 -158
- package/dist/themes/data/gruvbox.d.ts +0 -3
- package/dist/themes/data/gruvbox.js +0 -158
- package/dist/themes/data/index.d.ts +0 -35
- package/dist/themes/data/index.js +0 -64
- package/dist/themes/data/monokai.d.ts +0 -3
- package/dist/themes/data/monokai.js +0 -158
- package/dist/themes/data/nord.d.ts +0 -3
- package/dist/themes/data/nord.js +0 -158
- package/dist/themes/data/rose-pine-dawn.d.ts +0 -3
- package/dist/themes/data/rose-pine-dawn.js +0 -158
- package/dist/themes/data/rose-pine-moon.d.ts +0 -3
- package/dist/themes/data/rose-pine-moon.js +0 -158
- package/dist/themes/data/rose-pine.d.ts +0 -3
- package/dist/themes/data/rose-pine.js +0 -158
- package/dist/themes/data/solarized-dark.d.ts +0 -3
- package/dist/themes/data/solarized-dark.js +0 -158
- package/dist/themes/data/solarized-light.d.ts +0 -3
- package/dist/themes/data/solarized-light.js +0 -158
- package/dist/themes/data/svg-export.d.ts +0 -3
- package/dist/themes/data/svg-export.js +0 -158
- package/dist/themes/data/textual-ansi.d.ts +0 -3
- package/dist/themes/data/textual-ansi.js +0 -158
- package/dist/themes/data/textual-dark.d.ts +0 -3
- package/dist/themes/data/textual-dark.js +0 -158
- package/dist/themes/data/textual-light.d.ts +0 -3
- package/dist/themes/data/textual-light.js +0 -158
- package/dist/themes/data/tokyo-night.d.ts +0 -3
- package/dist/themes/data/tokyo-night.js +0 -158
- package/dist/themes/data/types.d.ts +0 -15
- package/dist/themes/data/types.js +0 -1
- package/dist/themes/palette.d.ts +0 -21
- package/dist/themes/palette.js +0 -28
- package/dist/themes/ramp.d.ts +0 -83
- package/dist/themes/ramp.js +0 -122
- package/dist/themes/registry.d.ts +0 -43
- package/dist/themes/registry.js +0 -82
- package/dist/themes/terminalThemes.d.ts +0 -54
- package/dist/themes/terminalThemes.js +0 -68
- package/dist/themes/transpose.d.ts +0 -87
- package/dist/themes/transpose.js +0 -138
- package/dist/widgets/button.d.ts +0 -49
- package/dist/widgets/button.js +0 -206
- package/dist/widgets/checkbox.d.ts +0 -41
- package/dist/widgets/checkbox.js +0 -155
- package/dist/widgets/dropdown.d.ts +0 -67
- package/dist/widgets/dropdown.js +0 -453
- package/dist/widgets/event-router.d.ts +0 -78
- package/dist/widgets/event-router.js +0 -622
- package/dist/widgets/focus-manager.d.ts +0 -26
- package/dist/widgets/focus-manager.js +0 -193
- package/dist/widgets/index.d.ts +0 -22
- package/dist/widgets/index.js +0 -15
- package/dist/widgets/screen.d.ts +0 -98
- package/dist/widgets/screen.js +0 -542
- package/dist/widgets/slider.d.ts +0 -57
- package/dist/widgets/slider.js +0 -270
- package/dist/widgets/static-item.d.ts +0 -40
- package/dist/widgets/static-item.js +0 -60
- package/dist/widgets/text-input.d.ts +0 -274
- package/dist/widgets/text-input.js +0 -1162
- package/dist/widgets/toggle.d.ts +0 -44
- package/dist/widgets/toggle.js +0 -179
- package/dist/widgets/types.d.ts +0 -108
- package/dist/widgets/types.js +0 -31
- package/dist/widgets/widget-base.d.ts +0 -42
- package/dist/widgets/widget-base.js +0 -173
package/dist/widgets/screen.js
DELETED
|
@@ -1,542 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Screen — owns the render loop and ANSI output for interactive widgets.
|
|
3
|
-
*
|
|
4
|
-
* [LAW:dataflow-not-control-flow] Same pipeline runs every frame: render
|
|
5
|
-
* widgets to Segment[], split into lines, encode to ANSI, write to stdout
|
|
6
|
-
* with cursor-up + overwrite. Variability lives in the values (which widgets
|
|
7
|
-
* are visible, what they render), never in whether the pipeline runs.
|
|
8
|
-
*
|
|
9
|
-
* [LAW:one-source-of-truth] Layout (widget bounds) is computed here and
|
|
10
|
-
* written to widget.bounds. Mouse hit-testing reads the same bounds — the
|
|
11
|
-
* Screen is the single authority on where each widget is drawn.
|
|
12
|
-
*
|
|
13
|
-
* [LAW:single-enforcer] Cursor management lives in one place: `draw()`
|
|
14
|
-
* tracks `lastLineCount` and emits a single `\x1b[<n>A` to reposition.
|
|
15
|
-
* Per-line `\x1b[K` (erase to end of line) overwrites old content without
|
|
16
|
-
* flicker — no clear-and-redraw cycle.
|
|
17
|
-
*
|
|
18
|
-
* Reactivity: `mobx.autorun` re-fires on any observable read during render
|
|
19
|
-
* (label, focused, hovered, active, disabled, visible, the widget list
|
|
20
|
-
* itself). Renders are debounced to a microtask so a burst of state changes
|
|
21
|
-
* within one tick produces one frame.
|
|
22
|
-
*
|
|
23
|
-
* Layout (per-widget Placement):
|
|
24
|
-
*
|
|
25
|
-
* flow — vertical stack at x=0; advances the layout cursor by the
|
|
26
|
-
* widget's measured height. Default placement; preserves the
|
|
27
|
-
* historical single-column behavior so existing consumers
|
|
28
|
-
* (and the screen tests) need no migration.
|
|
29
|
-
* inline — same row as the preceding flow/inline neighbor; x packs
|
|
30
|
-
* after that neighbor's right edge plus a one-cell gap. The
|
|
31
|
-
* row's height is the max of its members; the cursor advances
|
|
32
|
-
* past the tallest member when the next non-inline item is
|
|
33
|
-
* placed (or at end of frame).
|
|
34
|
-
* fixed — absolute (x, y); independent of the cursor. Used for
|
|
35
|
-
* anchored content like status/log rows that the host wants
|
|
36
|
-
* placed at a known coordinate regardless of flow growth.
|
|
37
|
-
*
|
|
38
|
-
* The overlay pass (single enforcer) runs after base layout: any widget
|
|
39
|
-
* implementing OverlayRenderable contributes overlay segments anchored
|
|
40
|
-
* directly below its inline footprint, and Screen unions the overlay
|
|
41
|
-
* area into the widget's bounds for hit-testing.
|
|
42
|
-
*
|
|
43
|
-
* Design alternatives considered:
|
|
44
|
-
* 1. Per-widget Placement (chosen) — smallest type that covers the legal
|
|
45
|
-
* variability; one total switch in computeFrame; back-compat default.
|
|
46
|
-
* 2. Named regions (header/body/status as flow containers) — adds a
|
|
47
|
-
* two-step API surface (declare regions, then mount into them) and
|
|
48
|
-
* another concept; rejected as larger than the problem requires.
|
|
49
|
-
* 3. Host-supplied layout function — pushes layout out of Screen,
|
|
50
|
-
* defeating single-enforcer; rejected.
|
|
51
|
-
*/
|
|
52
|
-
var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
|
|
53
|
-
function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
|
|
54
|
-
var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
|
|
55
|
-
var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
|
|
56
|
-
var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
|
|
57
|
-
var _, done = false;
|
|
58
|
-
for (var i = decorators.length - 1; i >= 0; i--) {
|
|
59
|
-
var context = {};
|
|
60
|
-
for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
|
|
61
|
-
for (var p in contextIn.access) context.access[p] = contextIn.access[p];
|
|
62
|
-
context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
|
|
63
|
-
var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
|
|
64
|
-
if (kind === "accessor") {
|
|
65
|
-
if (result === void 0) continue;
|
|
66
|
-
if (result === null || typeof result !== "object") throw new TypeError("Object expected");
|
|
67
|
-
if (_ = accept(result.get)) descriptor.get = _;
|
|
68
|
-
if (_ = accept(result.set)) descriptor.set = _;
|
|
69
|
-
if (_ = accept(result.init)) initializers.unshift(_);
|
|
70
|
-
}
|
|
71
|
-
else if (_ = accept(result)) {
|
|
72
|
-
if (kind === "field") initializers.unshift(_);
|
|
73
|
-
else descriptor[key] = _;
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
if (target) Object.defineProperty(target, contextIn.name, descriptor);
|
|
77
|
-
done = true;
|
|
78
|
-
};
|
|
79
|
-
var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
|
|
80
|
-
var useValue = arguments.length > 2;
|
|
81
|
-
for (var i = 0; i < initializers.length; i++) {
|
|
82
|
-
value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
|
|
83
|
-
}
|
|
84
|
-
return useValue ? value : void 0;
|
|
85
|
-
};
|
|
86
|
-
import { autorun, observableShallow, runInAction } from "mobx";
|
|
87
|
-
import { Segment } from "../core/segment.js";
|
|
88
|
-
import { asCellCol } from "../core/cells.js";
|
|
89
|
-
import { segmentsToString } from "../core/render.js";
|
|
90
|
-
import { resolveColorSystem } from "../core/color.js";
|
|
91
|
-
import { DefaultFocusManager } from "./focus-manager.js";
|
|
92
|
-
import { FLOW, hasOverlay } from "./types.js";
|
|
93
|
-
let DefaultScreen = (() => {
|
|
94
|
-
let _widgetList_decorators;
|
|
95
|
-
let _widgetList_initializers = [];
|
|
96
|
-
let _widgetList_extraInitializers = [];
|
|
97
|
-
return class DefaultScreen {
|
|
98
|
-
static {
|
|
99
|
-
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
|
|
100
|
-
_widgetList_decorators = [observableShallow];
|
|
101
|
-
__esDecorate(this, null, _widgetList_decorators, { kind: "accessor", name: "widgetList", static: false, private: false, access: { has: obj => "widgetList" in obj, get: obj => obj.widgetList, set: (obj, value) => { obj.widgetList = value; } }, metadata: _metadata }, _widgetList_initializers, _widgetList_extraInitializers);
|
|
102
|
-
if (_metadata) Object.defineProperty(this, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
|
|
103
|
-
}
|
|
104
|
-
focusManager;
|
|
105
|
-
#widgetList_accessor_storage = __runInitializers(this, _widgetList_initializers, []);
|
|
106
|
-
// [LAW:one-source-of-truth] The mounted widget list is observable so
|
|
107
|
-
// mount/unmount triggers the render autorun without an extra subscription.
|
|
108
|
-
get widgetList() { return this.#widgetList_accessor_storage; }
|
|
109
|
-
set widgetList(value) { this.#widgetList_accessor_storage = value; }
|
|
110
|
-
// [LAW:one-source-of-truth] Placements live in a parallel Map keyed by
|
|
111
|
-
// widget. Placements are immutable per mount — Screen sets them and never
|
|
112
|
-
// mutates them, so they don't need to be observable. The observable
|
|
113
|
-
// `widgetList` above already fires reactivity on mount/unmount.
|
|
114
|
-
placements = (__runInitializers(this, _widgetList_extraInitializers), new Map());
|
|
115
|
-
// [LAW:single-enforcer] Set of widgets whose overlay rendered visible
|
|
116
|
-
// rows in the most recent frame. Updated by draw() from FrameLayout;
|
|
117
|
-
// consumed by the `widgets` getter to expose hit-test z-order to
|
|
118
|
-
// EventRouter. Mount/unmount don't touch this — it's a per-frame
|
|
119
|
-
// derivation, not mount state.
|
|
120
|
-
_activeOverlays = new Set();
|
|
121
|
-
_running = false;
|
|
122
|
-
autorunDispose;
|
|
123
|
-
renderScheduled = false;
|
|
124
|
-
// [LAW:one-source-of-truth] The autorun computes the next frame once and
|
|
125
|
-
// stores it here; draw() reads and clears. Not observable — only the
|
|
126
|
-
// autorun writes, only draw reads, both run in the same single-threaded
|
|
127
|
-
// event loop.
|
|
128
|
-
pendingFrame = null;
|
|
129
|
-
lastLineCount = 0;
|
|
130
|
-
host;
|
|
131
|
-
widthOverride;
|
|
132
|
-
colorSystem;
|
|
133
|
-
manageCursor;
|
|
134
|
-
constructor(options) {
|
|
135
|
-
this.host = options.host;
|
|
136
|
-
this.widthOverride = options.width;
|
|
137
|
-
this.focusManager = options.focusManager ?? new DefaultFocusManager();
|
|
138
|
-
const isTTY = this.host.isTTY;
|
|
139
|
-
this.colorSystem = resolveSpec(options.colorSystem, isTTY);
|
|
140
|
-
this.manageCursor = options.manageCursor ?? isTTY;
|
|
141
|
-
}
|
|
142
|
-
get running() {
|
|
143
|
-
return this._running;
|
|
144
|
-
}
|
|
145
|
-
// [LAW:types-are-the-program] `widgets` is the *output* of layout: the
|
|
146
|
-
// list EventRouter uses for hit-testing, in z-order — non-overlay widgets
|
|
147
|
-
// first (in mount order), overlay-active widgets last. `topmostHit`
|
|
148
|
-
// iterates this in reverse and returns the first containing widget, so
|
|
149
|
-
// overlay-active widgets win against anything mounted under them.
|
|
150
|
-
// [LAW:single-enforcer] Screen is the sole authority on z-order; the
|
|
151
|
-
// router consumes the result, never derives it itself.
|
|
152
|
-
get widgets() {
|
|
153
|
-
if (this._activeOverlays.size === 0)
|
|
154
|
-
return this.widgetList;
|
|
155
|
-
const base = [];
|
|
156
|
-
const top = [];
|
|
157
|
-
for (const w of this.widgetList) {
|
|
158
|
-
(this._activeOverlays.has(w) ? top : base).push(w);
|
|
159
|
-
}
|
|
160
|
-
return [...base, ...top];
|
|
161
|
-
}
|
|
162
|
-
// --- Lifecycle ---
|
|
163
|
-
mount(...entries) {
|
|
164
|
-
runInAction(() => {
|
|
165
|
-
const next = [...this.widgetList];
|
|
166
|
-
for (const entry of entries) {
|
|
167
|
-
const { widget, placement } = normalizeEntry(entry);
|
|
168
|
-
if (next.includes(widget))
|
|
169
|
-
continue;
|
|
170
|
-
next.push(widget);
|
|
171
|
-
this.placements.set(widget, placement);
|
|
172
|
-
this.focusManager.register(widget);
|
|
173
|
-
}
|
|
174
|
-
this.widgetList = next;
|
|
175
|
-
});
|
|
176
|
-
}
|
|
177
|
-
unmount(widget) {
|
|
178
|
-
runInAction(() => {
|
|
179
|
-
const idx = this.widgetList.indexOf(widget);
|
|
180
|
-
if (idx === -1)
|
|
181
|
-
return;
|
|
182
|
-
this.widgetList = this.widgetList.filter((w) => w !== widget);
|
|
183
|
-
this.placements.delete(widget);
|
|
184
|
-
this.focusManager.unregister(widget);
|
|
185
|
-
// [LAW:one-source-of-truth] Drop any z-order entry for the removed
|
|
186
|
-
// widget so a later getter doesn't return a dangling reference.
|
|
187
|
-
// The next draw will rebuild this from scratch.
|
|
188
|
-
this._activeOverlays.delete(widget);
|
|
189
|
-
});
|
|
190
|
-
}
|
|
191
|
-
start() {
|
|
192
|
-
if (this._running)
|
|
193
|
-
return;
|
|
194
|
-
this._running = true;
|
|
195
|
-
this.lastLineCount = 0;
|
|
196
|
-
if (this.manageCursor)
|
|
197
|
-
this.host.write("\x1b[?25l");
|
|
198
|
-
// [LAW:dataflow-not-control-flow] One reactive pipeline. The autorun
|
|
199
|
-
// computes the frame (which reads observables; MobX subscribes to
|
|
200
|
-
// exactly what was read so only relevant state changes trigger redraws),
|
|
201
|
-
// caches it as `pendingFrame`, and schedules a microtask to paint it.
|
|
202
|
-
// draw() consumes the cached frame instead of recomputing — without the
|
|
203
|
-
// cache, every tick laid the frame out twice (once for subscription,
|
|
204
|
-
// once for paint).
|
|
205
|
-
this.autorunDispose = autorun(() => {
|
|
206
|
-
this.pendingFrame = this.computeFrame();
|
|
207
|
-
this.scheduleRender();
|
|
208
|
-
});
|
|
209
|
-
}
|
|
210
|
-
stop() {
|
|
211
|
-
if (!this._running)
|
|
212
|
-
return;
|
|
213
|
-
this._running = false;
|
|
214
|
-
if (this.autorunDispose) {
|
|
215
|
-
this.autorunDispose();
|
|
216
|
-
this.autorunDispose = undefined;
|
|
217
|
-
}
|
|
218
|
-
// [LAW:one-source-of-truth] stop() returns the screen to a clean
|
|
219
|
-
// baseline. Without these resets a stopped screen would retain the
|
|
220
|
-
// last FrameLayout (segments + bounds) — unnecessary memory and a
|
|
221
|
-
// restart hazard where the first frame after restart could leak the
|
|
222
|
-
// pre-stop layout. Same idempotent-cleanup pattern as EventRouter.stop.
|
|
223
|
-
this.pendingFrame = null;
|
|
224
|
-
this.renderScheduled = false;
|
|
225
|
-
this._activeOverlays = new Set();
|
|
226
|
-
if (this.manageCursor)
|
|
227
|
-
this.host.write("\x1b[?25h");
|
|
228
|
-
if (this.lastLineCount > 0)
|
|
229
|
-
this.host.write("\n");
|
|
230
|
-
}
|
|
231
|
-
// --- Internal: layout + draw ---
|
|
232
|
-
get width() {
|
|
233
|
-
if (this.widthOverride !== undefined)
|
|
234
|
-
return this.widthOverride;
|
|
235
|
-
return this.host.size().cols;
|
|
236
|
-
}
|
|
237
|
-
computeFrame() {
|
|
238
|
-
// [LAW:one-source-of-truth] Snapshot the width ONCE at the top of
|
|
239
|
-
// the frame so layout, per-widget clipping, per-line clipping, and
|
|
240
|
-
// the final paint all see the same value. Reading `this.width`
|
|
241
|
-
// multiple times can drift across a microtask boundary if the
|
|
242
|
-
// terminal is resized between compute and draw.
|
|
243
|
-
const width = this.width;
|
|
244
|
-
const renderOptions = {
|
|
245
|
-
maxWidth: width,
|
|
246
|
-
isTerminal: true,
|
|
247
|
-
encoding: "utf-8",
|
|
248
|
-
// [LAW:one-source-of-truth] The depth the frame is encoded at (below,
|
|
249
|
-
// `segmentsToString`), so a renderable that decides on drawn colours
|
|
250
|
-
// decides on the ones this screen draws.
|
|
251
|
-
colorSystem: this.colorSystem,
|
|
252
|
-
};
|
|
253
|
-
const lines = [];
|
|
254
|
-
const boundsList = [];
|
|
255
|
-
// [LAW:single-enforcer] Populated by pass 2 below. Pass 2 is the only
|
|
256
|
-
// place that decides whether a widget's overlay produced visible rows
|
|
257
|
-
// this frame — exactly where the z-order discriminator belongs.
|
|
258
|
-
const activeOverlays = new Set();
|
|
259
|
-
// Layout cursor for flow placement. `cursorY` is the next free row.
|
|
260
|
-
// `lastFlowRow` tracks the most recent flow/inline row so that a
|
|
261
|
-
// following inline placement can pack against its right edge.
|
|
262
|
-
let cursorY = 0;
|
|
263
|
-
let lastFlowRow = null;
|
|
264
|
-
// Pass 1 — base layout. Each widget contributes its inline footprint
|
|
265
|
-
// (Renderable.render). Position is determined by the widget's Placement.
|
|
266
|
-
for (const widget of this.widgetList) {
|
|
267
|
-
// [LAW:dataflow-not-control-flow] Hidden widgets still pass through the
|
|
268
|
-
// pipeline — their data just produces zero rows and zero-size bounds.
|
|
269
|
-
// The same operations execute every iteration.
|
|
270
|
-
const visible = widget.visible;
|
|
271
|
-
const segments = visible ? Array.from(widget.render(renderOptions)) : [];
|
|
272
|
-
const rawLines = visible ? Segment.splitLines(segments) : [];
|
|
273
|
-
const placement = this.placements.get(widget) ?? FLOW;
|
|
274
|
-
// [LAW:types-are-the-program] Single total switch on the discriminated
|
|
275
|
-
// union: every legal placement gets exactly one branch, the compiler
|
|
276
|
-
// enforces exhaustiveness, and the rest of the pipeline (clip to the
|
|
277
|
-
// remaining screen width, paint into `lines`, record bounds) is
|
|
278
|
-
// identical for every kind. x/y come from the placement; the available
|
|
279
|
-
// width comes from `this.width - x`.
|
|
280
|
-
let x;
|
|
281
|
-
let y;
|
|
282
|
-
let prevAtPlacement = null;
|
|
283
|
-
switch (placement.kind) {
|
|
284
|
-
case "flow":
|
|
285
|
-
x = 0;
|
|
286
|
-
y = cursorY;
|
|
287
|
-
break;
|
|
288
|
-
case "inline": {
|
|
289
|
-
// Pack onto the most recent flow/inline row. If there is no prior
|
|
290
|
-
// row (inline used as the first item), fall back to flow at x=0.
|
|
291
|
-
prevAtPlacement = lastFlowRow;
|
|
292
|
-
if (prevAtPlacement === null) {
|
|
293
|
-
x = 0;
|
|
294
|
-
y = cursorY;
|
|
295
|
-
}
|
|
296
|
-
else {
|
|
297
|
-
x = prevAtPlacement.rightX + (prevAtPlacement.rightX > 0 ? 1 : 0);
|
|
298
|
-
y = prevAtPlacement.startY;
|
|
299
|
-
}
|
|
300
|
-
break;
|
|
301
|
-
}
|
|
302
|
-
case "fixed":
|
|
303
|
-
x = placement.x;
|
|
304
|
-
y = placement.y;
|
|
305
|
-
break;
|
|
306
|
-
}
|
|
307
|
-
// [LAW:single-enforcer] Clip widget lines to the remaining screen
|
|
308
|
-
// width here so widget bounds match what actually gets painted. A
|
|
309
|
-
// renderable that emits more cells than its allotted slot (rogue
|
|
310
|
-
// widget, or an inline placement overflowing the screen edge) would
|
|
311
|
-
// otherwise compute bounds wider than reality, breaking hit-testing
|
|
312
|
-
// and the inline rightX that subsequent inline widgets pack against.
|
|
313
|
-
const available = Math.max(0, width - x);
|
|
314
|
-
const widgetLines = available > 0
|
|
315
|
-
? rawLines.map((line) => Segment.adjustLineLength(line, available, undefined, false))
|
|
316
|
-
: [];
|
|
317
|
-
const [w, h] = Segment.getShape(widgetLines);
|
|
318
|
-
paintLines(lines, widgetLines, x, y);
|
|
319
|
-
if (placement.kind === "flow" && h > 0) {
|
|
320
|
-
cursorY = y + h;
|
|
321
|
-
lastFlowRow = { startY: y, height: h, rightX: x + w };
|
|
322
|
-
}
|
|
323
|
-
else if (placement.kind === "inline" && h > 0) {
|
|
324
|
-
const rowStart = prevAtPlacement?.startY ?? y;
|
|
325
|
-
const rowHeight = Math.max(prevAtPlacement?.height ?? 0, h);
|
|
326
|
-
cursorY = Math.max(cursorY, rowStart + rowHeight);
|
|
327
|
-
lastFlowRow = { startY: rowStart, height: rowHeight, rightX: x + w };
|
|
328
|
-
}
|
|
329
|
-
// Fixed placements never advance the flow cursor — they are
|
|
330
|
-
// independent anchors. paintLines grows the canvas as needed.
|
|
331
|
-
boundsList.push({ widget, bounds: { x, y, width: w, height: h } });
|
|
332
|
-
}
|
|
333
|
-
// Pass 2 — overlays. Widgets that implement OverlayRenderable paint
|
|
334
|
-
// ON TOP of the frame, anchored directly below their inline footprint.
|
|
335
|
-
// Render order = z-order: the overlay pass runs last, so overlay rows
|
|
336
|
-
// overwrite anything that was placed below the widget by pass 1.
|
|
337
|
-
// [LAW:single-enforcer] Overlay placement, bounds-union, AND z-order
|
|
338
|
-
// for hit-testing all happen here; widgets never draw their own
|
|
339
|
-
// overlays and the router never re-derives z-order. Each widget that
|
|
340
|
-
// produces visible overlay rows is added to `activeOverlays` so the
|
|
341
|
-
// `widgets` getter can surface it as topmost.
|
|
342
|
-
for (const entry of boundsList) {
|
|
343
|
-
const widget = entry.widget;
|
|
344
|
-
if (!widget.visible)
|
|
345
|
-
continue;
|
|
346
|
-
if (!hasOverlay(widget))
|
|
347
|
-
continue;
|
|
348
|
-
const overlaySegs = widget.renderOverlay(renderOptions);
|
|
349
|
-
if (overlaySegs === null)
|
|
350
|
-
continue;
|
|
351
|
-
const overlayRawLines = Segment.splitLines(Array.from(overlaySegs));
|
|
352
|
-
if (overlayRawLines.length === 0)
|
|
353
|
-
continue;
|
|
354
|
-
// [LAW:single-enforcer] Same per-line clip as the base pass — bounds
|
|
355
|
-
// (used for hit-testing the overlay) match the painted output.
|
|
356
|
-
const overlayAvailable = Math.max(0, width - entry.bounds.x);
|
|
357
|
-
const overlayLines = overlayAvailable > 0
|
|
358
|
-
? overlayRawLines.map((line) => Segment.adjustLineLength(line, overlayAvailable, undefined, false))
|
|
359
|
-
: [];
|
|
360
|
-
if (overlayLines.length === 0)
|
|
361
|
-
continue;
|
|
362
|
-
const startY = entry.bounds.y + entry.bounds.height;
|
|
363
|
-
paintLines(lines, overlayLines, entry.bounds.x, startY);
|
|
364
|
-
const [overlayW] = Segment.getShape(overlayLines);
|
|
365
|
-
entry.bounds = {
|
|
366
|
-
x: entry.bounds.x,
|
|
367
|
-
y: entry.bounds.y,
|
|
368
|
-
width: Math.max(entry.bounds.width, overlayW),
|
|
369
|
-
height: entry.bounds.height + overlayLines.length,
|
|
370
|
-
};
|
|
371
|
-
activeOverlays.add(widget);
|
|
372
|
-
}
|
|
373
|
-
return { width, lines, bounds: boundsList, activeOverlays };
|
|
374
|
-
}
|
|
375
|
-
scheduleRender() {
|
|
376
|
-
if (this.renderScheduled)
|
|
377
|
-
return;
|
|
378
|
-
this.renderScheduled = true;
|
|
379
|
-
queueMicrotask(() => {
|
|
380
|
-
this.renderScheduled = false;
|
|
381
|
-
if (this._running)
|
|
382
|
-
this.draw();
|
|
383
|
-
});
|
|
384
|
-
}
|
|
385
|
-
draw() {
|
|
386
|
-
// [LAW:single-enforcer] One frame per tick. The autorun populates
|
|
387
|
-
// `pendingFrame`; we consume it here. Fall back to a fresh compute if
|
|
388
|
-
// draw is reached without a pending frame (test paths that call draw
|
|
389
|
-
// outside the autorun, or the very first frame before autorun fired).
|
|
390
|
-
const frame = this.pendingFrame ?? this.computeFrame();
|
|
391
|
-
this.pendingFrame = null;
|
|
392
|
-
const { width, lines, bounds, activeOverlays } = frame;
|
|
393
|
-
// [LAW:single-enforcer] Bounds are written here, the only place that
|
|
394
|
-
// computes layout. `bounds` is a plain field (see widget-base.ts) — no
|
|
395
|
-
// MobX action required.
|
|
396
|
-
for (const { widget, bounds: b } of bounds)
|
|
397
|
-
widget.bounds = b;
|
|
398
|
-
// [LAW:single-enforcer] Hit-test z-order is published by the same
|
|
399
|
-
// pass that wrote the bounds. The `widgets` getter reads this set to
|
|
400
|
-
// partition `widgetList` into [base, overlay-active] so EventRouter's
|
|
401
|
-
// topmostHit sees overlay owners as topmost.
|
|
402
|
-
this._activeOverlays = activeOverlays;
|
|
403
|
-
const newCount = lines.length;
|
|
404
|
-
const drawCount = Math.max(newCount, this.lastLineCount);
|
|
405
|
-
// [LAW:types-are-the-program] After the previous frame wrote N lines
|
|
406
|
-
// with N-1 newlines between them, the cursor sits at end-of-row-N (no
|
|
407
|
-
// trailing newline). Rewinding to the top of the frame is N-1 rows up
|
|
408
|
-
// plus a CR. `\x1b[0A` is still one row up on some terminals, so we
|
|
409
|
-
// skip the CSI entirely when there's only one row to overwrite.
|
|
410
|
-
let buf = "";
|
|
411
|
-
if (this.lastLineCount > 1) {
|
|
412
|
-
buf += `\x1b[${this.lastLineCount - 1}A`;
|
|
413
|
-
}
|
|
414
|
-
buf += "\r";
|
|
415
|
-
for (let i = 0; i < drawCount; i++) {
|
|
416
|
-
const line = lines[i];
|
|
417
|
-
if (line) {
|
|
418
|
-
// [LAW:single-enforcer] Clip each line to terminal width so wide
|
|
419
|
-
// content can't soft-wrap and break the "1 frame row = 1 terminal
|
|
420
|
-
// row" invariant the redraw loop depends on. Without this, a wrapped
|
|
421
|
-
// line shifts every later row down by one terminal cell, which
|
|
422
|
-
// misaligns overlays and leaves wrap-continuation residue when later
|
|
423
|
-
// frames write narrower content over the same logical row.
|
|
424
|
-
// Use the frame's captured width, not a re-read of this.width —
|
|
425
|
-
// see FrameLayout for the rationale.
|
|
426
|
-
const clipped = Segment.adjustLineLength(line, width, undefined, false);
|
|
427
|
-
buf += segmentsToString(clipped, this.colorSystem);
|
|
428
|
-
}
|
|
429
|
-
// [LAW:single-enforcer] Erase-to-end-of-line is the single mechanism
|
|
430
|
-
// for overwriting stale content. We do not pre-clear lines.
|
|
431
|
-
buf += "\x1b[K";
|
|
432
|
-
if (i < drawCount - 1)
|
|
433
|
-
buf += "\n";
|
|
434
|
-
}
|
|
435
|
-
this.host.write(buf);
|
|
436
|
-
this.lastLineCount = drawCount;
|
|
437
|
-
}
|
|
438
|
-
};
|
|
439
|
-
})();
|
|
440
|
-
export { DefaultScreen };
|
|
441
|
-
// [LAW:single-enforcer] One function resolves the color-system spec into a
|
|
442
|
-
// ColorDepth (or null). String specs route through resolveColorSystem; enum
|
|
443
|
-
// values pass through; null/`"none"` strip color.
|
|
444
|
-
function resolveSpec(spec, isTTY) {
|
|
445
|
-
if (spec === null)
|
|
446
|
-
return null;
|
|
447
|
-
if (spec === undefined)
|
|
448
|
-
return resolveColorSystem("auto", { isTTY });
|
|
449
|
-
if (typeof spec === "string")
|
|
450
|
-
return resolveColorSystem(spec, { isTTY });
|
|
451
|
-
return spec;
|
|
452
|
-
}
|
|
453
|
-
// Normalize a MountEntry into its internal { widget, placement } form.
|
|
454
|
-
// Bare widgets default to flow placement so existing call sites (and tests)
|
|
455
|
-
// that pass `mount(a, b, c)` keep working unchanged.
|
|
456
|
-
function normalizeEntry(entry) {
|
|
457
|
-
if ("widget" in entry && "placement" in entry) {
|
|
458
|
-
validatePlacement(entry.placement);
|
|
459
|
-
return { widget: entry.widget, placement: entry.placement };
|
|
460
|
-
}
|
|
461
|
-
return { widget: entry, placement: FLOW };
|
|
462
|
-
}
|
|
463
|
-
// [LAW:types-are-the-program] mount() is the trust boundary for placements.
|
|
464
|
-
// `x` and `y` are typed as `number` but the layout pipeline assumes
|
|
465
|
-
// non-negative integers — negative coordinates would index out of bounds in
|
|
466
|
-
// paintLines and either crash on bang-asserts or silently skip rows. Reject
|
|
467
|
-
// at construction so the rest of the pipeline can assume validity.
|
|
468
|
-
function validatePlacement(p) {
|
|
469
|
-
if (p.kind !== "fixed")
|
|
470
|
-
return;
|
|
471
|
-
if (!Number.isInteger(p.x) || !Number.isInteger(p.y) || p.x < 0 || p.y < 0) {
|
|
472
|
-
throw new RangeError(`fixed Placement requires non-negative integer x and y; got (${p.x}, ${p.y})`);
|
|
473
|
-
}
|
|
474
|
-
}
|
|
475
|
-
// Paint `widgetLines` into the frame `lines` at top-left (x, y). Grows the
|
|
476
|
-
// frame as needed and uses cell-accurate splice semantics so a fixed
|
|
477
|
-
// placement or overlay overwrites whatever was painted earlier underneath.
|
|
478
|
-
//
|
|
479
|
-
// [LAW:single-enforcer] All compositing into the frame buffer goes through
|
|
480
|
-
// this function. flow/inline/fixed placements and the overlay pass all call
|
|
481
|
-
// it, so the rules for "how characters land at (x, y)" live in one place.
|
|
482
|
-
function paintLines(lines, widgetLines, x, y) {
|
|
483
|
-
if (widgetLines.length === 0)
|
|
484
|
-
return;
|
|
485
|
-
while (lines.length < y + widgetLines.length)
|
|
486
|
-
lines.push([]);
|
|
487
|
-
for (let i = 0; i < widgetLines.length; i++) {
|
|
488
|
-
const target = lines[y + i];
|
|
489
|
-
const source = widgetLines[i];
|
|
490
|
-
const sourceWidth = lineCellLength(source);
|
|
491
|
-
if (sourceWidth === 0)
|
|
492
|
-
continue;
|
|
493
|
-
if (x === 0 && target.length === 0) {
|
|
494
|
-
lines[y + i] = source.slice();
|
|
495
|
-
continue;
|
|
496
|
-
}
|
|
497
|
-
lines[y + i] = spliceCells(target, asCellCol(x), sourceWidth, source);
|
|
498
|
-
}
|
|
499
|
-
}
|
|
500
|
-
function lineCellLength(line) {
|
|
501
|
-
let total = 0;
|
|
502
|
-
for (const s of line)
|
|
503
|
-
total += s.cellLength;
|
|
504
|
-
return asCellCol(total);
|
|
505
|
-
}
|
|
506
|
-
// Return a new row with cells [start, start+length) replaced by
|
|
507
|
-
// `replacement`. Pads the prefix with spaces if `row` is shorter than
|
|
508
|
-
// `start`. Honors wide characters and styled segments by splitting at cell
|
|
509
|
-
// boundaries.
|
|
510
|
-
function spliceCells(row, start, length, replacement) {
|
|
511
|
-
const rowWidth = lineCellLength(row);
|
|
512
|
-
const padded = row.slice();
|
|
513
|
-
if (rowWidth < start) {
|
|
514
|
-
padded.push(new Segment(" ".repeat(start - rowWidth)));
|
|
515
|
-
}
|
|
516
|
-
const prefix = [];
|
|
517
|
-
const suffix = [];
|
|
518
|
-
let cursor = 0;
|
|
519
|
-
for (const seg of padded) {
|
|
520
|
-
const segEnd = cursor + seg.cellLength;
|
|
521
|
-
if (segEnd <= start) {
|
|
522
|
-
prefix.push(seg);
|
|
523
|
-
}
|
|
524
|
-
else if (cursor >= start + length) {
|
|
525
|
-
suffix.push(seg);
|
|
526
|
-
}
|
|
527
|
-
else {
|
|
528
|
-
if (cursor < start) {
|
|
529
|
-
const [head] = seg.splitCells(asCellCol(start - cursor));
|
|
530
|
-
if (head.hasText)
|
|
531
|
-
prefix.push(head);
|
|
532
|
-
}
|
|
533
|
-
if (segEnd > start + length) {
|
|
534
|
-
const [, tail] = seg.splitCells(asCellCol(start + length - cursor));
|
|
535
|
-
if (tail.hasText)
|
|
536
|
-
suffix.push(tail);
|
|
537
|
-
}
|
|
538
|
-
}
|
|
539
|
-
cursor = segEnd;
|
|
540
|
-
}
|
|
541
|
-
return [...prefix, ...replacement, ...suffix];
|
|
542
|
-
}
|
package/dist/widgets/slider.d.ts
DELETED
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Slider widget — numeric value within [min, max] with step increments.
|
|
3
|
-
* [LAW:dataflow-not-control-flow] every render emits exactly `width`
|
|
4
|
-
* track cells; the marker cell index is data, not a branch.
|
|
5
|
-
* [LAW:one-type-per-behavior] shared infrastructure inherited from
|
|
6
|
-
* WidgetBase.
|
|
7
|
-
*
|
|
8
|
-
* Visual:
|
|
9
|
-
* track — `width` cells of "─" (ASCII fallback "-")
|
|
10
|
-
* marker — "●" (ASCII fallback "*") at round((value - min) / range * (width - 1))
|
|
11
|
-
* filled — cells left of and including the marker use primary fg
|
|
12
|
-
* on the default terminal bg
|
|
13
|
-
* unfilled — cells right of the marker use surface fg on the default
|
|
14
|
-
* terminal bg (no explicit bgcolor — the unfilled track
|
|
15
|
-
* should sit transparently against whatever the panel /
|
|
16
|
-
* parent paints underneath)
|
|
17
|
-
* focused — underline on the segments
|
|
18
|
-
* disabled — dim
|
|
19
|
-
*/
|
|
20
|
-
import { Segment } from "../core/segment.js";
|
|
21
|
-
import type { RenderOptions } from "../core/protocol.js";
|
|
22
|
-
import type { TerminalTheme } from "../core/color.js";
|
|
23
|
-
import { WidgetBase } from "./widget-base.js";
|
|
24
|
-
import type { KeyEvent, WidgetMouseEvent } from "./types.js";
|
|
25
|
-
export interface SliderOptions {
|
|
26
|
-
value?: number;
|
|
27
|
-
min?: number;
|
|
28
|
-
max?: number;
|
|
29
|
-
step?: number;
|
|
30
|
-
width?: number;
|
|
31
|
-
id?: string;
|
|
32
|
-
disabled?: boolean;
|
|
33
|
-
theme?: TerminalTheme;
|
|
34
|
-
}
|
|
35
|
-
export declare class Slider extends WidgetBase {
|
|
36
|
-
readonly id: string;
|
|
37
|
-
readonly focusable = true;
|
|
38
|
-
accessor value: number;
|
|
39
|
-
accessor min: number;
|
|
40
|
-
accessor max: number;
|
|
41
|
-
accessor step: number;
|
|
42
|
-
accessor width: number;
|
|
43
|
-
private accessor _theme;
|
|
44
|
-
private _dragging;
|
|
45
|
-
constructor(options?: SliderOptions);
|
|
46
|
-
setTheme(theme: TerminalTheme): void;
|
|
47
|
-
handleKey(event: KeyEvent): void;
|
|
48
|
-
handleMouse(event: WidgetMouseEvent): void;
|
|
49
|
-
private setValue;
|
|
50
|
-
private setValueFromMouseX;
|
|
51
|
-
render(options: RenderOptions): Iterable<Segment>;
|
|
52
|
-
measure(_options: RenderOptions): {
|
|
53
|
-
minimum: number;
|
|
54
|
-
maximum: number;
|
|
55
|
-
};
|
|
56
|
-
private resolvePalette;
|
|
57
|
-
}
|