@termuijs/widgets 0.1.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/dist/index.cjs +998 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +469 -0
- package/dist/index.d.ts +469 -0
- package/dist/index.js +968 -0
- package/dist/index.js.map +1 -0
- package/package.json +43 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,998 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
Box: () => Box,
|
|
24
|
+
Gauge: () => Gauge,
|
|
25
|
+
List: () => List,
|
|
26
|
+
LogView: () => LogView,
|
|
27
|
+
ProgressBar: () => ProgressBar,
|
|
28
|
+
SPINNER_FRAMES: () => SPINNER_FRAMES,
|
|
29
|
+
Sparkline: () => Sparkline,
|
|
30
|
+
Spinner: () => Spinner,
|
|
31
|
+
StatusIndicator: () => StatusIndicator,
|
|
32
|
+
Table: () => Table,
|
|
33
|
+
Text: () => Text,
|
|
34
|
+
TextInput: () => TextInput,
|
|
35
|
+
Widget: () => Widget
|
|
36
|
+
});
|
|
37
|
+
module.exports = __toCommonJS(index_exports);
|
|
38
|
+
|
|
39
|
+
// src/base/Widget.ts
|
|
40
|
+
var import_core = require("@termuijs/core");
|
|
41
|
+
var _widgetIdCounter = 0;
|
|
42
|
+
var Widget = class {
|
|
43
|
+
/** Unique widget identifier */
|
|
44
|
+
id;
|
|
45
|
+
/** Widget's style */
|
|
46
|
+
_style;
|
|
47
|
+
/** Child widgets */
|
|
48
|
+
_children = [];
|
|
49
|
+
/** Parent widget (null for root) */
|
|
50
|
+
parent = null;
|
|
51
|
+
/** Computed layout rectangle */
|
|
52
|
+
_rect = { x: 0, y: 0, width: 0, height: 0 };
|
|
53
|
+
/** Reference to the layout node (set during getLayoutNode) */
|
|
54
|
+
_layoutNode = null;
|
|
55
|
+
/** Whether this widget can receive focus */
|
|
56
|
+
focusable = false;
|
|
57
|
+
/** Tab index for focus ordering */
|
|
58
|
+
tabIndex = 0;
|
|
59
|
+
/** Event emitter for this widget */
|
|
60
|
+
events = new import_core.EventEmitter();
|
|
61
|
+
/** Whether the widget is currently focused */
|
|
62
|
+
isFocused = false;
|
|
63
|
+
/**
|
|
64
|
+
* Dirty flag — true when this widget needs re-rendering.
|
|
65
|
+
* Newly created widgets start dirty.
|
|
66
|
+
*/
|
|
67
|
+
_dirty = true;
|
|
68
|
+
constructor(style = {}) {
|
|
69
|
+
this.id = `widget_${++_widgetIdCounter}`;
|
|
70
|
+
this._style = (0, import_core.mergeStyles)((0, import_core.defaultStyle)(), style);
|
|
71
|
+
}
|
|
72
|
+
/** Get the current style */
|
|
73
|
+
get style() {
|
|
74
|
+
return this._style;
|
|
75
|
+
}
|
|
76
|
+
/** Update the style (merge with existing) */
|
|
77
|
+
setStyle(style) {
|
|
78
|
+
this._style = (0, import_core.mergeStyles)(this._style, style);
|
|
79
|
+
this.markDirty();
|
|
80
|
+
}
|
|
81
|
+
/** Get the computed rect after layout */
|
|
82
|
+
get rect() {
|
|
83
|
+
return this._rect;
|
|
84
|
+
}
|
|
85
|
+
/** Add a child widget */
|
|
86
|
+
addChild(child) {
|
|
87
|
+
child.parent = this;
|
|
88
|
+
this._children.push(child);
|
|
89
|
+
}
|
|
90
|
+
/** Remove a child widget */
|
|
91
|
+
removeChild(child) {
|
|
92
|
+
const idx = this._children.indexOf(child);
|
|
93
|
+
if (idx >= 0) {
|
|
94
|
+
this._children.splice(idx, 1);
|
|
95
|
+
child.parent = null;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
/** Remove all children */
|
|
99
|
+
clearChildren() {
|
|
100
|
+
for (const child of this._children) {
|
|
101
|
+
child.parent = null;
|
|
102
|
+
}
|
|
103
|
+
this._children = [];
|
|
104
|
+
}
|
|
105
|
+
/** Get all children */
|
|
106
|
+
get children() {
|
|
107
|
+
return this._children;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Build the LayoutNode tree for this widget.
|
|
111
|
+
* Stores a reference so we can sync computed rects back via syncLayout().
|
|
112
|
+
*/
|
|
113
|
+
getLayoutNode() {
|
|
114
|
+
const childNodes = this._children.filter((c) => c.style.visible !== false).map((c) => c.getLayoutNode());
|
|
115
|
+
this._layoutNode = (0, import_core.createLayoutNode)(this.id, this._style, childNodes);
|
|
116
|
+
return this._layoutNode;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* After computeLayout() has been called, sync the computed rects
|
|
120
|
+
* from the layout tree back into widget `_rect` fields.
|
|
121
|
+
* This MUST be called after computeLayout() and before render().
|
|
122
|
+
*/
|
|
123
|
+
syncLayout() {
|
|
124
|
+
if (this._layoutNode) {
|
|
125
|
+
this._rect = { ...this._layoutNode.computed };
|
|
126
|
+
}
|
|
127
|
+
const visibleChildren = this._children.filter((c) => c.style.visible !== false);
|
|
128
|
+
for (let i = 0; i < visibleChildren.length; i++) {
|
|
129
|
+
visibleChildren[i].syncLayout();
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Render this widget (and children) into the screen buffer.
|
|
134
|
+
* Automatically pushes a clip region if overflow is hidden (default).
|
|
135
|
+
*/
|
|
136
|
+
render(screen) {
|
|
137
|
+
if (this._style.visible === false) return;
|
|
138
|
+
const shouldClip = this._style.overflow !== "visible";
|
|
139
|
+
if (shouldClip) {
|
|
140
|
+
screen.pushClip(this._rect);
|
|
141
|
+
}
|
|
142
|
+
this._renderSelf(screen);
|
|
143
|
+
this._renderBorder(screen);
|
|
144
|
+
for (const child of this._children) {
|
|
145
|
+
child.render(screen);
|
|
146
|
+
}
|
|
147
|
+
if (shouldClip) {
|
|
148
|
+
screen.popClip();
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Update the computed rect from layout results.
|
|
153
|
+
*/
|
|
154
|
+
updateRect(rect) {
|
|
155
|
+
this._rect = rect;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Mark this widget as needing re-render.
|
|
159
|
+
* Propagates up to parent so the render loop can detect changes.
|
|
160
|
+
*/
|
|
161
|
+
markDirty() {
|
|
162
|
+
if (this._dirty) return;
|
|
163
|
+
this._dirty = true;
|
|
164
|
+
this.parent?.markDirty();
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Clear the dirty flag after rendering.
|
|
168
|
+
*/
|
|
169
|
+
clearDirty() {
|
|
170
|
+
this._dirty = false;
|
|
171
|
+
for (const child of this._children) {
|
|
172
|
+
child.clearDirty();
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
/** Check if this widget (or any child) needs re-rendering */
|
|
176
|
+
get isDirty() {
|
|
177
|
+
return this._dirty;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Render the border around this widget, including focus ring if focused.
|
|
181
|
+
*/
|
|
182
|
+
_renderBorder(screen) {
|
|
183
|
+
const border = this._style.border;
|
|
184
|
+
const hasBorder = border && border !== "none";
|
|
185
|
+
const showFocusRing = this.isFocused && this.focusable && this._style.focusRingStyle !== "none";
|
|
186
|
+
if (!hasBorder && !showFocusRing) return;
|
|
187
|
+
const { x, y, width, height } = this._rect;
|
|
188
|
+
if (width < 2 || height < 2) return;
|
|
189
|
+
if (hasBorder) {
|
|
190
|
+
const chars = (0, import_core.getBorderChars)(border);
|
|
191
|
+
if (!chars) return;
|
|
192
|
+
const attrs = (0, import_core.styleToCellAttrs)(this._style);
|
|
193
|
+
const borderFg = this._style.borderColor ?? attrs.fg;
|
|
194
|
+
const fg = showFocusRing ? this._style.focusRingColor ?? { type: "named", name: "cyan" } : borderFg;
|
|
195
|
+
const cellStyle = { fg };
|
|
196
|
+
screen.setCell(x, y, { char: chars.topLeft, ...cellStyle });
|
|
197
|
+
for (let c = 1; c < width - 1; c++) {
|
|
198
|
+
screen.setCell(x + c, y, { char: chars.top, ...cellStyle });
|
|
199
|
+
}
|
|
200
|
+
screen.setCell(x + width - 1, y, { char: chars.topRight, ...cellStyle });
|
|
201
|
+
screen.setCell(x, y + height - 1, { char: chars.bottomLeft, ...cellStyle });
|
|
202
|
+
for (let c = 1; c < width - 1; c++) {
|
|
203
|
+
screen.setCell(x + c, y + height - 1, { char: chars.bottom, ...cellStyle });
|
|
204
|
+
}
|
|
205
|
+
screen.setCell(x + width - 1, y + height - 1, { char: chars.bottomRight, ...cellStyle });
|
|
206
|
+
for (let r = 1; r < height - 1; r++) {
|
|
207
|
+
screen.setCell(x, y + r, { char: chars.left, ...cellStyle });
|
|
208
|
+
screen.setCell(x + width - 1, y + r, { char: chars.right, ...cellStyle });
|
|
209
|
+
}
|
|
210
|
+
} else if (showFocusRing) {
|
|
211
|
+
const fg = this._style.focusRingColor ?? { type: "named", name: "cyan" };
|
|
212
|
+
const cellStyle = { fg, bold: true };
|
|
213
|
+
screen.setCell(x, y, { char: "\u250C", ...cellStyle });
|
|
214
|
+
if (width > 2) screen.setCell(x + 1, y, { char: "\u2500", ...cellStyle });
|
|
215
|
+
screen.setCell(x + width - 1, y, { char: "\u2510", ...cellStyle });
|
|
216
|
+
if (width > 2) screen.setCell(x + width - 2, y, { char: "\u2500", ...cellStyle });
|
|
217
|
+
screen.setCell(x, y + height - 1, { char: "\u2514", ...cellStyle });
|
|
218
|
+
if (width > 2) screen.setCell(x + 1, y + height - 1, { char: "\u2500", ...cellStyle });
|
|
219
|
+
screen.setCell(x + width - 1, y + height - 1, { char: "\u2518", ...cellStyle });
|
|
220
|
+
if (width > 2) screen.setCell(x + width - 2, y + height - 1, { char: "\u2500", ...cellStyle });
|
|
221
|
+
if (height > 2) {
|
|
222
|
+
screen.setCell(x, y + 1, { char: "\u2502", ...cellStyle });
|
|
223
|
+
screen.setCell(x + width - 1, y + 1, { char: "\u2502", ...cellStyle });
|
|
224
|
+
screen.setCell(x, y + height - 2, { char: "\u2502", ...cellStyle });
|
|
225
|
+
screen.setCell(x + width - 1, y + height - 2, { char: "\u2502", ...cellStyle });
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Get the inner content area (after border + padding).
|
|
231
|
+
*/
|
|
232
|
+
_getContentRect() {
|
|
233
|
+
const padding = (0, import_core.normalizeEdges)(this._style.padding);
|
|
234
|
+
const border = this._style.border && this._style.border !== "none" ? 1 : 0;
|
|
235
|
+
return {
|
|
236
|
+
x: this._rect.x + padding.left + border,
|
|
237
|
+
y: this._rect.y + padding.top + border,
|
|
238
|
+
width: Math.max(0, this._rect.width - padding.left - padding.right - border * 2),
|
|
239
|
+
height: Math.max(0, this._rect.height - padding.top - padding.bottom - border * 2)
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Check if a point hits this widget.
|
|
244
|
+
*/
|
|
245
|
+
hitTest(x, y) {
|
|
246
|
+
return (0, import_core.containsPoint)(this._rect, x, y);
|
|
247
|
+
}
|
|
248
|
+
/** Lifecycle: called when the widget is mounted */
|
|
249
|
+
mount() {
|
|
250
|
+
this.events.emit("mount", void 0);
|
|
251
|
+
for (const child of this._children) {
|
|
252
|
+
child.mount();
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
/** Lifecycle: called when the widget is unmounted */
|
|
256
|
+
unmount() {
|
|
257
|
+
for (const child of this._children) {
|
|
258
|
+
child.unmount();
|
|
259
|
+
}
|
|
260
|
+
this.events.emit("unmount", void 0);
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
// src/display/Box.ts
|
|
265
|
+
var import_core2 = require("@termuijs/core");
|
|
266
|
+
var Box = class extends Widget {
|
|
267
|
+
constructor(style = {}) {
|
|
268
|
+
super(style);
|
|
269
|
+
}
|
|
270
|
+
_renderSelf(screen) {
|
|
271
|
+
const { bg } = (0, import_core2.styleToCellAttrs)(this._style);
|
|
272
|
+
if (bg.type === "none") return;
|
|
273
|
+
const { x, y, width, height } = this._rect;
|
|
274
|
+
const border = this._style.border && this._style.border !== "none" ? 1 : 0;
|
|
275
|
+
for (let r = border; r < height - border; r++) {
|
|
276
|
+
for (let c = border; c < width - border; c++) {
|
|
277
|
+
screen.setCell(x + c, y + r, { char: " ", bg });
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
// src/display/Text.ts
|
|
284
|
+
var import_core3 = require("@termuijs/core");
|
|
285
|
+
var Text = class extends Widget {
|
|
286
|
+
_content;
|
|
287
|
+
_wrap;
|
|
288
|
+
_align;
|
|
289
|
+
constructor(content, style = {}, props = {}) {
|
|
290
|
+
super(style);
|
|
291
|
+
this._content = content;
|
|
292
|
+
this._wrap = props.wrap ?? true;
|
|
293
|
+
this._align = props.align ?? "left";
|
|
294
|
+
}
|
|
295
|
+
/** Update the text content */
|
|
296
|
+
setContent(content) {
|
|
297
|
+
this._content = content;
|
|
298
|
+
}
|
|
299
|
+
/** Get current text content */
|
|
300
|
+
getContent() {
|
|
301
|
+
return this._content;
|
|
302
|
+
}
|
|
303
|
+
_renderSelf(screen) {
|
|
304
|
+
const contentRect = this._getContentRect();
|
|
305
|
+
const { x, y, width, height } = contentRect;
|
|
306
|
+
if (width <= 0 || height <= 0) return;
|
|
307
|
+
const attrs = (0, import_core3.styleToCellAttrs)(this._style);
|
|
308
|
+
let text = this._wrap ? (0, import_core3.wordWrap)(this._content, width) : this._content;
|
|
309
|
+
const lines = text.split("\n");
|
|
310
|
+
for (let i = 0; i < Math.min(lines.length, height); i++) {
|
|
311
|
+
let line = lines[i];
|
|
312
|
+
const lineWidth = (0, import_core3.stringWidth)(line);
|
|
313
|
+
let offsetX = 0;
|
|
314
|
+
if (this._align === "center") {
|
|
315
|
+
offsetX = Math.floor((width - lineWidth) / 2);
|
|
316
|
+
} else if (this._align === "right") {
|
|
317
|
+
offsetX = width - lineWidth;
|
|
318
|
+
}
|
|
319
|
+
screen.writeString(x + Math.max(0, offsetX), y + i, line, attrs);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
};
|
|
323
|
+
|
|
324
|
+
// src/display/LogView.ts
|
|
325
|
+
var import_core4 = require("@termuijs/core");
|
|
326
|
+
var LogView = class extends Widget {
|
|
327
|
+
_lines = [];
|
|
328
|
+
_scrollOffset = 0;
|
|
329
|
+
_highlight;
|
|
330
|
+
_autoScroll;
|
|
331
|
+
constructor(style = {}, opts = {}) {
|
|
332
|
+
super(style);
|
|
333
|
+
this._highlight = opts.highlight ?? {
|
|
334
|
+
ERROR: { type: "named", name: "red" },
|
|
335
|
+
WARN: { type: "named", name: "yellow" },
|
|
336
|
+
INFO: { type: "named", name: "green" },
|
|
337
|
+
DEBUG: { type: "named", name: "brightBlack" }
|
|
338
|
+
};
|
|
339
|
+
this._autoScroll = opts.autoScroll ?? true;
|
|
340
|
+
}
|
|
341
|
+
setLines(lines) {
|
|
342
|
+
this._lines = lines;
|
|
343
|
+
if (this._autoScroll) {
|
|
344
|
+
this._scrollToBottom();
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
appendLine(line) {
|
|
348
|
+
this._lines.push(line);
|
|
349
|
+
if (this._autoScroll) {
|
|
350
|
+
this._scrollToBottom();
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
scrollUp(n = 1) {
|
|
354
|
+
this._scrollOffset = Math.max(0, this._scrollOffset - n);
|
|
355
|
+
}
|
|
356
|
+
scrollDown(n = 1) {
|
|
357
|
+
this._scrollOffset = Math.min(
|
|
358
|
+
Math.max(0, this._lines.length - 1),
|
|
359
|
+
this._scrollOffset + n
|
|
360
|
+
);
|
|
361
|
+
}
|
|
362
|
+
_scrollToBottom() {
|
|
363
|
+
const rect = this._getContentRect();
|
|
364
|
+
const visibleLines = Math.max(1, rect.height);
|
|
365
|
+
this._scrollOffset = Math.max(0, this._lines.length - visibleLines);
|
|
366
|
+
}
|
|
367
|
+
_renderSelf(screen) {
|
|
368
|
+
const rect = this._getContentRect();
|
|
369
|
+
const { x, y, width, height } = rect;
|
|
370
|
+
if (width <= 0 || height <= 0) return;
|
|
371
|
+
const attrs = (0, import_core4.styleToCellAttrs)(this._style);
|
|
372
|
+
const visibleLines = this._lines.slice(this._scrollOffset, this._scrollOffset + height);
|
|
373
|
+
for (let i = 0; i < Math.min(visibleLines.length, height); i++) {
|
|
374
|
+
const line = (0, import_core4.truncate)(visibleLines[i], width);
|
|
375
|
+
const lineColor = this._getLineColor(line);
|
|
376
|
+
screen.writeString(x, y + i, line, {
|
|
377
|
+
...attrs,
|
|
378
|
+
...lineColor ? { fg: lineColor } : {}
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
_getLineColor(line) {
|
|
383
|
+
for (const [keyword, color] of Object.entries(this._highlight)) {
|
|
384
|
+
if (line.includes(keyword)) return color;
|
|
385
|
+
}
|
|
386
|
+
return null;
|
|
387
|
+
}
|
|
388
|
+
};
|
|
389
|
+
|
|
390
|
+
// src/input/List.ts
|
|
391
|
+
var import_core5 = require("@termuijs/core");
|
|
392
|
+
var List = class extends Widget {
|
|
393
|
+
_items;
|
|
394
|
+
_selectedIndex = 0;
|
|
395
|
+
_scrollOffset = 0;
|
|
396
|
+
_onSelect;
|
|
397
|
+
constructor(items, style = {}, onSelect) {
|
|
398
|
+
super({ border: "single", ...style });
|
|
399
|
+
this._items = items;
|
|
400
|
+
this._onSelect = onSelect;
|
|
401
|
+
this.focusable = true;
|
|
402
|
+
}
|
|
403
|
+
get selectedIndex() {
|
|
404
|
+
return this._selectedIndex;
|
|
405
|
+
}
|
|
406
|
+
get selectedItem() {
|
|
407
|
+
return this._items[this._selectedIndex];
|
|
408
|
+
}
|
|
409
|
+
setItems(items) {
|
|
410
|
+
this._items = items;
|
|
411
|
+
this._selectedIndex = Math.min(this._selectedIndex, items.length - 1);
|
|
412
|
+
this._clampScroll();
|
|
413
|
+
}
|
|
414
|
+
/** Move selection up */
|
|
415
|
+
selectPrev() {
|
|
416
|
+
let next = this._selectedIndex - 1;
|
|
417
|
+
while (next >= 0 && this._items[next].disabled) next--;
|
|
418
|
+
if (next >= 0) {
|
|
419
|
+
this._selectedIndex = next;
|
|
420
|
+
this._clampScroll();
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
/** Move selection down */
|
|
424
|
+
selectNext() {
|
|
425
|
+
let next = this._selectedIndex + 1;
|
|
426
|
+
while (next < this._items.length && this._items[next].disabled) next++;
|
|
427
|
+
if (next < this._items.length) {
|
|
428
|
+
this._selectedIndex = next;
|
|
429
|
+
this._clampScroll();
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
/** Confirm the current selection */
|
|
433
|
+
confirm() {
|
|
434
|
+
const item = this._items[this._selectedIndex];
|
|
435
|
+
if (item && !item.disabled) {
|
|
436
|
+
this._onSelect?.(item, this._selectedIndex);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
_renderSelf(screen) {
|
|
440
|
+
const rect = this._getContentRect();
|
|
441
|
+
const { x, y, width, height } = rect;
|
|
442
|
+
if (width <= 0 || height <= 0) return;
|
|
443
|
+
const attrs = (0, import_core5.styleToCellAttrs)(this._style);
|
|
444
|
+
const visibleCount = Math.min(this._items.length - this._scrollOffset, height);
|
|
445
|
+
for (let i = 0; i < visibleCount; i++) {
|
|
446
|
+
const itemIdx = this._scrollOffset + i;
|
|
447
|
+
const item = this._items[itemIdx];
|
|
448
|
+
const isSelected = itemIdx === this._selectedIndex;
|
|
449
|
+
const prefix = isSelected ? "\u25B8 " : " ";
|
|
450
|
+
let line = prefix + item.label;
|
|
451
|
+
line = (0, import_core5.truncate)(line, width);
|
|
452
|
+
const cellStyle = {
|
|
453
|
+
...attrs,
|
|
454
|
+
bold: isSelected,
|
|
455
|
+
dim: item.disabled ?? false,
|
|
456
|
+
inverse: isSelected && this.isFocused
|
|
457
|
+
};
|
|
458
|
+
screen.writeString(x, y + i, line, cellStyle);
|
|
459
|
+
if (isSelected && this.isFocused) {
|
|
460
|
+
const remaining = width - (0, import_core5.stringWidth)(line);
|
|
461
|
+
for (let c = 0; c < remaining; c++) {
|
|
462
|
+
screen.setCell(x + (0, import_core5.stringWidth)(line) + c, y + i, { char: " ", ...cellStyle });
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
if (this._items.length > height) {
|
|
467
|
+
const scrollRatio = this._scrollOffset / (this._items.length - height);
|
|
468
|
+
const scrollPos = Math.floor(scrollRatio * (height - 1));
|
|
469
|
+
for (let r = 0; r < height; r++) {
|
|
470
|
+
const scrollChar = r === scrollPos ? "\u2588" : "\u2591";
|
|
471
|
+
screen.setCell(x + width - 1, y + r, { char: scrollChar, ...attrs, dim: true });
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
_clampScroll() {
|
|
476
|
+
const rect = this._getContentRect();
|
|
477
|
+
const visibleHeight = rect.height;
|
|
478
|
+
if (visibleHeight <= 0) return;
|
|
479
|
+
if (this._selectedIndex < this._scrollOffset) {
|
|
480
|
+
this._scrollOffset = this._selectedIndex;
|
|
481
|
+
}
|
|
482
|
+
if (this._selectedIndex >= this._scrollOffset + visibleHeight) {
|
|
483
|
+
this._scrollOffset = this._selectedIndex - visibleHeight + 1;
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
};
|
|
487
|
+
|
|
488
|
+
// src/input/TextInput.ts
|
|
489
|
+
var import_core6 = require("@termuijs/core");
|
|
490
|
+
var TextInput = class extends Widget {
|
|
491
|
+
_value = "";
|
|
492
|
+
_cursorPos = 0;
|
|
493
|
+
_placeholder;
|
|
494
|
+
_mask;
|
|
495
|
+
_maxLength;
|
|
496
|
+
_onChange;
|
|
497
|
+
_onSubmit;
|
|
498
|
+
constructor(style = {}, options = {}) {
|
|
499
|
+
super({ border: "single", height: 3, ...style });
|
|
500
|
+
this._placeholder = options.placeholder ?? "";
|
|
501
|
+
this._mask = options.mask ?? null;
|
|
502
|
+
this._maxLength = options.maxLength ?? Infinity;
|
|
503
|
+
this._onChange = options.onChange;
|
|
504
|
+
this._onSubmit = options.onSubmit;
|
|
505
|
+
this.focusable = true;
|
|
506
|
+
}
|
|
507
|
+
get value() {
|
|
508
|
+
return this._value;
|
|
509
|
+
}
|
|
510
|
+
set value(v) {
|
|
511
|
+
this._value = v.slice(0, this._maxLength);
|
|
512
|
+
this._cursorPos = Math.min(this._cursorPos, this._value.length);
|
|
513
|
+
}
|
|
514
|
+
/**
|
|
515
|
+
* Handle a typed character.
|
|
516
|
+
*/
|
|
517
|
+
insertChar(char) {
|
|
518
|
+
if (this._value.length >= this._maxLength) return;
|
|
519
|
+
this._value = this._value.slice(0, this._cursorPos) + char + this._value.slice(this._cursorPos);
|
|
520
|
+
this._cursorPos++;
|
|
521
|
+
this._onChange?.(this._value);
|
|
522
|
+
}
|
|
523
|
+
/**
|
|
524
|
+
* Delete the character before the cursor.
|
|
525
|
+
*/
|
|
526
|
+
deleteBack() {
|
|
527
|
+
if (this._cursorPos > 0) {
|
|
528
|
+
this._value = this._value.slice(0, this._cursorPos - 1) + this._value.slice(this._cursorPos);
|
|
529
|
+
this._cursorPos--;
|
|
530
|
+
this._onChange?.(this._value);
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
/**
|
|
534
|
+
* Delete the character after the cursor.
|
|
535
|
+
*/
|
|
536
|
+
deleteForward() {
|
|
537
|
+
if (this._cursorPos < this._value.length) {
|
|
538
|
+
this._value = this._value.slice(0, this._cursorPos) + this._value.slice(this._cursorPos + 1);
|
|
539
|
+
this._onChange?.(this._value);
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
moveCursorLeft() {
|
|
543
|
+
this._cursorPos = Math.max(0, this._cursorPos - 1);
|
|
544
|
+
}
|
|
545
|
+
moveCursorRight() {
|
|
546
|
+
this._cursorPos = Math.min(this._value.length, this._cursorPos + 1);
|
|
547
|
+
}
|
|
548
|
+
moveCursorHome() {
|
|
549
|
+
this._cursorPos = 0;
|
|
550
|
+
}
|
|
551
|
+
moveCursorEnd() {
|
|
552
|
+
this._cursorPos = this._value.length;
|
|
553
|
+
}
|
|
554
|
+
submit() {
|
|
555
|
+
this._onSubmit?.(this._value);
|
|
556
|
+
}
|
|
557
|
+
clear() {
|
|
558
|
+
this._value = "";
|
|
559
|
+
this._cursorPos = 0;
|
|
560
|
+
this._onChange?.("");
|
|
561
|
+
}
|
|
562
|
+
_renderSelf(screen) {
|
|
563
|
+
const rect = this._getContentRect();
|
|
564
|
+
const { x, y, width, height } = rect;
|
|
565
|
+
if (width <= 0 || height <= 0) return;
|
|
566
|
+
const attrs = (0, import_core6.styleToCellAttrs)(this._style);
|
|
567
|
+
if (this._value.length === 0 && !this.isFocused) {
|
|
568
|
+
screen.writeString(x, y, (0, import_core6.truncate)(this._placeholder, width), { ...attrs, dim: true });
|
|
569
|
+
return;
|
|
570
|
+
}
|
|
571
|
+
const displayValue = this._mask ? this._mask.repeat(this._value.length) : this._value;
|
|
572
|
+
const visibleWidth = width - 1;
|
|
573
|
+
let scrollX = 0;
|
|
574
|
+
if (this._cursorPos > visibleWidth) {
|
|
575
|
+
scrollX = this._cursorPos - visibleWidth;
|
|
576
|
+
}
|
|
577
|
+
const visibleText = displayValue.slice(scrollX, scrollX + visibleWidth);
|
|
578
|
+
screen.writeString(x, y, visibleText, attrs);
|
|
579
|
+
if (this.isFocused) {
|
|
580
|
+
const cursorScreenPos = x + this._cursorPos - scrollX;
|
|
581
|
+
if (cursorScreenPos >= x && cursorScreenPos < x + width) {
|
|
582
|
+
const cursorChar = this._cursorPos < displayValue.length ? displayValue[this._cursorPos] : " ";
|
|
583
|
+
screen.setCell(cursorScreenPos, y, {
|
|
584
|
+
char: cursorChar,
|
|
585
|
+
...attrs,
|
|
586
|
+
inverse: true
|
|
587
|
+
});
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
};
|
|
592
|
+
|
|
593
|
+
// src/data/Table.ts
|
|
594
|
+
var import_core7 = require("@termuijs/core");
|
|
595
|
+
var Table = class extends Widget {
|
|
596
|
+
_columns;
|
|
597
|
+
_rows;
|
|
598
|
+
_showHeader;
|
|
599
|
+
_headerColor;
|
|
600
|
+
_stripe;
|
|
601
|
+
_stripeColor;
|
|
602
|
+
_separator;
|
|
603
|
+
constructor(columns, rows, style = {}, options = {}) {
|
|
604
|
+
super(style);
|
|
605
|
+
this._columns = columns;
|
|
606
|
+
this._rows = rows;
|
|
607
|
+
this._showHeader = options.showHeader ?? true;
|
|
608
|
+
this._headerColor = options.headerColor ?? { type: "named", name: "cyan" };
|
|
609
|
+
this._stripe = options.stripe ?? true;
|
|
610
|
+
this._stripeColor = options.stripeColor ?? { type: "named", name: "brightBlack" };
|
|
611
|
+
this._separator = options.separator ?? " \u2502 ";
|
|
612
|
+
}
|
|
613
|
+
setRows(rows) {
|
|
614
|
+
this._rows = rows;
|
|
615
|
+
}
|
|
616
|
+
_renderSelf(screen) {
|
|
617
|
+
const rect = this._getContentRect();
|
|
618
|
+
const { x, y, width, height } = rect;
|
|
619
|
+
if (width <= 0 || height <= 0) return;
|
|
620
|
+
const attrs = (0, import_core7.styleToCellAttrs)(this._style);
|
|
621
|
+
const sepWidth = (0, import_core7.stringWidth)(this._separator);
|
|
622
|
+
const colWidths = this._computeColumnWidths(
|
|
623
|
+
width - (this._columns.length - 1) * sepWidth
|
|
624
|
+
);
|
|
625
|
+
let row = 0;
|
|
626
|
+
if (this._showHeader && row < height) {
|
|
627
|
+
let cx = x;
|
|
628
|
+
for (let c = 0; c < this._columns.length; c++) {
|
|
629
|
+
const col = this._columns[c];
|
|
630
|
+
const cellText = this._alignText(col.header, colWidths[c], col.align ?? "left");
|
|
631
|
+
screen.writeString(cx, y + row, cellText, {
|
|
632
|
+
...attrs,
|
|
633
|
+
fg: this._headerColor,
|
|
634
|
+
bold: true
|
|
635
|
+
});
|
|
636
|
+
cx += colWidths[c];
|
|
637
|
+
if (c < this._columns.length - 1) {
|
|
638
|
+
screen.writeString(cx, y + row, this._separator, { ...attrs, dim: true });
|
|
639
|
+
cx += sepWidth;
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
row++;
|
|
643
|
+
if (row < height) {
|
|
644
|
+
const sepLine = "\u2500".repeat(width);
|
|
645
|
+
screen.writeString(x, y + row, sepLine, { ...attrs, dim: true });
|
|
646
|
+
row++;
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
for (let r = 0; r < this._rows.length && row < height; r++) {
|
|
650
|
+
const dataRow = this._rows[r];
|
|
651
|
+
const isStripe = this._stripe && r % 2 === 1;
|
|
652
|
+
let cx = x;
|
|
653
|
+
for (let c = 0; c < this._columns.length; c++) {
|
|
654
|
+
const col = this._columns[c];
|
|
655
|
+
const rawValue = String(dataRow[col.key] ?? "");
|
|
656
|
+
const cellText = this._alignText(rawValue, colWidths[c], col.align ?? "left");
|
|
657
|
+
screen.writeString(cx, y + row, cellText, {
|
|
658
|
+
...attrs,
|
|
659
|
+
bg: isStripe ? this._stripeColor : attrs.bg
|
|
660
|
+
});
|
|
661
|
+
cx += colWidths[c];
|
|
662
|
+
if (c < this._columns.length - 1) {
|
|
663
|
+
screen.writeString(cx, y + row, this._separator, {
|
|
664
|
+
...attrs,
|
|
665
|
+
dim: true,
|
|
666
|
+
bg: isStripe ? this._stripeColor : attrs.bg
|
|
667
|
+
});
|
|
668
|
+
cx += sepWidth;
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
if (isStripe) {
|
|
672
|
+
for (let fx = cx; fx < x + width; fx++) {
|
|
673
|
+
screen.setCell(fx, y + row, { char: " ", bg: this._stripeColor });
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
row++;
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
_computeColumnWidths(totalWidth) {
|
|
680
|
+
const fixedCols = this._columns.filter((c) => c.width !== void 0);
|
|
681
|
+
const flexCols = this._columns.filter((c) => c.width === void 0);
|
|
682
|
+
let usedWidth = fixedCols.reduce((sum, c) => sum + (c.width ?? 0), 0);
|
|
683
|
+
const remainingWidth = Math.max(0, totalWidth - usedWidth);
|
|
684
|
+
const flexWidth = flexCols.length > 0 ? Math.floor(remainingWidth / flexCols.length) : 0;
|
|
685
|
+
return this._columns.map((c) => c.width ?? flexWidth);
|
|
686
|
+
}
|
|
687
|
+
_alignText(text, width, align) {
|
|
688
|
+
const truncated = (0, import_core7.truncate)(text, width);
|
|
689
|
+
const textWidth = (0, import_core7.stringWidth)(truncated);
|
|
690
|
+
const pad = Math.max(0, width - textWidth);
|
|
691
|
+
switch (align) {
|
|
692
|
+
case "right":
|
|
693
|
+
return " ".repeat(pad) + truncated;
|
|
694
|
+
case "center": {
|
|
695
|
+
const left = Math.floor(pad / 2);
|
|
696
|
+
const right = pad - left;
|
|
697
|
+
return " ".repeat(left) + truncated + " ".repeat(right);
|
|
698
|
+
}
|
|
699
|
+
case "left":
|
|
700
|
+
default:
|
|
701
|
+
return truncated + " ".repeat(pad);
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
};
|
|
705
|
+
|
|
706
|
+
// src/data/Gauge.ts
|
|
707
|
+
var import_core8 = require("@termuijs/core");
|
|
708
|
+
var Gauge = class extends Widget {
|
|
709
|
+
_label;
|
|
710
|
+
_value = 0;
|
|
711
|
+
_color;
|
|
712
|
+
_showLabel;
|
|
713
|
+
constructor(label, style = {}, opts = {}) {
|
|
714
|
+
super(style);
|
|
715
|
+
this._label = label;
|
|
716
|
+
this._color = opts.color ?? { type: "named", name: "green" };
|
|
717
|
+
this._showLabel = opts.showLabel ?? true;
|
|
718
|
+
}
|
|
719
|
+
setValue(value) {
|
|
720
|
+
this._value = Math.max(0, Math.min(1, value));
|
|
721
|
+
}
|
|
722
|
+
getValue() {
|
|
723
|
+
return this._value;
|
|
724
|
+
}
|
|
725
|
+
setLabel(label) {
|
|
726
|
+
this._label = label;
|
|
727
|
+
}
|
|
728
|
+
_renderSelf(screen) {
|
|
729
|
+
const rect = this._getContentRect();
|
|
730
|
+
const { x, y, width, height } = rect;
|
|
731
|
+
if (width <= 0 || height <= 0) return;
|
|
732
|
+
const attrs = (0, import_core8.styleToCellAttrs)(this._style);
|
|
733
|
+
const labelStr = this._label + " ";
|
|
734
|
+
const percentStr = this._showLabel ? ` ${Math.round(this._value * 100)}%` : "";
|
|
735
|
+
const labelWidth = (0, import_core8.stringWidth)(labelStr);
|
|
736
|
+
const percentWidth = (0, import_core8.stringWidth)(percentStr);
|
|
737
|
+
const barWidth = Math.max(0, width - labelWidth - percentWidth);
|
|
738
|
+
screen.writeString(x, y, labelStr, { ...attrs, bold: true });
|
|
739
|
+
const filled = Math.round(barWidth * this._value);
|
|
740
|
+
const barX = x + labelWidth;
|
|
741
|
+
for (let i = 0; i < barWidth; i++) {
|
|
742
|
+
const char = i < filled ? "\u2588" : "\u2591";
|
|
743
|
+
screen.setCell(barX + i, y, {
|
|
744
|
+
char,
|
|
745
|
+
fg: i < filled ? this._color : { type: "named", name: "brightBlack" }
|
|
746
|
+
});
|
|
747
|
+
}
|
|
748
|
+
if (this._showLabel) {
|
|
749
|
+
screen.writeString(barX + barWidth, y, percentStr, {
|
|
750
|
+
...attrs,
|
|
751
|
+
bold: true
|
|
752
|
+
});
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
};
|
|
756
|
+
|
|
757
|
+
// src/data/Sparkline.ts
|
|
758
|
+
var import_core9 = require("@termuijs/core");
|
|
759
|
+
var SPARK_CHARS = ["\u2581", "\u2582", "\u2583", "\u2584", "\u2585", "\u2586", "\u2587", "\u2588"];
|
|
760
|
+
var Sparkline = class extends Widget {
|
|
761
|
+
_label;
|
|
762
|
+
_data = [];
|
|
763
|
+
_color;
|
|
764
|
+
_showRange;
|
|
765
|
+
constructor(label, style = {}, opts = {}) {
|
|
766
|
+
super(style);
|
|
767
|
+
this._label = label;
|
|
768
|
+
this._color = opts.color ?? { type: "named", name: "cyan" };
|
|
769
|
+
this._showRange = opts.showRange ?? false;
|
|
770
|
+
}
|
|
771
|
+
setData(data) {
|
|
772
|
+
this._data = data;
|
|
773
|
+
}
|
|
774
|
+
pushValue(value) {
|
|
775
|
+
this._data.push(value);
|
|
776
|
+
}
|
|
777
|
+
_renderSelf(screen) {
|
|
778
|
+
const rect = this._getContentRect();
|
|
779
|
+
const { x, y, width, height } = rect;
|
|
780
|
+
if (width <= 0 || height <= 0) return;
|
|
781
|
+
const attrs = (0, import_core9.styleToCellAttrs)(this._style);
|
|
782
|
+
const labelStr = this._label + " ";
|
|
783
|
+
const labelWidth = labelStr.length;
|
|
784
|
+
screen.writeString(x, y, labelStr, { ...attrs, bold: true });
|
|
785
|
+
const sparkWidth = width - labelWidth;
|
|
786
|
+
if (sparkWidth <= 0 || this._data.length === 0) return;
|
|
787
|
+
const data = this._data.slice(-sparkWidth);
|
|
788
|
+
const min = Math.min(...data);
|
|
789
|
+
const max = Math.max(...data);
|
|
790
|
+
const range = max - min || 1;
|
|
791
|
+
for (let i = 0; i < data.length; i++) {
|
|
792
|
+
const normalized = (data[i] - min) / range;
|
|
793
|
+
const charIdx = Math.min(7, Math.floor(normalized * 8));
|
|
794
|
+
screen.setCell(x + labelWidth + i, y, {
|
|
795
|
+
char: SPARK_CHARS[charIdx],
|
|
796
|
+
fg: this._color
|
|
797
|
+
});
|
|
798
|
+
}
|
|
799
|
+
if (this._showRange && height > 1) {
|
|
800
|
+
const rangeStr = `${min.toFixed(0)}\u2013${max.toFixed(0)}`;
|
|
801
|
+
screen.writeString(x + labelWidth, y + 1, rangeStr, {
|
|
802
|
+
...attrs,
|
|
803
|
+
dim: true
|
|
804
|
+
});
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
};
|
|
808
|
+
|
|
809
|
+
// src/data/StatusIndicator.ts
|
|
810
|
+
var import_core10 = require("@termuijs/core");
|
|
811
|
+
var StatusIndicator = class extends Widget {
|
|
812
|
+
_label;
|
|
813
|
+
_isUp;
|
|
814
|
+
_upColor;
|
|
815
|
+
_downColor;
|
|
816
|
+
constructor(label, isUp, style = {}, opts = {}) {
|
|
817
|
+
super(style);
|
|
818
|
+
this._label = label;
|
|
819
|
+
this._isUp = isUp;
|
|
820
|
+
this._upColor = opts.upColor ?? { type: "named", name: "green" };
|
|
821
|
+
this._downColor = opts.downColor ?? { type: "named", name: "red" };
|
|
822
|
+
}
|
|
823
|
+
setStatus(isUp) {
|
|
824
|
+
this._isUp = isUp;
|
|
825
|
+
}
|
|
826
|
+
getStatus() {
|
|
827
|
+
return this._isUp;
|
|
828
|
+
}
|
|
829
|
+
setLabel(label) {
|
|
830
|
+
this._label = label;
|
|
831
|
+
}
|
|
832
|
+
_renderSelf(screen) {
|
|
833
|
+
const rect = this._getContentRect();
|
|
834
|
+
const { x, y, width, height } = rect;
|
|
835
|
+
if (width <= 0 || height <= 0) return;
|
|
836
|
+
const attrs = (0, import_core10.styleToCellAttrs)(this._style);
|
|
837
|
+
const dot = this._isUp ? "\u25CF" : "\u25CB";
|
|
838
|
+
const statusText = this._isUp ? "Online" : "Offline";
|
|
839
|
+
const color = this._isUp ? this._upColor : this._downColor;
|
|
840
|
+
screen.setCell(x, y, { char: dot, fg: color });
|
|
841
|
+
screen.writeString(x + 2, y, `${this._label} \u2014 ${statusText}`, {
|
|
842
|
+
...attrs,
|
|
843
|
+
fg: color
|
|
844
|
+
});
|
|
845
|
+
}
|
|
846
|
+
};
|
|
847
|
+
|
|
848
|
+
// src/feedback/ProgressBar.ts
|
|
849
|
+
var import_core11 = require("@termuijs/core");
|
|
850
|
+
var ProgressBar = class extends Widget {
|
|
851
|
+
_value;
|
|
852
|
+
_fillChar;
|
|
853
|
+
_emptyChar;
|
|
854
|
+
_fillColor;
|
|
855
|
+
_showLabel;
|
|
856
|
+
_labelFormat;
|
|
857
|
+
_total;
|
|
858
|
+
constructor(style = {}, options = {}) {
|
|
859
|
+
super({ height: 1, ...style });
|
|
860
|
+
this._value = Math.max(0, Math.min(1, options.value ?? 0));
|
|
861
|
+
this._fillChar = options.fillChar ?? "\u2588";
|
|
862
|
+
this._emptyChar = options.emptyChar ?? "\u2591";
|
|
863
|
+
this._fillColor = options.fillColor ?? { type: "named", name: "green" };
|
|
864
|
+
this._showLabel = options.showLabel ?? true;
|
|
865
|
+
this._labelFormat = options.labelFormat ?? "percent";
|
|
866
|
+
this._total = options.total ?? 100;
|
|
867
|
+
}
|
|
868
|
+
/** Set progress value (0–1) */
|
|
869
|
+
setValue(value) {
|
|
870
|
+
this._value = Math.max(0, Math.min(1, value));
|
|
871
|
+
}
|
|
872
|
+
get value() {
|
|
873
|
+
return this._value;
|
|
874
|
+
}
|
|
875
|
+
_renderSelf(screen) {
|
|
876
|
+
const rect = this._getContentRect();
|
|
877
|
+
const { x, y, width } = rect;
|
|
878
|
+
if (width <= 0) return;
|
|
879
|
+
const attrs = (0, import_core11.styleToCellAttrs)(this._style);
|
|
880
|
+
let label = "";
|
|
881
|
+
if (this._showLabel) {
|
|
882
|
+
if (this._labelFormat === "percent") {
|
|
883
|
+
label = ` ${Math.round(this._value * 100)}%`;
|
|
884
|
+
} else {
|
|
885
|
+
label = ` ${Math.round(this._value * this._total)}/${this._total}`;
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
const barWidth = Math.max(0, width - label.length);
|
|
889
|
+
const filled = Math.round(barWidth * this._value);
|
|
890
|
+
const empty = barWidth - filled;
|
|
891
|
+
for (let i = 0; i < filled; i++) {
|
|
892
|
+
screen.setCell(x + i, y, { char: this._fillChar, ...attrs, fg: this._fillColor });
|
|
893
|
+
}
|
|
894
|
+
for (let i = 0; i < empty; i++) {
|
|
895
|
+
screen.setCell(x + filled + i, y, { char: this._emptyChar, ...attrs, dim: true });
|
|
896
|
+
}
|
|
897
|
+
if (label) {
|
|
898
|
+
screen.writeString(x + barWidth, y, label, { ...attrs, bold: true });
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
};
|
|
902
|
+
|
|
903
|
+
// src/feedback/Spinner.ts
|
|
904
|
+
var import_core12 = require("@termuijs/core");
|
|
905
|
+
var SPINNER_FRAMES = {
|
|
906
|
+
dots: {
|
|
907
|
+
frames: ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"],
|
|
908
|
+
interval: 80
|
|
909
|
+
},
|
|
910
|
+
line: {
|
|
911
|
+
frames: ["-", "\\", "|", "/"],
|
|
912
|
+
interval: 130
|
|
913
|
+
},
|
|
914
|
+
star: {
|
|
915
|
+
frames: ["\u2736", "\u2738", "\u2739", "\u273A", "\u2739", "\u2737"],
|
|
916
|
+
interval: 70
|
|
917
|
+
},
|
|
918
|
+
arc: {
|
|
919
|
+
frames: ["\u25DC", "\u25E0", "\u25DD", "\u25DE", "\u25E1", "\u25DF"],
|
|
920
|
+
interval: 100
|
|
921
|
+
},
|
|
922
|
+
circle: {
|
|
923
|
+
frames: ["\u25D0", "\u25D3", "\u25D1", "\u25D2"],
|
|
924
|
+
interval: 120
|
|
925
|
+
},
|
|
926
|
+
bounce: {
|
|
927
|
+
frames: ["\u2801", "\u2802", "\u2804", "\u2802"],
|
|
928
|
+
interval: 120
|
|
929
|
+
},
|
|
930
|
+
arrow: {
|
|
931
|
+
frames: ["\u2190", "\u2196", "\u2191", "\u2197", "\u2192", "\u2198", "\u2193", "\u2199"],
|
|
932
|
+
interval: 100
|
|
933
|
+
},
|
|
934
|
+
clock: {
|
|
935
|
+
frames: ["\u{1F550}", "\u{1F551}", "\u{1F552}", "\u{1F553}", "\u{1F554}", "\u{1F555}", "\u{1F556}", "\u{1F557}", "\u{1F558}", "\u{1F559}", "\u{1F55A}", "\u{1F55B}"],
|
|
936
|
+
interval: 100
|
|
937
|
+
}
|
|
938
|
+
};
|
|
939
|
+
var Spinner = class extends Widget {
|
|
940
|
+
_frames;
|
|
941
|
+
_interval;
|
|
942
|
+
_frameIndex = 0;
|
|
943
|
+
_label;
|
|
944
|
+
_color;
|
|
945
|
+
_lastTick = 0;
|
|
946
|
+
_elapsed = 0;
|
|
947
|
+
constructor(style = {}, options = {}) {
|
|
948
|
+
super({ height: 1, ...style });
|
|
949
|
+
const spinnerDef = typeof options.spinner === "string" ? SPINNER_FRAMES[options.spinner] ?? SPINNER_FRAMES.dots : options.spinner ?? SPINNER_FRAMES.dots;
|
|
950
|
+
this._frames = spinnerDef.frames;
|
|
951
|
+
this._interval = spinnerDef.interval;
|
|
952
|
+
this._label = options.label ?? "";
|
|
953
|
+
this._color = options.color ?? { type: "named", name: "cyan" };
|
|
954
|
+
}
|
|
955
|
+
/** Update the spinner label */
|
|
956
|
+
setLabel(label) {
|
|
957
|
+
this._label = label;
|
|
958
|
+
}
|
|
959
|
+
/**
|
|
960
|
+
* Advance the spinner frame based on elapsed time.
|
|
961
|
+
* Call this with a delta (ms) from the render loop.
|
|
962
|
+
*/
|
|
963
|
+
tick(deltaMs) {
|
|
964
|
+
this._elapsed += deltaMs;
|
|
965
|
+
if (this._elapsed >= this._interval) {
|
|
966
|
+
this._frameIndex = (this._frameIndex + 1) % this._frames.length;
|
|
967
|
+
this._elapsed = 0;
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
_renderSelf(screen) {
|
|
971
|
+
const rect = this._getContentRect();
|
|
972
|
+
const { x, y, width } = rect;
|
|
973
|
+
if (width <= 0) return;
|
|
974
|
+
const attrs = (0, import_core12.styleToCellAttrs)(this._style);
|
|
975
|
+
const frame = this._frames[this._frameIndex];
|
|
976
|
+
screen.writeString(x, y, frame, { ...attrs, fg: this._color });
|
|
977
|
+
if (this._label) {
|
|
978
|
+
screen.writeString(x + 2, y, this._label, attrs);
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
};
|
|
982
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
983
|
+
0 && (module.exports = {
|
|
984
|
+
Box,
|
|
985
|
+
Gauge,
|
|
986
|
+
List,
|
|
987
|
+
LogView,
|
|
988
|
+
ProgressBar,
|
|
989
|
+
SPINNER_FRAMES,
|
|
990
|
+
Sparkline,
|
|
991
|
+
Spinner,
|
|
992
|
+
StatusIndicator,
|
|
993
|
+
Table,
|
|
994
|
+
Text,
|
|
995
|
+
TextInput,
|
|
996
|
+
Widget
|
|
997
|
+
});
|
|
998
|
+
//# sourceMappingURL=index.cjs.map
|