@termuijs/ui 0.1.3 → 0.1.5
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 +100 -19
- package/dist/index.cjs +977 -31
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +277 -2
- package/dist/index.d.ts +277 -2
- package/dist/index.js +952 -26
- package/dist/index.js.map +1 -1
- package/package.json +12 -7
- package/LICENSE +0 -21
package/dist/index.cjs
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
2
3
|
var __defProp = Object.defineProperty;
|
|
3
4
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
5
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
8
|
var __export = (target, all) => {
|
|
7
9
|
for (var name in all)
|
|
@@ -15,37 +17,55 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
15
17
|
}
|
|
16
18
|
return to;
|
|
17
19
|
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
18
28
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
29
|
|
|
20
30
|
// src/index.ts
|
|
21
31
|
var index_exports = {};
|
|
22
32
|
__export(index_exports, {
|
|
23
|
-
Box: () =>
|
|
33
|
+
Box: () => import_widgets17.Box,
|
|
24
34
|
CommandPalette: () => CommandPalette,
|
|
25
35
|
ConfirmDialog: () => ConfirmDialog,
|
|
26
36
|
Divider: () => Divider,
|
|
27
37
|
Form: () => Form,
|
|
28
|
-
Gauge: () =>
|
|
29
|
-
|
|
30
|
-
|
|
38
|
+
Gauge: () => import_widgets17.Gauge,
|
|
39
|
+
KeyboardShortcuts: () => KeyboardShortcuts,
|
|
40
|
+
List: () => import_widgets17.List,
|
|
41
|
+
LogView: () => import_widgets17.LogView,
|
|
31
42
|
Modal: () => Modal,
|
|
32
43
|
MultiSelect: () => MultiSelect,
|
|
33
|
-
|
|
44
|
+
NonInteractiveError: () => NonInteractiveError,
|
|
45
|
+
NotificationCenter: () => NotificationCenter,
|
|
46
|
+
NotificationStore: () => NotificationStore,
|
|
47
|
+
NumberInput: () => NumberInput,
|
|
48
|
+
PasswordInput: () => PasswordInput,
|
|
49
|
+
PathInput: () => PathInput,
|
|
50
|
+
ProgressBar: () => import_widgets17.ProgressBar,
|
|
34
51
|
Select: () => Select,
|
|
35
52
|
Spacer: () => Spacer,
|
|
36
|
-
Sparkline: () =>
|
|
37
|
-
Spinner: () =>
|
|
38
|
-
StatusIndicator: () =>
|
|
39
|
-
Table: () =>
|
|
53
|
+
Sparkline: () => import_widgets17.Sparkline,
|
|
54
|
+
Spinner: () => import_widgets17.Spinner,
|
|
55
|
+
StatusIndicator: () => import_widgets17.StatusIndicator,
|
|
56
|
+
Table: () => import_widgets17.Table,
|
|
40
57
|
Tabs: () => Tabs,
|
|
41
|
-
Text: () =>
|
|
42
|
-
TextInput: () =>
|
|
58
|
+
Text: () => import_widgets17.Text,
|
|
59
|
+
TextInput: () => import_widgets17.TextInput,
|
|
43
60
|
Toast: () => Toast,
|
|
44
61
|
Tree: () => Tree,
|
|
45
|
-
Widget: () =>
|
|
62
|
+
Widget: () => import_widgets17.Widget,
|
|
63
|
+
notifications: () => notifications,
|
|
64
|
+
prompt: () => prompt,
|
|
65
|
+
useNotifications: () => useNotifications
|
|
46
66
|
});
|
|
47
67
|
module.exports = __toCommonJS(index_exports);
|
|
48
|
-
var
|
|
68
|
+
var import_widgets17 = require("@termuijs/widgets");
|
|
49
69
|
|
|
50
70
|
// src/Divider.ts
|
|
51
71
|
var import_widgets = require("@termuijs/widgets");
|
|
@@ -132,7 +152,7 @@ var Tabs = class extends import_widgets3.Widget {
|
|
|
132
152
|
for (let i = 0; i < this._tabs.length; i++) {
|
|
133
153
|
const tab = this._tabs[i];
|
|
134
154
|
const isActive = i === this._activeIndex;
|
|
135
|
-
const label = isActive ? ` \u25CF ${tab.label} ` : ` ${tab.label} `;
|
|
155
|
+
const label = isActive ? ` ${import_core3.caps.unicode ? "\u25CF" : "*"} ${tab.label} ` : ` ${tab.label} `;
|
|
136
156
|
screen.writeString(col, y, label, {
|
|
137
157
|
...attrs,
|
|
138
158
|
fg: isActive ? this._activeColor : this._inactiveColor,
|
|
@@ -141,7 +161,7 @@ var Tabs = class extends import_widgets3.Widget {
|
|
|
141
161
|
});
|
|
142
162
|
col += label.length;
|
|
143
163
|
if (i < this._tabs.length - 1) {
|
|
144
|
-
screen.writeString(col, y, "\u2502", { ...attrs, dim: true });
|
|
164
|
+
screen.writeString(col, y, import_core3.caps.unicode ? "\u2502" : "|", { ...attrs, dim: true });
|
|
145
165
|
col++;
|
|
146
166
|
}
|
|
147
167
|
}
|
|
@@ -166,7 +186,7 @@ var Modal = class extends import_widgets4.Widget {
|
|
|
166
186
|
this._modalWidth = options.width ?? 50;
|
|
167
187
|
this._modalHeight = options.height ?? 15;
|
|
168
188
|
this._borderColor = options.borderColor ?? { type: "named", name: "cyan" };
|
|
169
|
-
this._backdropChar = options.backdropChar ?? "\u2591";
|
|
189
|
+
this._backdropChar = options.backdropChar ?? (import_core4.caps.unicode ? "\u2591" : " ");
|
|
170
190
|
}
|
|
171
191
|
get visible() {
|
|
172
192
|
return this._visible;
|
|
@@ -290,13 +310,13 @@ var Select = class extends import_widgets5.Widget {
|
|
|
290
310
|
const attrs = (0, import_core5.styleToCellAttrs)(this.style);
|
|
291
311
|
const sel = this._options[this._selectedIndex];
|
|
292
312
|
const label = sel ? sel.label : this._placeholder;
|
|
293
|
-
const prefix = this._isOpen ? "\u25BC " : "\u25B6 ";
|
|
313
|
+
const prefix = this._isOpen ? import_core5.caps.unicode ? "\u25BC " : "v " : import_core5.caps.unicode ? "\u25B6 " : "> ";
|
|
294
314
|
screen.writeString(x, y, prefix + label.slice(0, width - 2), { ...attrs, fg: this._activeColor });
|
|
295
315
|
if (this._isOpen) {
|
|
296
316
|
for (let i = 0; i < this._options.length; i++) {
|
|
297
317
|
const o = this._options[i];
|
|
298
318
|
const isSel = i === this._selectedIndex;
|
|
299
|
-
const m = isSel ? "\u25CF " : " ";
|
|
319
|
+
const m = isSel ? import_core5.caps.unicode ? "\u25CF " : "* " : " ";
|
|
300
320
|
screen.writeString(x, y + 1 + i, m + o.label.slice(0, width - 2), {
|
|
301
321
|
...attrs,
|
|
302
322
|
fg: o.disabled ? { type: "named", name: "brightBlack" } : isSel ? this._activeColor : attrs.fg,
|
|
@@ -324,8 +344,8 @@ var MultiSelect = class extends import_widgets6.Widget {
|
|
|
324
344
|
super((0, import_core6.mergeStyles)((0, import_core6.defaultStyle)(), { height: Math.max(options.length, 1) }));
|
|
325
345
|
this._options = options;
|
|
326
346
|
this._activeColor = config.activeColor ?? { type: "named", name: "cyan" };
|
|
327
|
-
this._checkChar = config.checkChar ?? "\u25FC";
|
|
328
|
-
this._uncheckChar = config.uncheckChar ?? "\u25FB";
|
|
347
|
+
this._checkChar = config.checkChar ?? (import_core6.caps.unicode ? "\u25FC" : "[x]");
|
|
348
|
+
this._uncheckChar = config.uncheckChar ?? (import_core6.caps.unicode ? "\u25FB" : "[ ]");
|
|
329
349
|
this._onSubmit = config.onSubmit;
|
|
330
350
|
}
|
|
331
351
|
get selectedOptions() {
|
|
@@ -365,7 +385,7 @@ var MultiSelect = class extends import_widgets6.Widget {
|
|
|
365
385
|
const o = this._options[i];
|
|
366
386
|
const active = i === this._cursorIndex;
|
|
367
387
|
const checked = this._checked.has(i);
|
|
368
|
-
const label = `${active ? "\u276F " : " "}${checked ? this._checkChar : this._uncheckChar} ${o.label}`;
|
|
388
|
+
const label = `${active ? import_core6.caps.unicode ? "\u276F " : "> " : " "}${checked ? this._checkChar : this._uncheckChar} ${o.label}`;
|
|
369
389
|
screen.writeString(x, y + i, label.slice(0, width), {
|
|
370
390
|
...attrs,
|
|
371
391
|
fg: o.disabled ? { type: "named", name: "brightBlack" } : active ? this._activeColor : attrs.fg,
|
|
@@ -393,12 +413,12 @@ var Tree = class extends import_widgets7.Widget {
|
|
|
393
413
|
}
|
|
394
414
|
_flatten() {
|
|
395
415
|
const result = [];
|
|
396
|
-
const walk = (nodes, depth,
|
|
416
|
+
const walk = (nodes, depth, path2) => {
|
|
397
417
|
for (let i = 0; i < nodes.length; i++) {
|
|
398
418
|
const node = nodes[i];
|
|
399
419
|
const hasChildren = (node.children?.length ?? 0) > 0;
|
|
400
|
-
result.push({ node, depth, path: [...
|
|
401
|
-
if (hasChildren && node.expanded) walk(node.children, depth + 1, [...
|
|
420
|
+
result.push({ node, depth, path: [...path2, i], hasChildren });
|
|
421
|
+
if (hasChildren && node.expanded) walk(node.children, depth + 1, [...path2, i]);
|
|
402
422
|
}
|
|
403
423
|
};
|
|
404
424
|
walk(this._roots, 0, []);
|
|
@@ -441,7 +461,7 @@ var Tree = class extends import_widgets7.Widget {
|
|
|
441
461
|
const it = flat[i];
|
|
442
462
|
const active = i === this._cursorIndex;
|
|
443
463
|
const indent = " ".repeat(it.depth);
|
|
444
|
-
const icon = it.hasChildren ? it.node.expanded ? "\u25BC " : "\u25B6 " : " ";
|
|
464
|
+
const icon = it.hasChildren ? it.node.expanded ? import_core7.caps.unicode ? "\u25BC " : "v " : import_core7.caps.unicode ? "\u25B6 " : "> " : " ";
|
|
445
465
|
const nodeIcon = it.node.icon ? `${it.node.icon} ` : "";
|
|
446
466
|
const line = `${indent}${icon}${nodeIcon}${it.node.label}`;
|
|
447
467
|
screen.writeString(x, y + i, line.slice(0, width), { ...attrs, fg: active ? this._activeColor : attrs.fg, bold: active });
|
|
@@ -452,7 +472,8 @@ var Tree = class extends import_widgets7.Widget {
|
|
|
452
472
|
// src/Toast.ts
|
|
453
473
|
var import_widgets8 = require("@termuijs/widgets");
|
|
454
474
|
var import_core8 = require("@termuijs/core");
|
|
455
|
-
var
|
|
475
|
+
var ICONS_UNICODE = { info: "\u2139", success: "\u2713", warning: "\u26A0", error: "\u2717" };
|
|
476
|
+
var ICONS_ASCII = { info: "i", success: "+", warning: "!", error: "x" };
|
|
456
477
|
var COLORS = { info: "cyan", success: "green", warning: "yellow", error: "red" };
|
|
457
478
|
var Toast = class extends import_widgets8.Widget {
|
|
458
479
|
_messages = [];
|
|
@@ -492,10 +513,11 @@ var Toast = class extends import_widgets8.Widget {
|
|
|
492
513
|
const isBottom = this._position.includes("bottom");
|
|
493
514
|
const sx = isRight ? x + width - tw - 1 : x + 1;
|
|
494
515
|
const sy = isBottom ? y + height - visible.length - 1 : y + 1;
|
|
516
|
+
const icons = import_core8.caps.unicode ? ICONS_UNICODE : ICONS_ASCII;
|
|
495
517
|
const attrs = (0, import_core8.styleToCellAttrs)(this.style);
|
|
496
518
|
for (let i = 0; i < visible.length; i++) {
|
|
497
519
|
const m = visible[i];
|
|
498
|
-
const label = ` ${
|
|
520
|
+
const label = ` ${icons[m.type]} ${m.text} `.slice(0, tw).padEnd(tw);
|
|
499
521
|
screen.writeString(sx, sy + i, label, { ...attrs, fg: { type: "named", name: COLORS[m.type] }, bold: true });
|
|
500
522
|
}
|
|
501
523
|
}
|
|
@@ -779,7 +801,8 @@ var CommandPalette = class extends import_widgets11.Widget {
|
|
|
779
801
|
if (!this._visible) return;
|
|
780
802
|
const { x, y, width, height } = this._rect;
|
|
781
803
|
const attrs = (0, import_core11.styleToCellAttrs)(this.style);
|
|
782
|
-
|
|
804
|
+
const backdropCh = import_core11.caps.unicode ? "\u2591" : " ";
|
|
805
|
+
for (let r = 0; r < height; r++) screen.writeString(x, y + r, backdropCh.repeat(width), { ...attrs, dim: true });
|
|
783
806
|
const vis = this._filtered.slice(0, this._maxVisible);
|
|
784
807
|
const bw = Math.min(60, width - 4);
|
|
785
808
|
const bh = Math.min(vis.length + 3, height - 2);
|
|
@@ -791,13 +814,13 @@ var CommandPalette = class extends import_widgets11.Widget {
|
|
|
791
814
|
screen.writeString(bx, by, border.topLeft + border.top.repeat(bw - 2) + border.topRight, ba);
|
|
792
815
|
screen.writeString(bx, by + 1, border.left, ba);
|
|
793
816
|
const input = this._query || this._placeholder;
|
|
794
|
-
screen.writeString(bx + 1, by + 1, ("
|
|
817
|
+
screen.writeString(bx + 1, by + 1, (` ${import_core11.caps.unicode ? "\u{1F50D}" : "[?]"} ` + input).slice(0, bw - 2).padEnd(bw - 2), { ...attrs, dim: !this._query });
|
|
795
818
|
screen.writeString(bx + bw - 1, by + 1, border.right, ba);
|
|
796
819
|
screen.writeString(bx, by + 2, border.left + "\u2500".repeat(bw - 2) + border.right, ba);
|
|
797
820
|
for (let i = 0; i < vis.length && i + 3 < bh - 1; i++) {
|
|
798
821
|
const c = vis[i];
|
|
799
822
|
const active = i === this._selectedIndex;
|
|
800
|
-
const label = (active ? "\u276F " : " ") + c.label;
|
|
823
|
+
const label = (active ? import_core11.caps.unicode ? "\u276F " : "> " : " ") + c.label;
|
|
801
824
|
const sc = c.shortcut ?? "";
|
|
802
825
|
screen.writeString(bx, by + 3 + i, border.left, ba);
|
|
803
826
|
screen.writeString(bx + 1, by + 3 + i, (" " + label).slice(0, bw - sc.length - 3).padEnd(bw - sc.length - 3), { ...attrs, fg: active ? this._activeColor : attrs.fg, bold: active });
|
|
@@ -808,6 +831,919 @@ var CommandPalette = class extends import_widgets11.Widget {
|
|
|
808
831
|
screen.writeString(bx, last, border.bottomLeft + border.bottom.repeat(bw - 2) + border.bottomRight, ba);
|
|
809
832
|
}
|
|
810
833
|
};
|
|
834
|
+
|
|
835
|
+
// src/prompts.ts
|
|
836
|
+
var readline = __toESM(require("readline"), 1);
|
|
837
|
+
var NonInteractiveError = class extends Error {
|
|
838
|
+
constructor() {
|
|
839
|
+
super("Prompts require an interactive TTY. stdin is not a TTY.");
|
|
840
|
+
this.name = "NonInteractiveError";
|
|
841
|
+
}
|
|
842
|
+
};
|
|
843
|
+
async function promptText(options) {
|
|
844
|
+
if (!process.stdin.isTTY) throw new NonInteractiveError();
|
|
845
|
+
const defaultHint = options.default ? ` (${options.default})` : "";
|
|
846
|
+
const placeholder = options.placeholder ? ` [${options.placeholder}]` : "";
|
|
847
|
+
return new Promise((resolve) => {
|
|
848
|
+
const rl = readline.createInterface({
|
|
849
|
+
input: process.stdin,
|
|
850
|
+
output: process.stdout
|
|
851
|
+
});
|
|
852
|
+
const ask = () => {
|
|
853
|
+
rl.question(`${options.message}${defaultHint}${placeholder}: `, (answer) => {
|
|
854
|
+
const value = answer.trim() || options.default || "";
|
|
855
|
+
if (options.validate) {
|
|
856
|
+
const error = options.validate(value);
|
|
857
|
+
if (error) {
|
|
858
|
+
process.stdout.write(` ${error}
|
|
859
|
+
`);
|
|
860
|
+
ask();
|
|
861
|
+
return;
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
rl.close();
|
|
865
|
+
resolve(value);
|
|
866
|
+
});
|
|
867
|
+
};
|
|
868
|
+
ask();
|
|
869
|
+
});
|
|
870
|
+
}
|
|
871
|
+
async function promptConfirm(options) {
|
|
872
|
+
if (!process.stdin.isTTY) throw new NonInteractiveError();
|
|
873
|
+
const hint = options.default === true ? "Y/n" : options.default === false ? "y/N" : "y/n";
|
|
874
|
+
return new Promise((resolve) => {
|
|
875
|
+
const rl = readline.createInterface({
|
|
876
|
+
input: process.stdin,
|
|
877
|
+
output: process.stdout
|
|
878
|
+
});
|
|
879
|
+
rl.question(`${options.message} [${hint}]: `, (answer) => {
|
|
880
|
+
rl.close();
|
|
881
|
+
const a = answer.trim().toLowerCase();
|
|
882
|
+
if (a === "y" || a === "yes") {
|
|
883
|
+
resolve(true);
|
|
884
|
+
return;
|
|
885
|
+
}
|
|
886
|
+
if (a === "n" || a === "no") {
|
|
887
|
+
resolve(false);
|
|
888
|
+
return;
|
|
889
|
+
}
|
|
890
|
+
if (a === "" && options.default !== void 0) {
|
|
891
|
+
resolve(options.default);
|
|
892
|
+
return;
|
|
893
|
+
}
|
|
894
|
+
resolve(false);
|
|
895
|
+
});
|
|
896
|
+
});
|
|
897
|
+
}
|
|
898
|
+
async function promptSelect(options) {
|
|
899
|
+
if (!process.stdin.isTTY) throw new NonInteractiveError();
|
|
900
|
+
const { options: choices, default: defaultValue } = options;
|
|
901
|
+
process.stdout.write(`${options.message}
|
|
902
|
+
`);
|
|
903
|
+
choices.forEach((opt, i) => {
|
|
904
|
+
const isDefault = opt.value === defaultValue;
|
|
905
|
+
process.stdout.write(` ${i + 1}. ${opt.label}${isDefault ? " (default)" : ""}
|
|
906
|
+
`);
|
|
907
|
+
});
|
|
908
|
+
return new Promise((resolve) => {
|
|
909
|
+
const rl = readline.createInterface({
|
|
910
|
+
input: process.stdin,
|
|
911
|
+
output: process.stdout
|
|
912
|
+
});
|
|
913
|
+
const ask = () => {
|
|
914
|
+
rl.question(`Enter number (1-${choices.length}): `, (answer) => {
|
|
915
|
+
const trimmed = answer.trim();
|
|
916
|
+
if (trimmed === "" && defaultValue !== void 0) {
|
|
917
|
+
rl.close();
|
|
918
|
+
resolve(defaultValue);
|
|
919
|
+
return;
|
|
920
|
+
}
|
|
921
|
+
const n = parseInt(trimmed, 10);
|
|
922
|
+
if (!isNaN(n) && n >= 1 && n <= choices.length) {
|
|
923
|
+
rl.close();
|
|
924
|
+
resolve(choices[n - 1].value);
|
|
925
|
+
return;
|
|
926
|
+
}
|
|
927
|
+
process.stdout.write(` Invalid choice. Enter a number 1-${choices.length}.
|
|
928
|
+
`);
|
|
929
|
+
ask();
|
|
930
|
+
});
|
|
931
|
+
};
|
|
932
|
+
ask();
|
|
933
|
+
});
|
|
934
|
+
}
|
|
935
|
+
var prompt = {
|
|
936
|
+
text: promptText,
|
|
937
|
+
confirm: promptConfirm,
|
|
938
|
+
select: promptSelect
|
|
939
|
+
};
|
|
940
|
+
|
|
941
|
+
// src/NotificationCenter.ts
|
|
942
|
+
var import_widgets12 = require("@termuijs/widgets");
|
|
943
|
+
var import_jsx = require("@termuijs/jsx");
|
|
944
|
+
var import_core12 = require("@termuijs/core");
|
|
945
|
+
var NotificationStore = class _NotificationStore {
|
|
946
|
+
static _instance;
|
|
947
|
+
_notifications = [];
|
|
948
|
+
_subs = /* @__PURE__ */ new Set();
|
|
949
|
+
static getInstance() {
|
|
950
|
+
if (!_NotificationStore._instance) {
|
|
951
|
+
_NotificationStore._instance = new _NotificationStore();
|
|
952
|
+
}
|
|
953
|
+
return _NotificationStore._instance;
|
|
954
|
+
}
|
|
955
|
+
push(message, type = "info", durationMs) {
|
|
956
|
+
const id = Date.now().toString(36) + Math.random().toString(36).slice(2);
|
|
957
|
+
const notification = { id, message, type, durationMs, createdAt: Date.now() };
|
|
958
|
+
this._notifications = [...this._notifications, notification];
|
|
959
|
+
this._emit();
|
|
960
|
+
if (durationMs && durationMs > 0) {
|
|
961
|
+
setTimeout(() => this.dismiss(id), durationMs);
|
|
962
|
+
}
|
|
963
|
+
return id;
|
|
964
|
+
}
|
|
965
|
+
dismiss(id) {
|
|
966
|
+
const prev = this._notifications;
|
|
967
|
+
this._notifications = this._notifications.filter((n) => n.id !== id);
|
|
968
|
+
if (this._notifications.length !== prev.length) {
|
|
969
|
+
this._emit();
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
dismissAll() {
|
|
973
|
+
if (this._notifications.length > 0) {
|
|
974
|
+
this._notifications = [];
|
|
975
|
+
this._emit();
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
subscribe(fn) {
|
|
979
|
+
this._subs.add(fn);
|
|
980
|
+
return () => this._subs.delete(fn);
|
|
981
|
+
}
|
|
982
|
+
get notifications() {
|
|
983
|
+
return this._notifications;
|
|
984
|
+
}
|
|
985
|
+
_emit() {
|
|
986
|
+
for (const fn of this._subs) fn(this._notifications);
|
|
987
|
+
}
|
|
988
|
+
};
|
|
989
|
+
var notifications = NotificationStore.getInstance();
|
|
990
|
+
function useNotifications() {
|
|
991
|
+
const store = NotificationStore.getInstance();
|
|
992
|
+
const [current, setCurrent] = (0, import_jsx.useState)(store.notifications);
|
|
993
|
+
(0, import_jsx.useEffect)(() => {
|
|
994
|
+
const unsub = store.subscribe((ns) => setCurrent([...ns]));
|
|
995
|
+
return unsub;
|
|
996
|
+
}, []);
|
|
997
|
+
return {
|
|
998
|
+
notifications: current,
|
|
999
|
+
push: (msg, type, dur) => store.push(msg, type, dur),
|
|
1000
|
+
dismiss: (id) => store.dismiss(id),
|
|
1001
|
+
dismissAll: () => store.dismissAll()
|
|
1002
|
+
};
|
|
1003
|
+
}
|
|
1004
|
+
var TYPE_ICONS = {
|
|
1005
|
+
info: { unicode: "\u2139", ascii: "i" },
|
|
1006
|
+
success: { unicode: "\u2713", ascii: "+" },
|
|
1007
|
+
warning: { unicode: "\u26A0", ascii: "!" },
|
|
1008
|
+
error: { unicode: "\u2717", ascii: "x" }
|
|
1009
|
+
};
|
|
1010
|
+
var TYPE_COLORS = {
|
|
1011
|
+
info: { type: "named", name: "cyan" },
|
|
1012
|
+
success: { type: "named", name: "green" },
|
|
1013
|
+
warning: { type: "named", name: "yellow" },
|
|
1014
|
+
error: { type: "named", name: "red" }
|
|
1015
|
+
};
|
|
1016
|
+
var NotificationCenter = class extends import_widgets12.Widget {
|
|
1017
|
+
_position;
|
|
1018
|
+
_maxVisible;
|
|
1019
|
+
_notifWidth;
|
|
1020
|
+
_unsub;
|
|
1021
|
+
_current = [];
|
|
1022
|
+
constructor(options = {}) {
|
|
1023
|
+
super();
|
|
1024
|
+
this._position = options.position ?? "top-right";
|
|
1025
|
+
this._maxVisible = options.maxVisible ?? 5;
|
|
1026
|
+
this._notifWidth = options.width ?? 40;
|
|
1027
|
+
const store = NotificationStore.getInstance();
|
|
1028
|
+
this._current = store.notifications;
|
|
1029
|
+
this._unsub = store.subscribe((ns) => {
|
|
1030
|
+
this._current = ns;
|
|
1031
|
+
this.markDirty();
|
|
1032
|
+
});
|
|
1033
|
+
}
|
|
1034
|
+
unmount() {
|
|
1035
|
+
this._unsub?.();
|
|
1036
|
+
this._unsub = void 0;
|
|
1037
|
+
super.unmount();
|
|
1038
|
+
}
|
|
1039
|
+
_renderSelf(screen) {
|
|
1040
|
+
const visible = this._current.slice(-this._maxVisible);
|
|
1041
|
+
if (visible.length === 0) return;
|
|
1042
|
+
const { x, y, width, height } = this._rect;
|
|
1043
|
+
const tw = Math.min(this._notifWidth, width - 2);
|
|
1044
|
+
if (tw <= 0) return;
|
|
1045
|
+
const isRight = this._position.includes("right");
|
|
1046
|
+
const isBottom = this._position.includes("bottom");
|
|
1047
|
+
const sx = isRight ? x + width - tw - 1 : x + 1;
|
|
1048
|
+
const sy = isBottom ? y + height - visible.length - 1 : y + 1;
|
|
1049
|
+
for (let i = 0; i < visible.length; i++) {
|
|
1050
|
+
const notif = visible[i];
|
|
1051
|
+
const icon = import_core12.caps.unicode ? TYPE_ICONS[notif.type].unicode : TYPE_ICONS[notif.type].ascii;
|
|
1052
|
+
const raw = `${icon} ${notif.message}`;
|
|
1053
|
+
const label = ` ${raw} `.slice(0, tw).padEnd(tw);
|
|
1054
|
+
screen.writeString(sx, sy + i, label, {
|
|
1055
|
+
fg: TYPE_COLORS[notif.type],
|
|
1056
|
+
bold: true
|
|
1057
|
+
});
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
};
|
|
1061
|
+
|
|
1062
|
+
// src/PasswordInput.ts
|
|
1063
|
+
var import_widgets13 = require("@termuijs/widgets");
|
|
1064
|
+
var import_core13 = require("@termuijs/core");
|
|
1065
|
+
var PasswordInput = class extends import_widgets13.Widget {
|
|
1066
|
+
_value = "";
|
|
1067
|
+
_cursorPos = 0;
|
|
1068
|
+
_placeholder;
|
|
1069
|
+
_maxLength;
|
|
1070
|
+
_showText = false;
|
|
1071
|
+
_onChange;
|
|
1072
|
+
_onSubmit;
|
|
1073
|
+
focusable = true;
|
|
1074
|
+
// '●' in unicode terminals, '*' in ASCII fallback
|
|
1075
|
+
get _maskChar() {
|
|
1076
|
+
return import_core13.caps.unicode ? "\u25CF" : "*";
|
|
1077
|
+
}
|
|
1078
|
+
constructor(style = {}, options = {}) {
|
|
1079
|
+
super({ border: "single", height: 3, ...style });
|
|
1080
|
+
this._placeholder = options.placeholder ?? "";
|
|
1081
|
+
this._maxLength = options.maxLength ?? Infinity;
|
|
1082
|
+
this._onChange = options.onChange;
|
|
1083
|
+
this._onSubmit = options.onSubmit;
|
|
1084
|
+
}
|
|
1085
|
+
/** The actual (unmasked) value. */
|
|
1086
|
+
get value() {
|
|
1087
|
+
return this._value;
|
|
1088
|
+
}
|
|
1089
|
+
set value(v) {
|
|
1090
|
+
this._value = v.slice(0, this._maxLength);
|
|
1091
|
+
this._cursorPos = Math.min(this._cursorPos, this._value.length);
|
|
1092
|
+
}
|
|
1093
|
+
/** Whether the text is currently visible (unmaksed). */
|
|
1094
|
+
get showText() {
|
|
1095
|
+
return this._showText;
|
|
1096
|
+
}
|
|
1097
|
+
/** Toggle visibility of the actual text (Alt+V). */
|
|
1098
|
+
toggleVisibility() {
|
|
1099
|
+
this._showText = !this._showText;
|
|
1100
|
+
this.markDirty();
|
|
1101
|
+
}
|
|
1102
|
+
insertChar(char) {
|
|
1103
|
+
if (this._value.length >= this._maxLength) return;
|
|
1104
|
+
this._value = this._value.slice(0, this._cursorPos) + char + this._value.slice(this._cursorPos);
|
|
1105
|
+
this._cursorPos++;
|
|
1106
|
+
this._onChange?.(this._value);
|
|
1107
|
+
this.markDirty();
|
|
1108
|
+
}
|
|
1109
|
+
deleteBack() {
|
|
1110
|
+
if (this._cursorPos > 0) {
|
|
1111
|
+
this._value = this._value.slice(0, this._cursorPos - 1) + this._value.slice(this._cursorPos);
|
|
1112
|
+
this._cursorPos--;
|
|
1113
|
+
this._onChange?.(this._value);
|
|
1114
|
+
this.markDirty();
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
deleteForward() {
|
|
1118
|
+
if (this._cursorPos < this._value.length) {
|
|
1119
|
+
this._value = this._value.slice(0, this._cursorPos) + this._value.slice(this._cursorPos + 1);
|
|
1120
|
+
this._onChange?.(this._value);
|
|
1121
|
+
this.markDirty();
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
moveCursorLeft() {
|
|
1125
|
+
this._cursorPos = Math.max(0, this._cursorPos - 1);
|
|
1126
|
+
this.markDirty();
|
|
1127
|
+
}
|
|
1128
|
+
moveCursorRight() {
|
|
1129
|
+
this._cursorPos = Math.min(this._value.length, this._cursorPos + 1);
|
|
1130
|
+
this.markDirty();
|
|
1131
|
+
}
|
|
1132
|
+
moveCursorHome() {
|
|
1133
|
+
this._cursorPos = 0;
|
|
1134
|
+
this.markDirty();
|
|
1135
|
+
}
|
|
1136
|
+
moveCursorEnd() {
|
|
1137
|
+
this._cursorPos = this._value.length;
|
|
1138
|
+
this.markDirty();
|
|
1139
|
+
}
|
|
1140
|
+
submit() {
|
|
1141
|
+
this._onSubmit?.(this._value);
|
|
1142
|
+
}
|
|
1143
|
+
clear() {
|
|
1144
|
+
this._value = "";
|
|
1145
|
+
this._cursorPos = 0;
|
|
1146
|
+
this._onChange?.("");
|
|
1147
|
+
this.markDirty();
|
|
1148
|
+
}
|
|
1149
|
+
/**
|
|
1150
|
+
* Handle key events. Call this from your input loop.
|
|
1151
|
+
* Alt+V — toggle visibility
|
|
1152
|
+
* Other — standard text editing
|
|
1153
|
+
*/
|
|
1154
|
+
handleKey(event) {
|
|
1155
|
+
if (event.alt && event.key === "v") {
|
|
1156
|
+
this.toggleVisibility();
|
|
1157
|
+
return;
|
|
1158
|
+
}
|
|
1159
|
+
switch (event.key) {
|
|
1160
|
+
case "backspace":
|
|
1161
|
+
this.deleteBack();
|
|
1162
|
+
break;
|
|
1163
|
+
case "delete":
|
|
1164
|
+
this.deleteForward();
|
|
1165
|
+
break;
|
|
1166
|
+
case "left":
|
|
1167
|
+
this.moveCursorLeft();
|
|
1168
|
+
break;
|
|
1169
|
+
case "right":
|
|
1170
|
+
this.moveCursorRight();
|
|
1171
|
+
break;
|
|
1172
|
+
case "home":
|
|
1173
|
+
this.moveCursorHome();
|
|
1174
|
+
break;
|
|
1175
|
+
case "end":
|
|
1176
|
+
this.moveCursorEnd();
|
|
1177
|
+
break;
|
|
1178
|
+
case "return":
|
|
1179
|
+
case "enter":
|
|
1180
|
+
this.submit();
|
|
1181
|
+
break;
|
|
1182
|
+
default:
|
|
1183
|
+
if (event.key && event.key.length === 1 && !event.ctrl && !event.alt) {
|
|
1184
|
+
this.insertChar(event.key);
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
_renderSelf(screen) {
|
|
1189
|
+
const rect = this._getContentRect();
|
|
1190
|
+
const { x, y, width, height } = rect;
|
|
1191
|
+
if (width <= 0 || height <= 0) return;
|
|
1192
|
+
const attrs = (0, import_core13.styleToCellAttrs)(this._style);
|
|
1193
|
+
if (this._value.length === 0 && !this.isFocused) {
|
|
1194
|
+
screen.writeString(x, y, (0, import_core13.truncate)(this._placeholder, width), { ...attrs, dim: true });
|
|
1195
|
+
return;
|
|
1196
|
+
}
|
|
1197
|
+
const displayValue = this._showText ? this._value : this._maskChar.repeat(this._value.length);
|
|
1198
|
+
const visibleWidth = width - 1;
|
|
1199
|
+
let scrollX = 0;
|
|
1200
|
+
if (this._cursorPos > visibleWidth) {
|
|
1201
|
+
scrollX = this._cursorPos - visibleWidth;
|
|
1202
|
+
}
|
|
1203
|
+
const visibleText = displayValue.slice(scrollX, scrollX + visibleWidth);
|
|
1204
|
+
screen.writeString(x, y, visibleText, attrs);
|
|
1205
|
+
if (this.isFocused) {
|
|
1206
|
+
const cursorScreenPos = x + this._cursorPos - scrollX;
|
|
1207
|
+
if (cursorScreenPos >= x && cursorScreenPos < x + width) {
|
|
1208
|
+
const cursorChar = this._cursorPos < displayValue.length ? displayValue[this._cursorPos] : " ";
|
|
1209
|
+
screen.setCell(cursorScreenPos, y, {
|
|
1210
|
+
char: cursorChar,
|
|
1211
|
+
...attrs,
|
|
1212
|
+
inverse: true
|
|
1213
|
+
});
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
if (this._showText && width > 4) {
|
|
1217
|
+
const indicator = import_core13.caps.unicode ? " \u{1F441}" : "[v]";
|
|
1218
|
+
screen.writeString(x + width - indicator.length, y, indicator, { ...attrs, dim: true });
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
1221
|
+
};
|
|
1222
|
+
|
|
1223
|
+
// src/NumberInput.ts
|
|
1224
|
+
var import_widgets14 = require("@termuijs/widgets");
|
|
1225
|
+
var import_core14 = require("@termuijs/core");
|
|
1226
|
+
var NumberInput = class extends import_widgets14.Widget {
|
|
1227
|
+
_raw = "";
|
|
1228
|
+
// raw string the user typed
|
|
1229
|
+
_cursorPos = 0;
|
|
1230
|
+
_placeholder;
|
|
1231
|
+
_step;
|
|
1232
|
+
_min;
|
|
1233
|
+
_max;
|
|
1234
|
+
_allowDecimal;
|
|
1235
|
+
_onChange;
|
|
1236
|
+
_onSubmit;
|
|
1237
|
+
focusable = true;
|
|
1238
|
+
constructor(style = {}, options = {}) {
|
|
1239
|
+
super({ border: "single", height: 3, ...style });
|
|
1240
|
+
this._placeholder = options.placeholder ?? "";
|
|
1241
|
+
this._step = options.step ?? 1;
|
|
1242
|
+
this._min = options.min ?? -Infinity;
|
|
1243
|
+
this._max = options.max ?? Infinity;
|
|
1244
|
+
this._allowDecimal = options.allowDecimal ?? true;
|
|
1245
|
+
this._onChange = options.onChange;
|
|
1246
|
+
this._onSubmit = options.onSubmit;
|
|
1247
|
+
}
|
|
1248
|
+
/** The numeric value, or null if the field is empty / invalid. */
|
|
1249
|
+
get numericValue() {
|
|
1250
|
+
if (this._raw === "" || this._raw === "-") return null;
|
|
1251
|
+
const n = parseFloat(this._raw);
|
|
1252
|
+
return isNaN(n) ? null : n;
|
|
1253
|
+
}
|
|
1254
|
+
/** Raw text string (what the user typed). */
|
|
1255
|
+
get rawValue() {
|
|
1256
|
+
return this._raw;
|
|
1257
|
+
}
|
|
1258
|
+
set rawValue(v) {
|
|
1259
|
+
this._raw = v;
|
|
1260
|
+
this._cursorPos = Math.min(this._cursorPos, this._raw.length);
|
|
1261
|
+
}
|
|
1262
|
+
_clamp(n) {
|
|
1263
|
+
return Math.min(this._max, Math.max(this._min, n));
|
|
1264
|
+
}
|
|
1265
|
+
_notify() {
|
|
1266
|
+
this._onChange?.(this.numericValue);
|
|
1267
|
+
this.markDirty();
|
|
1268
|
+
}
|
|
1269
|
+
/** Accept only digits, '-' at position 0 (if min < 0), and (optionally) one '.'. */
|
|
1270
|
+
_isAllowed(char) {
|
|
1271
|
+
if (char === "-" && this._cursorPos === 0 && !this._raw.includes("-")) {
|
|
1272
|
+
return this._min < 0;
|
|
1273
|
+
}
|
|
1274
|
+
if (char === "." && this._allowDecimal && !this._raw.includes(".")) return true;
|
|
1275
|
+
return /^\d$/.test(char);
|
|
1276
|
+
}
|
|
1277
|
+
insertChar(char) {
|
|
1278
|
+
if (!this._isAllowed(char)) return;
|
|
1279
|
+
this._raw = this._raw.slice(0, this._cursorPos) + char + this._raw.slice(this._cursorPos);
|
|
1280
|
+
this._cursorPos++;
|
|
1281
|
+
this._notify();
|
|
1282
|
+
}
|
|
1283
|
+
deleteBack() {
|
|
1284
|
+
if (this._cursorPos > 0) {
|
|
1285
|
+
this._raw = this._raw.slice(0, this._cursorPos - 1) + this._raw.slice(this._cursorPos);
|
|
1286
|
+
this._cursorPos--;
|
|
1287
|
+
this._notify();
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
1290
|
+
deleteForward() {
|
|
1291
|
+
if (this._cursorPos < this._raw.length) {
|
|
1292
|
+
this._raw = this._raw.slice(0, this._cursorPos) + this._raw.slice(this._cursorPos + 1);
|
|
1293
|
+
this._notify();
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
moveCursorLeft() {
|
|
1297
|
+
this._cursorPos = Math.max(0, this._cursorPos - 1);
|
|
1298
|
+
this.markDirty();
|
|
1299
|
+
}
|
|
1300
|
+
moveCursorRight() {
|
|
1301
|
+
this._cursorPos = Math.min(this._raw.length, this._cursorPos + 1);
|
|
1302
|
+
this.markDirty();
|
|
1303
|
+
}
|
|
1304
|
+
moveCursorHome() {
|
|
1305
|
+
this._cursorPos = 0;
|
|
1306
|
+
this.markDirty();
|
|
1307
|
+
}
|
|
1308
|
+
moveCursorEnd() {
|
|
1309
|
+
this._cursorPos = this._raw.length;
|
|
1310
|
+
this.markDirty();
|
|
1311
|
+
}
|
|
1312
|
+
/** Increment value by step (↑ arrow). */
|
|
1313
|
+
increment() {
|
|
1314
|
+
const current = this.numericValue ?? 0;
|
|
1315
|
+
const next = this._clamp(current + this._step);
|
|
1316
|
+
this._raw = String(next);
|
|
1317
|
+
this._cursorPos = this._raw.length;
|
|
1318
|
+
this._notify();
|
|
1319
|
+
}
|
|
1320
|
+
/** Decrement value by step (↓ arrow). */
|
|
1321
|
+
decrement() {
|
|
1322
|
+
const current = this.numericValue ?? 0;
|
|
1323
|
+
const next = this._clamp(current - this._step);
|
|
1324
|
+
this._raw = String(next);
|
|
1325
|
+
this._cursorPos = this._raw.length;
|
|
1326
|
+
this._notify();
|
|
1327
|
+
}
|
|
1328
|
+
submit() {
|
|
1329
|
+
this._onSubmit?.(this.numericValue);
|
|
1330
|
+
}
|
|
1331
|
+
clear() {
|
|
1332
|
+
this._raw = "";
|
|
1333
|
+
this._cursorPos = 0;
|
|
1334
|
+
this._notify();
|
|
1335
|
+
}
|
|
1336
|
+
/**
|
|
1337
|
+
* Handle key events. Call this from your input loop.
|
|
1338
|
+
*/
|
|
1339
|
+
handleKey(event) {
|
|
1340
|
+
switch (event.key) {
|
|
1341
|
+
case "up":
|
|
1342
|
+
this.increment();
|
|
1343
|
+
break;
|
|
1344
|
+
case "down":
|
|
1345
|
+
this.decrement();
|
|
1346
|
+
break;
|
|
1347
|
+
case "backspace":
|
|
1348
|
+
this.deleteBack();
|
|
1349
|
+
break;
|
|
1350
|
+
case "delete":
|
|
1351
|
+
this.deleteForward();
|
|
1352
|
+
break;
|
|
1353
|
+
case "left":
|
|
1354
|
+
this.moveCursorLeft();
|
|
1355
|
+
break;
|
|
1356
|
+
case "right":
|
|
1357
|
+
this.moveCursorRight();
|
|
1358
|
+
break;
|
|
1359
|
+
case "home":
|
|
1360
|
+
this.moveCursorHome();
|
|
1361
|
+
break;
|
|
1362
|
+
case "end":
|
|
1363
|
+
this.moveCursorEnd();
|
|
1364
|
+
break;
|
|
1365
|
+
case "return":
|
|
1366
|
+
case "enter":
|
|
1367
|
+
this.submit();
|
|
1368
|
+
break;
|
|
1369
|
+
default:
|
|
1370
|
+
if (event.key && event.key.length === 1 && !event.ctrl && !event.alt) {
|
|
1371
|
+
this.insertChar(event.key);
|
|
1372
|
+
}
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1375
|
+
_renderSelf(screen) {
|
|
1376
|
+
const rect = this._getContentRect();
|
|
1377
|
+
const { x, y, width, height } = rect;
|
|
1378
|
+
if (width <= 0 || height <= 0) return;
|
|
1379
|
+
const attrs = (0, import_core14.styleToCellAttrs)(this._style);
|
|
1380
|
+
if (this._raw.length === 0 && !this.isFocused) {
|
|
1381
|
+
screen.writeString(x, y, (0, import_core14.truncate)(this._placeholder, width), { ...attrs, dim: true });
|
|
1382
|
+
return;
|
|
1383
|
+
}
|
|
1384
|
+
const display = this._raw;
|
|
1385
|
+
const visibleWidth = width - 1;
|
|
1386
|
+
let scrollX = 0;
|
|
1387
|
+
if (this._cursorPos > visibleWidth) {
|
|
1388
|
+
scrollX = this._cursorPos - visibleWidth;
|
|
1389
|
+
}
|
|
1390
|
+
const visibleText = display.slice(scrollX, scrollX + visibleWidth);
|
|
1391
|
+
screen.writeString(x, y, visibleText, attrs);
|
|
1392
|
+
if (this.isFocused) {
|
|
1393
|
+
const cursorScreenPos = x + this._cursorPos - scrollX;
|
|
1394
|
+
if (cursorScreenPos >= x && cursorScreenPos < x + width) {
|
|
1395
|
+
const cursorChar = this._cursorPos < display.length ? display[this._cursorPos] : " ";
|
|
1396
|
+
screen.setCell(cursorScreenPos, y, {
|
|
1397
|
+
char: cursorChar,
|
|
1398
|
+
...attrs,
|
|
1399
|
+
inverse: true
|
|
1400
|
+
});
|
|
1401
|
+
}
|
|
1402
|
+
}
|
|
1403
|
+
if (this.isFocused && width > 8) {
|
|
1404
|
+
const hint = `\xB1${this._step}`;
|
|
1405
|
+
screen.writeString(x + width - hint.length, y, hint, { ...attrs, dim: true });
|
|
1406
|
+
}
|
|
1407
|
+
}
|
|
1408
|
+
};
|
|
1409
|
+
|
|
1410
|
+
// src/PathInput.ts
|
|
1411
|
+
var fs = __toESM(require("fs"), 1);
|
|
1412
|
+
var path = __toESM(require("path"), 1);
|
|
1413
|
+
var import_widgets15 = require("@termuijs/widgets");
|
|
1414
|
+
var import_core15 = require("@termuijs/core");
|
|
1415
|
+
var PathInput = class extends import_widgets15.Widget {
|
|
1416
|
+
_value = "";
|
|
1417
|
+
_cursorPos = 0;
|
|
1418
|
+
_placeholder;
|
|
1419
|
+
_maxLength;
|
|
1420
|
+
_cwd;
|
|
1421
|
+
_maxCompletions;
|
|
1422
|
+
_completions = [];
|
|
1423
|
+
_completionIndex = -1;
|
|
1424
|
+
// -1 = original value, 0..n = cycling
|
|
1425
|
+
_preCompletionValue = "";
|
|
1426
|
+
// value before Tab was pressed
|
|
1427
|
+
_showCompletions = false;
|
|
1428
|
+
_onChange;
|
|
1429
|
+
_onSubmit;
|
|
1430
|
+
focusable = true;
|
|
1431
|
+
constructor(style = {}, options = {}) {
|
|
1432
|
+
super({ border: "single", height: 3, ...style });
|
|
1433
|
+
this._placeholder = options.placeholder ?? "";
|
|
1434
|
+
this._maxLength = options.maxLength ?? 4096;
|
|
1435
|
+
this._cwd = options.cwd ?? process.cwd();
|
|
1436
|
+
this._maxCompletions = options.maxCompletions ?? 5;
|
|
1437
|
+
this._onChange = options.onChange;
|
|
1438
|
+
this._onSubmit = options.onSubmit;
|
|
1439
|
+
}
|
|
1440
|
+
get value() {
|
|
1441
|
+
return this._value;
|
|
1442
|
+
}
|
|
1443
|
+
set value(v) {
|
|
1444
|
+
this._value = v.slice(0, this._maxLength);
|
|
1445
|
+
this._cursorPos = Math.min(this._cursorPos, this._value.length);
|
|
1446
|
+
this._dismissCompletions();
|
|
1447
|
+
}
|
|
1448
|
+
get completions() {
|
|
1449
|
+
return this._completions;
|
|
1450
|
+
}
|
|
1451
|
+
get isShowingCompletions() {
|
|
1452
|
+
return this._showCompletions && this._completions.length > 0;
|
|
1453
|
+
}
|
|
1454
|
+
_dismissCompletions() {
|
|
1455
|
+
this._completions = [];
|
|
1456
|
+
this._completionIndex = -1;
|
|
1457
|
+
this._showCompletions = false;
|
|
1458
|
+
}
|
|
1459
|
+
/** Compute completions for the current value. Does NOT throw. */
|
|
1460
|
+
_computeCompletions() {
|
|
1461
|
+
try {
|
|
1462
|
+
const raw = this._value;
|
|
1463
|
+
const isAbs = path.isAbsolute(raw);
|
|
1464
|
+
const base = isAbs ? raw : path.join(this._cwd, raw);
|
|
1465
|
+
let dir;
|
|
1466
|
+
let prefix;
|
|
1467
|
+
if (raw === "" || raw.endsWith("/") || raw.endsWith(path.sep)) {
|
|
1468
|
+
dir = isAbs ? raw || "/" : path.join(this._cwd, raw || ".");
|
|
1469
|
+
prefix = "";
|
|
1470
|
+
} else {
|
|
1471
|
+
dir = path.dirname(base);
|
|
1472
|
+
prefix = path.basename(base).toLowerCase();
|
|
1473
|
+
}
|
|
1474
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
1475
|
+
const matches = entries.filter((e) => e.name.toLowerCase().startsWith(prefix)).slice(0, this._maxCompletions).map((e) => {
|
|
1476
|
+
const full = path.join(dir, e.name);
|
|
1477
|
+
const candidate = isAbs ? full + (e.isDirectory() ? path.sep : "") : path.relative(this._cwd, full) + (e.isDirectory() ? path.sep : "");
|
|
1478
|
+
return candidate;
|
|
1479
|
+
});
|
|
1480
|
+
return matches;
|
|
1481
|
+
} catch {
|
|
1482
|
+
return [];
|
|
1483
|
+
}
|
|
1484
|
+
}
|
|
1485
|
+
/** Trigger tab-completion. */
|
|
1486
|
+
triggerCompletion() {
|
|
1487
|
+
if (!this._showCompletions || this._completions.length === 0) {
|
|
1488
|
+
this._preCompletionValue = this._value;
|
|
1489
|
+
this._completions = this._computeCompletions();
|
|
1490
|
+
if (this._completions.length === 0) {
|
|
1491
|
+
this._showCompletions = false;
|
|
1492
|
+
return;
|
|
1493
|
+
}
|
|
1494
|
+
this._showCompletions = true;
|
|
1495
|
+
this._completionIndex = 0;
|
|
1496
|
+
} else {
|
|
1497
|
+
this._completionIndex = (this._completionIndex + 1) % this._completions.length;
|
|
1498
|
+
}
|
|
1499
|
+
this._value = this._completions[this._completionIndex];
|
|
1500
|
+
this._cursorPos = this._value.length;
|
|
1501
|
+
this._onChange?.(this._value);
|
|
1502
|
+
this.markDirty();
|
|
1503
|
+
}
|
|
1504
|
+
insertChar(char) {
|
|
1505
|
+
if (this._value.length >= this._maxLength) return;
|
|
1506
|
+
this._value = this._value.slice(0, this._cursorPos) + char + this._value.slice(this._cursorPos);
|
|
1507
|
+
this._cursorPos++;
|
|
1508
|
+
this._dismissCompletions();
|
|
1509
|
+
this._onChange?.(this._value);
|
|
1510
|
+
this.markDirty();
|
|
1511
|
+
}
|
|
1512
|
+
deleteBack() {
|
|
1513
|
+
if (this._cursorPos > 0) {
|
|
1514
|
+
this._value = this._value.slice(0, this._cursorPos - 1) + this._value.slice(this._cursorPos);
|
|
1515
|
+
this._cursorPos--;
|
|
1516
|
+
this._dismissCompletions();
|
|
1517
|
+
this._onChange?.(this._value);
|
|
1518
|
+
this.markDirty();
|
|
1519
|
+
}
|
|
1520
|
+
}
|
|
1521
|
+
deleteForward() {
|
|
1522
|
+
if (this._cursorPos < this._value.length) {
|
|
1523
|
+
this._value = this._value.slice(0, this._cursorPos) + this._value.slice(this._cursorPos + 1);
|
|
1524
|
+
this._dismissCompletions();
|
|
1525
|
+
this._onChange?.(this._value);
|
|
1526
|
+
this.markDirty();
|
|
1527
|
+
}
|
|
1528
|
+
}
|
|
1529
|
+
moveCursorLeft() {
|
|
1530
|
+
this._cursorPos = Math.max(0, this._cursorPos - 1);
|
|
1531
|
+
this.markDirty();
|
|
1532
|
+
}
|
|
1533
|
+
moveCursorRight() {
|
|
1534
|
+
this._cursorPos = Math.min(this._value.length, this._cursorPos + 1);
|
|
1535
|
+
this.markDirty();
|
|
1536
|
+
}
|
|
1537
|
+
moveCursorHome() {
|
|
1538
|
+
this._cursorPos = 0;
|
|
1539
|
+
this.markDirty();
|
|
1540
|
+
}
|
|
1541
|
+
moveCursorEnd() {
|
|
1542
|
+
this._cursorPos = this._value.length;
|
|
1543
|
+
this.markDirty();
|
|
1544
|
+
}
|
|
1545
|
+
submit() {
|
|
1546
|
+
this._dismissCompletions();
|
|
1547
|
+
this._onSubmit?.(this._value);
|
|
1548
|
+
}
|
|
1549
|
+
clear() {
|
|
1550
|
+
this._value = "";
|
|
1551
|
+
this._cursorPos = 0;
|
|
1552
|
+
this._dismissCompletions();
|
|
1553
|
+
this._onChange?.("");
|
|
1554
|
+
this.markDirty();
|
|
1555
|
+
}
|
|
1556
|
+
/**
|
|
1557
|
+
* Handle key events. Call this from your input loop.
|
|
1558
|
+
* Tab — trigger/cycle completions
|
|
1559
|
+
* Esc — dismiss completions
|
|
1560
|
+
* Enter — submit current value
|
|
1561
|
+
*/
|
|
1562
|
+
handleKey(event) {
|
|
1563
|
+
switch (event.key) {
|
|
1564
|
+
case "tab":
|
|
1565
|
+
this.triggerCompletion();
|
|
1566
|
+
break;
|
|
1567
|
+
case "escape":
|
|
1568
|
+
this._dismissCompletions();
|
|
1569
|
+
this.markDirty();
|
|
1570
|
+
break;
|
|
1571
|
+
case "backspace":
|
|
1572
|
+
this.deleteBack();
|
|
1573
|
+
break;
|
|
1574
|
+
case "delete":
|
|
1575
|
+
this.deleteForward();
|
|
1576
|
+
break;
|
|
1577
|
+
case "left":
|
|
1578
|
+
this.moveCursorLeft();
|
|
1579
|
+
break;
|
|
1580
|
+
case "right":
|
|
1581
|
+
this.moveCursorRight();
|
|
1582
|
+
break;
|
|
1583
|
+
case "home":
|
|
1584
|
+
this.moveCursorHome();
|
|
1585
|
+
break;
|
|
1586
|
+
case "end":
|
|
1587
|
+
this.moveCursorEnd();
|
|
1588
|
+
break;
|
|
1589
|
+
case "return":
|
|
1590
|
+
case "enter":
|
|
1591
|
+
this.submit();
|
|
1592
|
+
break;
|
|
1593
|
+
default:
|
|
1594
|
+
if (event.key && event.key.length === 1 && !event.ctrl && !event.alt) {
|
|
1595
|
+
this.insertChar(event.key);
|
|
1596
|
+
}
|
|
1597
|
+
}
|
|
1598
|
+
}
|
|
1599
|
+
_renderSelf(screen) {
|
|
1600
|
+
const rect = this._getContentRect();
|
|
1601
|
+
const { x, y, width, height } = rect;
|
|
1602
|
+
if (width <= 0 || height <= 0) return;
|
|
1603
|
+
const attrs = (0, import_core15.styleToCellAttrs)(this._style);
|
|
1604
|
+
if (height <= 1 && this._showCompletions) {
|
|
1605
|
+
this._showCompletions = false;
|
|
1606
|
+
}
|
|
1607
|
+
if (this._value.length === 0 && !this.isFocused) {
|
|
1608
|
+
screen.writeString(x, y, (0, import_core15.truncate)(this._placeholder, width), { ...attrs, dim: true });
|
|
1609
|
+
} else {
|
|
1610
|
+
const visibleWidth = width - 1;
|
|
1611
|
+
let scrollX = 0;
|
|
1612
|
+
if (this._cursorPos > visibleWidth) {
|
|
1613
|
+
scrollX = this._cursorPos - visibleWidth;
|
|
1614
|
+
}
|
|
1615
|
+
const visibleText = this._value.slice(scrollX, scrollX + visibleWidth);
|
|
1616
|
+
screen.writeString(x, y, visibleText, attrs);
|
|
1617
|
+
if (this.isFocused) {
|
|
1618
|
+
const cursorScreenPos = x + this._cursorPos - scrollX;
|
|
1619
|
+
if (cursorScreenPos >= x && cursorScreenPos < x + width) {
|
|
1620
|
+
const cursorChar = this._cursorPos < this._value.length ? this._value[this._cursorPos] : " ";
|
|
1621
|
+
screen.setCell(cursorScreenPos, y, {
|
|
1622
|
+
char: cursorChar,
|
|
1623
|
+
...attrs,
|
|
1624
|
+
inverse: true
|
|
1625
|
+
});
|
|
1626
|
+
}
|
|
1627
|
+
}
|
|
1628
|
+
}
|
|
1629
|
+
if (this._showCompletions && this._completions.length > 0 && height > 1) {
|
|
1630
|
+
const maxRows = Math.min(this._completions.length, height - 1);
|
|
1631
|
+
for (let i = 0; i < maxRows; i++) {
|
|
1632
|
+
const row = y + 1 + i;
|
|
1633
|
+
const isSelected = i === this._completionIndex;
|
|
1634
|
+
const prefix = isSelected ? import_core15.caps.unicode ? "\u25B6 " : "> " : " ";
|
|
1635
|
+
const entry = this._completions[i];
|
|
1636
|
+
screen.writeString(x, row, (0, import_core15.truncate)(prefix + entry, width), {
|
|
1637
|
+
...attrs,
|
|
1638
|
+
bold: isSelected,
|
|
1639
|
+
inverse: isSelected
|
|
1640
|
+
});
|
|
1641
|
+
}
|
|
1642
|
+
}
|
|
1643
|
+
}
|
|
1644
|
+
};
|
|
1645
|
+
|
|
1646
|
+
// src/KeyboardShortcuts.ts
|
|
1647
|
+
var import_widgets16 = require("@termuijs/widgets");
|
|
1648
|
+
var import_core16 = require("@termuijs/core");
|
|
1649
|
+
var KeyboardShortcuts = class extends import_widgets16.Widget {
|
|
1650
|
+
_bindings;
|
|
1651
|
+
_keyColor;
|
|
1652
|
+
_categoryColor;
|
|
1653
|
+
_columns;
|
|
1654
|
+
_showCategories;
|
|
1655
|
+
constructor(bindings, options = {}) {
|
|
1656
|
+
super({});
|
|
1657
|
+
this._bindings = bindings;
|
|
1658
|
+
this._keyColor = options.keyColor ?? { type: "named", name: "cyan" };
|
|
1659
|
+
this._categoryColor = options.categoryColor ?? { type: "named", name: "yellow" };
|
|
1660
|
+
this._columns = Math.max(1, options.columns ?? 2);
|
|
1661
|
+
this._showCategories = options.showCategories ?? true;
|
|
1662
|
+
}
|
|
1663
|
+
/** Replace all bindings and trigger a re-render. */
|
|
1664
|
+
setBindings(bindings) {
|
|
1665
|
+
this._bindings = bindings;
|
|
1666
|
+
this.markDirty();
|
|
1667
|
+
}
|
|
1668
|
+
/** Group bindings by category, preserving insertion order. */
|
|
1669
|
+
_groupBindings() {
|
|
1670
|
+
const groups = [];
|
|
1671
|
+
const indexMap = /* @__PURE__ */ new Map();
|
|
1672
|
+
for (const b of this._bindings) {
|
|
1673
|
+
const cat = b.category ?? "";
|
|
1674
|
+
const key = cat;
|
|
1675
|
+
if (!indexMap.has(key)) {
|
|
1676
|
+
indexMap.set(key, groups.length);
|
|
1677
|
+
groups.push({ category: b.category, bindings: [] });
|
|
1678
|
+
}
|
|
1679
|
+
groups[indexMap.get(key)].bindings.push(b);
|
|
1680
|
+
}
|
|
1681
|
+
return groups;
|
|
1682
|
+
}
|
|
1683
|
+
/**
|
|
1684
|
+
* Render a key label in a bordered box style:
|
|
1685
|
+
* ┌─────┐
|
|
1686
|
+
* │ key │
|
|
1687
|
+
* └─────┘
|
|
1688
|
+
*
|
|
1689
|
+
* We fit it on one line: [ key ] — box drawing characters or ASCII fallback.
|
|
1690
|
+
*/
|
|
1691
|
+
_renderKeyLabel(screen, kx, ky, key, attrs) {
|
|
1692
|
+
if (import_core16.caps.unicode) {
|
|
1693
|
+
const label = `[${key}]`;
|
|
1694
|
+
screen.writeString(kx, ky, label, { ...attrs, fg: this._keyColor, bold: true });
|
|
1695
|
+
return label.length;
|
|
1696
|
+
} else {
|
|
1697
|
+
const label = `[${key}]`;
|
|
1698
|
+
screen.writeString(kx, ky, label, { ...attrs, fg: this._keyColor, bold: true });
|
|
1699
|
+
return label.length;
|
|
1700
|
+
}
|
|
1701
|
+
}
|
|
1702
|
+
_renderSelf(screen) {
|
|
1703
|
+
const { x, y, width, height } = this._rect;
|
|
1704
|
+
if (width <= 0 || height <= 0) return;
|
|
1705
|
+
const attrs = (0, import_core16.styleToCellAttrs)(this._style);
|
|
1706
|
+
const groups = this._groupBindings();
|
|
1707
|
+
let row = y;
|
|
1708
|
+
const colWidth = Math.floor(width / this._columns);
|
|
1709
|
+
for (const group of groups) {
|
|
1710
|
+
if (row >= y + height) break;
|
|
1711
|
+
if (this._showCategories && group.category) {
|
|
1712
|
+
const heading = group.category.toUpperCase();
|
|
1713
|
+
const divider = import_core16.caps.unicode ? "\u2500" : "-";
|
|
1714
|
+
const line = heading + " " + divider.repeat(Math.max(0, width - heading.length - 1));
|
|
1715
|
+
screen.writeString(x, row, line.slice(0, width), {
|
|
1716
|
+
...attrs,
|
|
1717
|
+
fg: this._categoryColor,
|
|
1718
|
+
bold: true
|
|
1719
|
+
});
|
|
1720
|
+
row++;
|
|
1721
|
+
if (row >= y + height) break;
|
|
1722
|
+
}
|
|
1723
|
+
for (let i = 0; i < group.bindings.length; i += this._columns) {
|
|
1724
|
+
if (row >= y + height) break;
|
|
1725
|
+
for (let col = 0; col < this._columns; col++) {
|
|
1726
|
+
const binding = group.bindings[i + col];
|
|
1727
|
+
if (!binding) continue;
|
|
1728
|
+
const cx = x + col * colWidth;
|
|
1729
|
+
const availWidth = colWidth - 1;
|
|
1730
|
+
if (availWidth <= 0) continue;
|
|
1731
|
+
const labelLen = this._renderKeyLabel(screen, cx, row, binding.key, attrs);
|
|
1732
|
+
const descX = cx + labelLen + 1;
|
|
1733
|
+
const descWidth = availWidth - labelLen - 1;
|
|
1734
|
+
if (descWidth > 0) {
|
|
1735
|
+
const desc = binding.description.slice(0, descWidth);
|
|
1736
|
+
screen.writeString(descX, row, desc, attrs);
|
|
1737
|
+
}
|
|
1738
|
+
}
|
|
1739
|
+
row++;
|
|
1740
|
+
}
|
|
1741
|
+
if (this._showCategories && group.category) {
|
|
1742
|
+
row++;
|
|
1743
|
+
}
|
|
1744
|
+
}
|
|
1745
|
+
}
|
|
1746
|
+
};
|
|
811
1747
|
// Annotate the CommonJS export names for ESM import in node:
|
|
812
1748
|
0 && (module.exports = {
|
|
813
1749
|
Box,
|
|
@@ -816,10 +1752,17 @@ var CommandPalette = class extends import_widgets11.Widget {
|
|
|
816
1752
|
Divider,
|
|
817
1753
|
Form,
|
|
818
1754
|
Gauge,
|
|
1755
|
+
KeyboardShortcuts,
|
|
819
1756
|
List,
|
|
820
1757
|
LogView,
|
|
821
1758
|
Modal,
|
|
822
1759
|
MultiSelect,
|
|
1760
|
+
NonInteractiveError,
|
|
1761
|
+
NotificationCenter,
|
|
1762
|
+
NotificationStore,
|
|
1763
|
+
NumberInput,
|
|
1764
|
+
PasswordInput,
|
|
1765
|
+
PathInput,
|
|
823
1766
|
ProgressBar,
|
|
824
1767
|
Select,
|
|
825
1768
|
Spacer,
|
|
@@ -832,6 +1775,9 @@ var CommandPalette = class extends import_widgets11.Widget {
|
|
|
832
1775
|
TextInput,
|
|
833
1776
|
Toast,
|
|
834
1777
|
Tree,
|
|
835
|
-
Widget
|
|
1778
|
+
Widget,
|
|
1779
|
+
notifications,
|
|
1780
|
+
prompt,
|
|
1781
|
+
useNotifications
|
|
836
1782
|
});
|
|
837
1783
|
//# sourceMappingURL=index.cjs.map
|