@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.js
CHANGED
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
LogView,
|
|
12
12
|
ProgressBar,
|
|
13
13
|
Spinner,
|
|
14
|
-
Widget as
|
|
14
|
+
Widget as Widget17
|
|
15
15
|
} from "@termuijs/widgets";
|
|
16
16
|
|
|
17
17
|
// src/Divider.ts
|
|
@@ -58,7 +58,7 @@ var Spacer = class extends Widget2 {
|
|
|
58
58
|
|
|
59
59
|
// src/Tabs.ts
|
|
60
60
|
import { Widget as Widget3 } from "@termuijs/widgets";
|
|
61
|
-
import { mergeStyles as mergeStyles3, defaultStyle as defaultStyle3, styleToCellAttrs as styleToCellAttrs2 } from "@termuijs/core";
|
|
61
|
+
import { mergeStyles as mergeStyles3, defaultStyle as defaultStyle3, styleToCellAttrs as styleToCellAttrs2, caps } from "@termuijs/core";
|
|
62
62
|
var Tabs = class extends Widget3 {
|
|
63
63
|
_tabs = [];
|
|
64
64
|
_activeIndex = 0;
|
|
@@ -99,7 +99,7 @@ var Tabs = class extends Widget3 {
|
|
|
99
99
|
for (let i = 0; i < this._tabs.length; i++) {
|
|
100
100
|
const tab = this._tabs[i];
|
|
101
101
|
const isActive = i === this._activeIndex;
|
|
102
|
-
const label = isActive ? ` \u25CF ${tab.label} ` : ` ${tab.label} `;
|
|
102
|
+
const label = isActive ? ` ${caps.unicode ? "\u25CF" : "*"} ${tab.label} ` : ` ${tab.label} `;
|
|
103
103
|
screen.writeString(col, y, label, {
|
|
104
104
|
...attrs,
|
|
105
105
|
fg: isActive ? this._activeColor : this._inactiveColor,
|
|
@@ -108,7 +108,7 @@ var Tabs = class extends Widget3 {
|
|
|
108
108
|
});
|
|
109
109
|
col += label.length;
|
|
110
110
|
if (i < this._tabs.length - 1) {
|
|
111
|
-
screen.writeString(col, y, "\u2502", { ...attrs, dim: true });
|
|
111
|
+
screen.writeString(col, y, caps.unicode ? "\u2502" : "|", { ...attrs, dim: true });
|
|
112
112
|
col++;
|
|
113
113
|
}
|
|
114
114
|
}
|
|
@@ -118,7 +118,7 @@ var Tabs = class extends Widget3 {
|
|
|
118
118
|
|
|
119
119
|
// src/Modal.ts
|
|
120
120
|
import { Widget as Widget4 } from "@termuijs/widgets";
|
|
121
|
-
import { mergeStyles as mergeStyles4, defaultStyle as defaultStyle4, styleToCellAttrs as styleToCellAttrs3, getBorderChars } from "@termuijs/core";
|
|
121
|
+
import { mergeStyles as mergeStyles4, defaultStyle as defaultStyle4, styleToCellAttrs as styleToCellAttrs3, getBorderChars, caps as caps2 } from "@termuijs/core";
|
|
122
122
|
var Modal = class extends Widget4 {
|
|
123
123
|
_title;
|
|
124
124
|
_modalWidth;
|
|
@@ -133,7 +133,7 @@ var Modal = class extends Widget4 {
|
|
|
133
133
|
this._modalWidth = options.width ?? 50;
|
|
134
134
|
this._modalHeight = options.height ?? 15;
|
|
135
135
|
this._borderColor = options.borderColor ?? { type: "named", name: "cyan" };
|
|
136
|
-
this._backdropChar = options.backdropChar ?? "\u2591";
|
|
136
|
+
this._backdropChar = options.backdropChar ?? (caps2.unicode ? "\u2591" : " ");
|
|
137
137
|
}
|
|
138
138
|
get visible() {
|
|
139
139
|
return this._visible;
|
|
@@ -190,7 +190,7 @@ var Modal = class extends Widget4 {
|
|
|
190
190
|
|
|
191
191
|
// src/Select.ts
|
|
192
192
|
import { Widget as Widget5 } from "@termuijs/widgets";
|
|
193
|
-
import { mergeStyles as mergeStyles5, defaultStyle as defaultStyle5, styleToCellAttrs as styleToCellAttrs4 } from "@termuijs/core";
|
|
193
|
+
import { mergeStyles as mergeStyles5, defaultStyle as defaultStyle5, styleToCellAttrs as styleToCellAttrs4, caps as caps3 } from "@termuijs/core";
|
|
194
194
|
var Select = class extends Widget5 {
|
|
195
195
|
_options;
|
|
196
196
|
_selectedIndex = 0;
|
|
@@ -257,13 +257,13 @@ var Select = class extends Widget5 {
|
|
|
257
257
|
const attrs = styleToCellAttrs4(this.style);
|
|
258
258
|
const sel = this._options[this._selectedIndex];
|
|
259
259
|
const label = sel ? sel.label : this._placeholder;
|
|
260
|
-
const prefix = this._isOpen ? "\u25BC " : "\u25B6 ";
|
|
260
|
+
const prefix = this._isOpen ? caps3.unicode ? "\u25BC " : "v " : caps3.unicode ? "\u25B6 " : "> ";
|
|
261
261
|
screen.writeString(x, y, prefix + label.slice(0, width - 2), { ...attrs, fg: this._activeColor });
|
|
262
262
|
if (this._isOpen) {
|
|
263
263
|
for (let i = 0; i < this._options.length; i++) {
|
|
264
264
|
const o = this._options[i];
|
|
265
265
|
const isSel = i === this._selectedIndex;
|
|
266
|
-
const m = isSel ? "\u25CF " : " ";
|
|
266
|
+
const m = isSel ? caps3.unicode ? "\u25CF " : "* " : " ";
|
|
267
267
|
screen.writeString(x, y + 1 + i, m + o.label.slice(0, width - 2), {
|
|
268
268
|
...attrs,
|
|
269
269
|
fg: o.disabled ? { type: "named", name: "brightBlack" } : isSel ? this._activeColor : attrs.fg,
|
|
@@ -277,7 +277,7 @@ var Select = class extends Widget5 {
|
|
|
277
277
|
|
|
278
278
|
// src/MultiSelect.ts
|
|
279
279
|
import { Widget as Widget6 } from "@termuijs/widgets";
|
|
280
|
-
import { mergeStyles as mergeStyles6, defaultStyle as defaultStyle6, styleToCellAttrs as styleToCellAttrs5 } from "@termuijs/core";
|
|
280
|
+
import { mergeStyles as mergeStyles6, defaultStyle as defaultStyle6, styleToCellAttrs as styleToCellAttrs5, caps as caps4 } from "@termuijs/core";
|
|
281
281
|
var MultiSelect = class extends Widget6 {
|
|
282
282
|
_options;
|
|
283
283
|
_cursorIndex = 0;
|
|
@@ -291,8 +291,8 @@ var MultiSelect = class extends Widget6 {
|
|
|
291
291
|
super(mergeStyles6(defaultStyle6(), { height: Math.max(options.length, 1) }));
|
|
292
292
|
this._options = options;
|
|
293
293
|
this._activeColor = config.activeColor ?? { type: "named", name: "cyan" };
|
|
294
|
-
this._checkChar = config.checkChar ?? "\u25FC";
|
|
295
|
-
this._uncheckChar = config.uncheckChar ?? "\u25FB";
|
|
294
|
+
this._checkChar = config.checkChar ?? (caps4.unicode ? "\u25FC" : "[x]");
|
|
295
|
+
this._uncheckChar = config.uncheckChar ?? (caps4.unicode ? "\u25FB" : "[ ]");
|
|
296
296
|
this._onSubmit = config.onSubmit;
|
|
297
297
|
}
|
|
298
298
|
get selectedOptions() {
|
|
@@ -332,7 +332,7 @@ var MultiSelect = class extends Widget6 {
|
|
|
332
332
|
const o = this._options[i];
|
|
333
333
|
const active = i === this._cursorIndex;
|
|
334
334
|
const checked = this._checked.has(i);
|
|
335
|
-
const label = `${active ? "\u276F " : " "}${checked ? this._checkChar : this._uncheckChar} ${o.label}`;
|
|
335
|
+
const label = `${active ? caps4.unicode ? "\u276F " : "> " : " "}${checked ? this._checkChar : this._uncheckChar} ${o.label}`;
|
|
336
336
|
screen.writeString(x, y + i, label.slice(0, width), {
|
|
337
337
|
...attrs,
|
|
338
338
|
fg: o.disabled ? { type: "named", name: "brightBlack" } : active ? this._activeColor : attrs.fg,
|
|
@@ -345,7 +345,7 @@ var MultiSelect = class extends Widget6 {
|
|
|
345
345
|
|
|
346
346
|
// src/Tree.ts
|
|
347
347
|
import { Widget as Widget7 } from "@termuijs/widgets";
|
|
348
|
-
import { mergeStyles as mergeStyles7, defaultStyle as defaultStyle7, styleToCellAttrs as styleToCellAttrs6 } from "@termuijs/core";
|
|
348
|
+
import { mergeStyles as mergeStyles7, defaultStyle as defaultStyle7, styleToCellAttrs as styleToCellAttrs6, caps as caps5 } from "@termuijs/core";
|
|
349
349
|
var Tree = class extends Widget7 {
|
|
350
350
|
_roots;
|
|
351
351
|
_cursorIndex = 0;
|
|
@@ -360,12 +360,12 @@ var Tree = class extends Widget7 {
|
|
|
360
360
|
}
|
|
361
361
|
_flatten() {
|
|
362
362
|
const result = [];
|
|
363
|
-
const walk = (nodes, depth,
|
|
363
|
+
const walk = (nodes, depth, path2) => {
|
|
364
364
|
for (let i = 0; i < nodes.length; i++) {
|
|
365
365
|
const node = nodes[i];
|
|
366
366
|
const hasChildren = (node.children?.length ?? 0) > 0;
|
|
367
|
-
result.push({ node, depth, path: [...
|
|
368
|
-
if (hasChildren && node.expanded) walk(node.children, depth + 1, [...
|
|
367
|
+
result.push({ node, depth, path: [...path2, i], hasChildren });
|
|
368
|
+
if (hasChildren && node.expanded) walk(node.children, depth + 1, [...path2, i]);
|
|
369
369
|
}
|
|
370
370
|
};
|
|
371
371
|
walk(this._roots, 0, []);
|
|
@@ -408,7 +408,7 @@ var Tree = class extends Widget7 {
|
|
|
408
408
|
const it = flat[i];
|
|
409
409
|
const active = i === this._cursorIndex;
|
|
410
410
|
const indent = " ".repeat(it.depth);
|
|
411
|
-
const icon = it.hasChildren ? it.node.expanded ? "\u25BC " : "\u25B6 " : " ";
|
|
411
|
+
const icon = it.hasChildren ? it.node.expanded ? caps5.unicode ? "\u25BC " : "v " : caps5.unicode ? "\u25B6 " : "> " : " ";
|
|
412
412
|
const nodeIcon = it.node.icon ? `${it.node.icon} ` : "";
|
|
413
413
|
const line = `${indent}${icon}${nodeIcon}${it.node.label}`;
|
|
414
414
|
screen.writeString(x, y + i, line.slice(0, width), { ...attrs, fg: active ? this._activeColor : attrs.fg, bold: active });
|
|
@@ -418,8 +418,9 @@ var Tree = class extends Widget7 {
|
|
|
418
418
|
|
|
419
419
|
// src/Toast.ts
|
|
420
420
|
import { Widget as Widget8 } from "@termuijs/widgets";
|
|
421
|
-
import { mergeStyles as mergeStyles8, defaultStyle as defaultStyle8, styleToCellAttrs as styleToCellAttrs7 } from "@termuijs/core";
|
|
422
|
-
var
|
|
421
|
+
import { mergeStyles as mergeStyles8, defaultStyle as defaultStyle8, styleToCellAttrs as styleToCellAttrs7, caps as caps6 } from "@termuijs/core";
|
|
422
|
+
var ICONS_UNICODE = { info: "\u2139", success: "\u2713", warning: "\u26A0", error: "\u2717" };
|
|
423
|
+
var ICONS_ASCII = { info: "i", success: "+", warning: "!", error: "x" };
|
|
423
424
|
var COLORS = { info: "cyan", success: "green", warning: "yellow", error: "red" };
|
|
424
425
|
var Toast = class extends Widget8 {
|
|
425
426
|
_messages = [];
|
|
@@ -459,10 +460,11 @@ var Toast = class extends Widget8 {
|
|
|
459
460
|
const isBottom = this._position.includes("bottom");
|
|
460
461
|
const sx = isRight ? x + width - tw - 1 : x + 1;
|
|
461
462
|
const sy = isBottom ? y + height - visible.length - 1 : y + 1;
|
|
463
|
+
const icons = caps6.unicode ? ICONS_UNICODE : ICONS_ASCII;
|
|
462
464
|
const attrs = styleToCellAttrs7(this.style);
|
|
463
465
|
for (let i = 0; i < visible.length; i++) {
|
|
464
466
|
const m = visible[i];
|
|
465
|
-
const label = ` ${
|
|
467
|
+
const label = ` ${icons[m.type]} ${m.text} `.slice(0, tw).padEnd(tw);
|
|
466
468
|
screen.writeString(sx, sy + i, label, { ...attrs, fg: { type: "named", name: COLORS[m.type] }, bold: true });
|
|
467
469
|
}
|
|
468
470
|
}
|
|
@@ -653,7 +655,7 @@ var Form = class extends Widget10 {
|
|
|
653
655
|
|
|
654
656
|
// src/CommandPalette.ts
|
|
655
657
|
import { Widget as Widget11 } from "@termuijs/widgets";
|
|
656
|
-
import { mergeStyles as mergeStyles11, defaultStyle as defaultStyle11, styleToCellAttrs as styleToCellAttrs10, getBorderChars as getBorderChars3 } from "@termuijs/core";
|
|
658
|
+
import { mergeStyles as mergeStyles11, defaultStyle as defaultStyle11, styleToCellAttrs as styleToCellAttrs10, getBorderChars as getBorderChars3, caps as caps7 } from "@termuijs/core";
|
|
657
659
|
var CommandPalette = class extends Widget11 {
|
|
658
660
|
_commands;
|
|
659
661
|
_filtered = [];
|
|
@@ -746,7 +748,8 @@ var CommandPalette = class extends Widget11 {
|
|
|
746
748
|
if (!this._visible) return;
|
|
747
749
|
const { x, y, width, height } = this._rect;
|
|
748
750
|
const attrs = styleToCellAttrs10(this.style);
|
|
749
|
-
|
|
751
|
+
const backdropCh = caps7.unicode ? "\u2591" : " ";
|
|
752
|
+
for (let r = 0; r < height; r++) screen.writeString(x, y + r, backdropCh.repeat(width), { ...attrs, dim: true });
|
|
750
753
|
const vis = this._filtered.slice(0, this._maxVisible);
|
|
751
754
|
const bw = Math.min(60, width - 4);
|
|
752
755
|
const bh = Math.min(vis.length + 3, height - 2);
|
|
@@ -758,13 +761,13 @@ var CommandPalette = class extends Widget11 {
|
|
|
758
761
|
screen.writeString(bx, by, border.topLeft + border.top.repeat(bw - 2) + border.topRight, ba);
|
|
759
762
|
screen.writeString(bx, by + 1, border.left, ba);
|
|
760
763
|
const input = this._query || this._placeholder;
|
|
761
|
-
screen.writeString(bx + 1, by + 1, ("
|
|
764
|
+
screen.writeString(bx + 1, by + 1, (` ${caps7.unicode ? "\u{1F50D}" : "[?]"} ` + input).slice(0, bw - 2).padEnd(bw - 2), { ...attrs, dim: !this._query });
|
|
762
765
|
screen.writeString(bx + bw - 1, by + 1, border.right, ba);
|
|
763
766
|
screen.writeString(bx, by + 2, border.left + "\u2500".repeat(bw - 2) + border.right, ba);
|
|
764
767
|
for (let i = 0; i < vis.length && i + 3 < bh - 1; i++) {
|
|
765
768
|
const c = vis[i];
|
|
766
769
|
const active = i === this._selectedIndex;
|
|
767
|
-
const label = (active ? "\u276F " : " ") + c.label;
|
|
770
|
+
const label = (active ? caps7.unicode ? "\u276F " : "> " : " ") + c.label;
|
|
768
771
|
const sc = c.shortcut ?? "";
|
|
769
772
|
screen.writeString(bx, by + 3 + i, border.left, ba);
|
|
770
773
|
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 });
|
|
@@ -775,6 +778,919 @@ var CommandPalette = class extends Widget11 {
|
|
|
775
778
|
screen.writeString(bx, last, border.bottomLeft + border.bottom.repeat(bw - 2) + border.bottomRight, ba);
|
|
776
779
|
}
|
|
777
780
|
};
|
|
781
|
+
|
|
782
|
+
// src/prompts.ts
|
|
783
|
+
import * as readline from "readline";
|
|
784
|
+
var NonInteractiveError = class extends Error {
|
|
785
|
+
constructor() {
|
|
786
|
+
super("Prompts require an interactive TTY. stdin is not a TTY.");
|
|
787
|
+
this.name = "NonInteractiveError";
|
|
788
|
+
}
|
|
789
|
+
};
|
|
790
|
+
async function promptText(options) {
|
|
791
|
+
if (!process.stdin.isTTY) throw new NonInteractiveError();
|
|
792
|
+
const defaultHint = options.default ? ` (${options.default})` : "";
|
|
793
|
+
const placeholder = options.placeholder ? ` [${options.placeholder}]` : "";
|
|
794
|
+
return new Promise((resolve) => {
|
|
795
|
+
const rl = readline.createInterface({
|
|
796
|
+
input: process.stdin,
|
|
797
|
+
output: process.stdout
|
|
798
|
+
});
|
|
799
|
+
const ask = () => {
|
|
800
|
+
rl.question(`${options.message}${defaultHint}${placeholder}: `, (answer) => {
|
|
801
|
+
const value = answer.trim() || options.default || "";
|
|
802
|
+
if (options.validate) {
|
|
803
|
+
const error = options.validate(value);
|
|
804
|
+
if (error) {
|
|
805
|
+
process.stdout.write(` ${error}
|
|
806
|
+
`);
|
|
807
|
+
ask();
|
|
808
|
+
return;
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
rl.close();
|
|
812
|
+
resolve(value);
|
|
813
|
+
});
|
|
814
|
+
};
|
|
815
|
+
ask();
|
|
816
|
+
});
|
|
817
|
+
}
|
|
818
|
+
async function promptConfirm(options) {
|
|
819
|
+
if (!process.stdin.isTTY) throw new NonInteractiveError();
|
|
820
|
+
const hint = options.default === true ? "Y/n" : options.default === false ? "y/N" : "y/n";
|
|
821
|
+
return new Promise((resolve) => {
|
|
822
|
+
const rl = readline.createInterface({
|
|
823
|
+
input: process.stdin,
|
|
824
|
+
output: process.stdout
|
|
825
|
+
});
|
|
826
|
+
rl.question(`${options.message} [${hint}]: `, (answer) => {
|
|
827
|
+
rl.close();
|
|
828
|
+
const a = answer.trim().toLowerCase();
|
|
829
|
+
if (a === "y" || a === "yes") {
|
|
830
|
+
resolve(true);
|
|
831
|
+
return;
|
|
832
|
+
}
|
|
833
|
+
if (a === "n" || a === "no") {
|
|
834
|
+
resolve(false);
|
|
835
|
+
return;
|
|
836
|
+
}
|
|
837
|
+
if (a === "" && options.default !== void 0) {
|
|
838
|
+
resolve(options.default);
|
|
839
|
+
return;
|
|
840
|
+
}
|
|
841
|
+
resolve(false);
|
|
842
|
+
});
|
|
843
|
+
});
|
|
844
|
+
}
|
|
845
|
+
async function promptSelect(options) {
|
|
846
|
+
if (!process.stdin.isTTY) throw new NonInteractiveError();
|
|
847
|
+
const { options: choices, default: defaultValue } = options;
|
|
848
|
+
process.stdout.write(`${options.message}
|
|
849
|
+
`);
|
|
850
|
+
choices.forEach((opt, i) => {
|
|
851
|
+
const isDefault = opt.value === defaultValue;
|
|
852
|
+
process.stdout.write(` ${i + 1}. ${opt.label}${isDefault ? " (default)" : ""}
|
|
853
|
+
`);
|
|
854
|
+
});
|
|
855
|
+
return new Promise((resolve) => {
|
|
856
|
+
const rl = readline.createInterface({
|
|
857
|
+
input: process.stdin,
|
|
858
|
+
output: process.stdout
|
|
859
|
+
});
|
|
860
|
+
const ask = () => {
|
|
861
|
+
rl.question(`Enter number (1-${choices.length}): `, (answer) => {
|
|
862
|
+
const trimmed = answer.trim();
|
|
863
|
+
if (trimmed === "" && defaultValue !== void 0) {
|
|
864
|
+
rl.close();
|
|
865
|
+
resolve(defaultValue);
|
|
866
|
+
return;
|
|
867
|
+
}
|
|
868
|
+
const n = parseInt(trimmed, 10);
|
|
869
|
+
if (!isNaN(n) && n >= 1 && n <= choices.length) {
|
|
870
|
+
rl.close();
|
|
871
|
+
resolve(choices[n - 1].value);
|
|
872
|
+
return;
|
|
873
|
+
}
|
|
874
|
+
process.stdout.write(` Invalid choice. Enter a number 1-${choices.length}.
|
|
875
|
+
`);
|
|
876
|
+
ask();
|
|
877
|
+
});
|
|
878
|
+
};
|
|
879
|
+
ask();
|
|
880
|
+
});
|
|
881
|
+
}
|
|
882
|
+
var prompt = {
|
|
883
|
+
text: promptText,
|
|
884
|
+
confirm: promptConfirm,
|
|
885
|
+
select: promptSelect
|
|
886
|
+
};
|
|
887
|
+
|
|
888
|
+
// src/NotificationCenter.ts
|
|
889
|
+
import { Widget as Widget12 } from "@termuijs/widgets";
|
|
890
|
+
import { useState, useEffect } from "@termuijs/jsx";
|
|
891
|
+
import { caps as caps8 } from "@termuijs/core";
|
|
892
|
+
var NotificationStore = class _NotificationStore {
|
|
893
|
+
static _instance;
|
|
894
|
+
_notifications = [];
|
|
895
|
+
_subs = /* @__PURE__ */ new Set();
|
|
896
|
+
static getInstance() {
|
|
897
|
+
if (!_NotificationStore._instance) {
|
|
898
|
+
_NotificationStore._instance = new _NotificationStore();
|
|
899
|
+
}
|
|
900
|
+
return _NotificationStore._instance;
|
|
901
|
+
}
|
|
902
|
+
push(message, type = "info", durationMs) {
|
|
903
|
+
const id = Date.now().toString(36) + Math.random().toString(36).slice(2);
|
|
904
|
+
const notification = { id, message, type, durationMs, createdAt: Date.now() };
|
|
905
|
+
this._notifications = [...this._notifications, notification];
|
|
906
|
+
this._emit();
|
|
907
|
+
if (durationMs && durationMs > 0) {
|
|
908
|
+
setTimeout(() => this.dismiss(id), durationMs);
|
|
909
|
+
}
|
|
910
|
+
return id;
|
|
911
|
+
}
|
|
912
|
+
dismiss(id) {
|
|
913
|
+
const prev = this._notifications;
|
|
914
|
+
this._notifications = this._notifications.filter((n) => n.id !== id);
|
|
915
|
+
if (this._notifications.length !== prev.length) {
|
|
916
|
+
this._emit();
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
dismissAll() {
|
|
920
|
+
if (this._notifications.length > 0) {
|
|
921
|
+
this._notifications = [];
|
|
922
|
+
this._emit();
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
subscribe(fn) {
|
|
926
|
+
this._subs.add(fn);
|
|
927
|
+
return () => this._subs.delete(fn);
|
|
928
|
+
}
|
|
929
|
+
get notifications() {
|
|
930
|
+
return this._notifications;
|
|
931
|
+
}
|
|
932
|
+
_emit() {
|
|
933
|
+
for (const fn of this._subs) fn(this._notifications);
|
|
934
|
+
}
|
|
935
|
+
};
|
|
936
|
+
var notifications = NotificationStore.getInstance();
|
|
937
|
+
function useNotifications() {
|
|
938
|
+
const store = NotificationStore.getInstance();
|
|
939
|
+
const [current, setCurrent] = useState(store.notifications);
|
|
940
|
+
useEffect(() => {
|
|
941
|
+
const unsub = store.subscribe((ns) => setCurrent([...ns]));
|
|
942
|
+
return unsub;
|
|
943
|
+
}, []);
|
|
944
|
+
return {
|
|
945
|
+
notifications: current,
|
|
946
|
+
push: (msg, type, dur) => store.push(msg, type, dur),
|
|
947
|
+
dismiss: (id) => store.dismiss(id),
|
|
948
|
+
dismissAll: () => store.dismissAll()
|
|
949
|
+
};
|
|
950
|
+
}
|
|
951
|
+
var TYPE_ICONS = {
|
|
952
|
+
info: { unicode: "\u2139", ascii: "i" },
|
|
953
|
+
success: { unicode: "\u2713", ascii: "+" },
|
|
954
|
+
warning: { unicode: "\u26A0", ascii: "!" },
|
|
955
|
+
error: { unicode: "\u2717", ascii: "x" }
|
|
956
|
+
};
|
|
957
|
+
var TYPE_COLORS = {
|
|
958
|
+
info: { type: "named", name: "cyan" },
|
|
959
|
+
success: { type: "named", name: "green" },
|
|
960
|
+
warning: { type: "named", name: "yellow" },
|
|
961
|
+
error: { type: "named", name: "red" }
|
|
962
|
+
};
|
|
963
|
+
var NotificationCenter = class extends Widget12 {
|
|
964
|
+
_position;
|
|
965
|
+
_maxVisible;
|
|
966
|
+
_notifWidth;
|
|
967
|
+
_unsub;
|
|
968
|
+
_current = [];
|
|
969
|
+
constructor(options = {}) {
|
|
970
|
+
super();
|
|
971
|
+
this._position = options.position ?? "top-right";
|
|
972
|
+
this._maxVisible = options.maxVisible ?? 5;
|
|
973
|
+
this._notifWidth = options.width ?? 40;
|
|
974
|
+
const store = NotificationStore.getInstance();
|
|
975
|
+
this._current = store.notifications;
|
|
976
|
+
this._unsub = store.subscribe((ns) => {
|
|
977
|
+
this._current = ns;
|
|
978
|
+
this.markDirty();
|
|
979
|
+
});
|
|
980
|
+
}
|
|
981
|
+
unmount() {
|
|
982
|
+
this._unsub?.();
|
|
983
|
+
this._unsub = void 0;
|
|
984
|
+
super.unmount();
|
|
985
|
+
}
|
|
986
|
+
_renderSelf(screen) {
|
|
987
|
+
const visible = this._current.slice(-this._maxVisible);
|
|
988
|
+
if (visible.length === 0) return;
|
|
989
|
+
const { x, y, width, height } = this._rect;
|
|
990
|
+
const tw = Math.min(this._notifWidth, width - 2);
|
|
991
|
+
if (tw <= 0) return;
|
|
992
|
+
const isRight = this._position.includes("right");
|
|
993
|
+
const isBottom = this._position.includes("bottom");
|
|
994
|
+
const sx = isRight ? x + width - tw - 1 : x + 1;
|
|
995
|
+
const sy = isBottom ? y + height - visible.length - 1 : y + 1;
|
|
996
|
+
for (let i = 0; i < visible.length; i++) {
|
|
997
|
+
const notif = visible[i];
|
|
998
|
+
const icon = caps8.unicode ? TYPE_ICONS[notif.type].unicode : TYPE_ICONS[notif.type].ascii;
|
|
999
|
+
const raw = `${icon} ${notif.message}`;
|
|
1000
|
+
const label = ` ${raw} `.slice(0, tw).padEnd(tw);
|
|
1001
|
+
screen.writeString(sx, sy + i, label, {
|
|
1002
|
+
fg: TYPE_COLORS[notif.type],
|
|
1003
|
+
bold: true
|
|
1004
|
+
});
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
};
|
|
1008
|
+
|
|
1009
|
+
// src/PasswordInput.ts
|
|
1010
|
+
import { Widget as Widget13 } from "@termuijs/widgets";
|
|
1011
|
+
import { styleToCellAttrs as styleToCellAttrs11, truncate, caps as caps9 } from "@termuijs/core";
|
|
1012
|
+
var PasswordInput = class extends Widget13 {
|
|
1013
|
+
_value = "";
|
|
1014
|
+
_cursorPos = 0;
|
|
1015
|
+
_placeholder;
|
|
1016
|
+
_maxLength;
|
|
1017
|
+
_showText = false;
|
|
1018
|
+
_onChange;
|
|
1019
|
+
_onSubmit;
|
|
1020
|
+
focusable = true;
|
|
1021
|
+
// '●' in unicode terminals, '*' in ASCII fallback
|
|
1022
|
+
get _maskChar() {
|
|
1023
|
+
return caps9.unicode ? "\u25CF" : "*";
|
|
1024
|
+
}
|
|
1025
|
+
constructor(style = {}, options = {}) {
|
|
1026
|
+
super({ border: "single", height: 3, ...style });
|
|
1027
|
+
this._placeholder = options.placeholder ?? "";
|
|
1028
|
+
this._maxLength = options.maxLength ?? Infinity;
|
|
1029
|
+
this._onChange = options.onChange;
|
|
1030
|
+
this._onSubmit = options.onSubmit;
|
|
1031
|
+
}
|
|
1032
|
+
/** The actual (unmasked) value. */
|
|
1033
|
+
get value() {
|
|
1034
|
+
return this._value;
|
|
1035
|
+
}
|
|
1036
|
+
set value(v) {
|
|
1037
|
+
this._value = v.slice(0, this._maxLength);
|
|
1038
|
+
this._cursorPos = Math.min(this._cursorPos, this._value.length);
|
|
1039
|
+
}
|
|
1040
|
+
/** Whether the text is currently visible (unmaksed). */
|
|
1041
|
+
get showText() {
|
|
1042
|
+
return this._showText;
|
|
1043
|
+
}
|
|
1044
|
+
/** Toggle visibility of the actual text (Alt+V). */
|
|
1045
|
+
toggleVisibility() {
|
|
1046
|
+
this._showText = !this._showText;
|
|
1047
|
+
this.markDirty();
|
|
1048
|
+
}
|
|
1049
|
+
insertChar(char) {
|
|
1050
|
+
if (this._value.length >= this._maxLength) return;
|
|
1051
|
+
this._value = this._value.slice(0, this._cursorPos) + char + this._value.slice(this._cursorPos);
|
|
1052
|
+
this._cursorPos++;
|
|
1053
|
+
this._onChange?.(this._value);
|
|
1054
|
+
this.markDirty();
|
|
1055
|
+
}
|
|
1056
|
+
deleteBack() {
|
|
1057
|
+
if (this._cursorPos > 0) {
|
|
1058
|
+
this._value = this._value.slice(0, this._cursorPos - 1) + this._value.slice(this._cursorPos);
|
|
1059
|
+
this._cursorPos--;
|
|
1060
|
+
this._onChange?.(this._value);
|
|
1061
|
+
this.markDirty();
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
deleteForward() {
|
|
1065
|
+
if (this._cursorPos < this._value.length) {
|
|
1066
|
+
this._value = this._value.slice(0, this._cursorPos) + this._value.slice(this._cursorPos + 1);
|
|
1067
|
+
this._onChange?.(this._value);
|
|
1068
|
+
this.markDirty();
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
moveCursorLeft() {
|
|
1072
|
+
this._cursorPos = Math.max(0, this._cursorPos - 1);
|
|
1073
|
+
this.markDirty();
|
|
1074
|
+
}
|
|
1075
|
+
moveCursorRight() {
|
|
1076
|
+
this._cursorPos = Math.min(this._value.length, this._cursorPos + 1);
|
|
1077
|
+
this.markDirty();
|
|
1078
|
+
}
|
|
1079
|
+
moveCursorHome() {
|
|
1080
|
+
this._cursorPos = 0;
|
|
1081
|
+
this.markDirty();
|
|
1082
|
+
}
|
|
1083
|
+
moveCursorEnd() {
|
|
1084
|
+
this._cursorPos = this._value.length;
|
|
1085
|
+
this.markDirty();
|
|
1086
|
+
}
|
|
1087
|
+
submit() {
|
|
1088
|
+
this._onSubmit?.(this._value);
|
|
1089
|
+
}
|
|
1090
|
+
clear() {
|
|
1091
|
+
this._value = "";
|
|
1092
|
+
this._cursorPos = 0;
|
|
1093
|
+
this._onChange?.("");
|
|
1094
|
+
this.markDirty();
|
|
1095
|
+
}
|
|
1096
|
+
/**
|
|
1097
|
+
* Handle key events. Call this from your input loop.
|
|
1098
|
+
* Alt+V — toggle visibility
|
|
1099
|
+
* Other — standard text editing
|
|
1100
|
+
*/
|
|
1101
|
+
handleKey(event) {
|
|
1102
|
+
if (event.alt && event.key === "v") {
|
|
1103
|
+
this.toggleVisibility();
|
|
1104
|
+
return;
|
|
1105
|
+
}
|
|
1106
|
+
switch (event.key) {
|
|
1107
|
+
case "backspace":
|
|
1108
|
+
this.deleteBack();
|
|
1109
|
+
break;
|
|
1110
|
+
case "delete":
|
|
1111
|
+
this.deleteForward();
|
|
1112
|
+
break;
|
|
1113
|
+
case "left":
|
|
1114
|
+
this.moveCursorLeft();
|
|
1115
|
+
break;
|
|
1116
|
+
case "right":
|
|
1117
|
+
this.moveCursorRight();
|
|
1118
|
+
break;
|
|
1119
|
+
case "home":
|
|
1120
|
+
this.moveCursorHome();
|
|
1121
|
+
break;
|
|
1122
|
+
case "end":
|
|
1123
|
+
this.moveCursorEnd();
|
|
1124
|
+
break;
|
|
1125
|
+
case "return":
|
|
1126
|
+
case "enter":
|
|
1127
|
+
this.submit();
|
|
1128
|
+
break;
|
|
1129
|
+
default:
|
|
1130
|
+
if (event.key && event.key.length === 1 && !event.ctrl && !event.alt) {
|
|
1131
|
+
this.insertChar(event.key);
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
_renderSelf(screen) {
|
|
1136
|
+
const rect = this._getContentRect();
|
|
1137
|
+
const { x, y, width, height } = rect;
|
|
1138
|
+
if (width <= 0 || height <= 0) return;
|
|
1139
|
+
const attrs = styleToCellAttrs11(this._style);
|
|
1140
|
+
if (this._value.length === 0 && !this.isFocused) {
|
|
1141
|
+
screen.writeString(x, y, truncate(this._placeholder, width), { ...attrs, dim: true });
|
|
1142
|
+
return;
|
|
1143
|
+
}
|
|
1144
|
+
const displayValue = this._showText ? this._value : this._maskChar.repeat(this._value.length);
|
|
1145
|
+
const visibleWidth = width - 1;
|
|
1146
|
+
let scrollX = 0;
|
|
1147
|
+
if (this._cursorPos > visibleWidth) {
|
|
1148
|
+
scrollX = this._cursorPos - visibleWidth;
|
|
1149
|
+
}
|
|
1150
|
+
const visibleText = displayValue.slice(scrollX, scrollX + visibleWidth);
|
|
1151
|
+
screen.writeString(x, y, visibleText, attrs);
|
|
1152
|
+
if (this.isFocused) {
|
|
1153
|
+
const cursorScreenPos = x + this._cursorPos - scrollX;
|
|
1154
|
+
if (cursorScreenPos >= x && cursorScreenPos < x + width) {
|
|
1155
|
+
const cursorChar = this._cursorPos < displayValue.length ? displayValue[this._cursorPos] : " ";
|
|
1156
|
+
screen.setCell(cursorScreenPos, y, {
|
|
1157
|
+
char: cursorChar,
|
|
1158
|
+
...attrs,
|
|
1159
|
+
inverse: true
|
|
1160
|
+
});
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
if (this._showText && width > 4) {
|
|
1164
|
+
const indicator = caps9.unicode ? " \u{1F441}" : "[v]";
|
|
1165
|
+
screen.writeString(x + width - indicator.length, y, indicator, { ...attrs, dim: true });
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
};
|
|
1169
|
+
|
|
1170
|
+
// src/NumberInput.ts
|
|
1171
|
+
import { Widget as Widget14 } from "@termuijs/widgets";
|
|
1172
|
+
import { styleToCellAttrs as styleToCellAttrs12, truncate as truncate2 } from "@termuijs/core";
|
|
1173
|
+
var NumberInput = class extends Widget14 {
|
|
1174
|
+
_raw = "";
|
|
1175
|
+
// raw string the user typed
|
|
1176
|
+
_cursorPos = 0;
|
|
1177
|
+
_placeholder;
|
|
1178
|
+
_step;
|
|
1179
|
+
_min;
|
|
1180
|
+
_max;
|
|
1181
|
+
_allowDecimal;
|
|
1182
|
+
_onChange;
|
|
1183
|
+
_onSubmit;
|
|
1184
|
+
focusable = true;
|
|
1185
|
+
constructor(style = {}, options = {}) {
|
|
1186
|
+
super({ border: "single", height: 3, ...style });
|
|
1187
|
+
this._placeholder = options.placeholder ?? "";
|
|
1188
|
+
this._step = options.step ?? 1;
|
|
1189
|
+
this._min = options.min ?? -Infinity;
|
|
1190
|
+
this._max = options.max ?? Infinity;
|
|
1191
|
+
this._allowDecimal = options.allowDecimal ?? true;
|
|
1192
|
+
this._onChange = options.onChange;
|
|
1193
|
+
this._onSubmit = options.onSubmit;
|
|
1194
|
+
}
|
|
1195
|
+
/** The numeric value, or null if the field is empty / invalid. */
|
|
1196
|
+
get numericValue() {
|
|
1197
|
+
if (this._raw === "" || this._raw === "-") return null;
|
|
1198
|
+
const n = parseFloat(this._raw);
|
|
1199
|
+
return isNaN(n) ? null : n;
|
|
1200
|
+
}
|
|
1201
|
+
/** Raw text string (what the user typed). */
|
|
1202
|
+
get rawValue() {
|
|
1203
|
+
return this._raw;
|
|
1204
|
+
}
|
|
1205
|
+
set rawValue(v) {
|
|
1206
|
+
this._raw = v;
|
|
1207
|
+
this._cursorPos = Math.min(this._cursorPos, this._raw.length);
|
|
1208
|
+
}
|
|
1209
|
+
_clamp(n) {
|
|
1210
|
+
return Math.min(this._max, Math.max(this._min, n));
|
|
1211
|
+
}
|
|
1212
|
+
_notify() {
|
|
1213
|
+
this._onChange?.(this.numericValue);
|
|
1214
|
+
this.markDirty();
|
|
1215
|
+
}
|
|
1216
|
+
/** Accept only digits, '-' at position 0 (if min < 0), and (optionally) one '.'. */
|
|
1217
|
+
_isAllowed(char) {
|
|
1218
|
+
if (char === "-" && this._cursorPos === 0 && !this._raw.includes("-")) {
|
|
1219
|
+
return this._min < 0;
|
|
1220
|
+
}
|
|
1221
|
+
if (char === "." && this._allowDecimal && !this._raw.includes(".")) return true;
|
|
1222
|
+
return /^\d$/.test(char);
|
|
1223
|
+
}
|
|
1224
|
+
insertChar(char) {
|
|
1225
|
+
if (!this._isAllowed(char)) return;
|
|
1226
|
+
this._raw = this._raw.slice(0, this._cursorPos) + char + this._raw.slice(this._cursorPos);
|
|
1227
|
+
this._cursorPos++;
|
|
1228
|
+
this._notify();
|
|
1229
|
+
}
|
|
1230
|
+
deleteBack() {
|
|
1231
|
+
if (this._cursorPos > 0) {
|
|
1232
|
+
this._raw = this._raw.slice(0, this._cursorPos - 1) + this._raw.slice(this._cursorPos);
|
|
1233
|
+
this._cursorPos--;
|
|
1234
|
+
this._notify();
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
1237
|
+
deleteForward() {
|
|
1238
|
+
if (this._cursorPos < this._raw.length) {
|
|
1239
|
+
this._raw = this._raw.slice(0, this._cursorPos) + this._raw.slice(this._cursorPos + 1);
|
|
1240
|
+
this._notify();
|
|
1241
|
+
}
|
|
1242
|
+
}
|
|
1243
|
+
moveCursorLeft() {
|
|
1244
|
+
this._cursorPos = Math.max(0, this._cursorPos - 1);
|
|
1245
|
+
this.markDirty();
|
|
1246
|
+
}
|
|
1247
|
+
moveCursorRight() {
|
|
1248
|
+
this._cursorPos = Math.min(this._raw.length, this._cursorPos + 1);
|
|
1249
|
+
this.markDirty();
|
|
1250
|
+
}
|
|
1251
|
+
moveCursorHome() {
|
|
1252
|
+
this._cursorPos = 0;
|
|
1253
|
+
this.markDirty();
|
|
1254
|
+
}
|
|
1255
|
+
moveCursorEnd() {
|
|
1256
|
+
this._cursorPos = this._raw.length;
|
|
1257
|
+
this.markDirty();
|
|
1258
|
+
}
|
|
1259
|
+
/** Increment value by step (↑ arrow). */
|
|
1260
|
+
increment() {
|
|
1261
|
+
const current = this.numericValue ?? 0;
|
|
1262
|
+
const next = this._clamp(current + this._step);
|
|
1263
|
+
this._raw = String(next);
|
|
1264
|
+
this._cursorPos = this._raw.length;
|
|
1265
|
+
this._notify();
|
|
1266
|
+
}
|
|
1267
|
+
/** Decrement value by step (↓ arrow). */
|
|
1268
|
+
decrement() {
|
|
1269
|
+
const current = this.numericValue ?? 0;
|
|
1270
|
+
const next = this._clamp(current - this._step);
|
|
1271
|
+
this._raw = String(next);
|
|
1272
|
+
this._cursorPos = this._raw.length;
|
|
1273
|
+
this._notify();
|
|
1274
|
+
}
|
|
1275
|
+
submit() {
|
|
1276
|
+
this._onSubmit?.(this.numericValue);
|
|
1277
|
+
}
|
|
1278
|
+
clear() {
|
|
1279
|
+
this._raw = "";
|
|
1280
|
+
this._cursorPos = 0;
|
|
1281
|
+
this._notify();
|
|
1282
|
+
}
|
|
1283
|
+
/**
|
|
1284
|
+
* Handle key events. Call this from your input loop.
|
|
1285
|
+
*/
|
|
1286
|
+
handleKey(event) {
|
|
1287
|
+
switch (event.key) {
|
|
1288
|
+
case "up":
|
|
1289
|
+
this.increment();
|
|
1290
|
+
break;
|
|
1291
|
+
case "down":
|
|
1292
|
+
this.decrement();
|
|
1293
|
+
break;
|
|
1294
|
+
case "backspace":
|
|
1295
|
+
this.deleteBack();
|
|
1296
|
+
break;
|
|
1297
|
+
case "delete":
|
|
1298
|
+
this.deleteForward();
|
|
1299
|
+
break;
|
|
1300
|
+
case "left":
|
|
1301
|
+
this.moveCursorLeft();
|
|
1302
|
+
break;
|
|
1303
|
+
case "right":
|
|
1304
|
+
this.moveCursorRight();
|
|
1305
|
+
break;
|
|
1306
|
+
case "home":
|
|
1307
|
+
this.moveCursorHome();
|
|
1308
|
+
break;
|
|
1309
|
+
case "end":
|
|
1310
|
+
this.moveCursorEnd();
|
|
1311
|
+
break;
|
|
1312
|
+
case "return":
|
|
1313
|
+
case "enter":
|
|
1314
|
+
this.submit();
|
|
1315
|
+
break;
|
|
1316
|
+
default:
|
|
1317
|
+
if (event.key && event.key.length === 1 && !event.ctrl && !event.alt) {
|
|
1318
|
+
this.insertChar(event.key);
|
|
1319
|
+
}
|
|
1320
|
+
}
|
|
1321
|
+
}
|
|
1322
|
+
_renderSelf(screen) {
|
|
1323
|
+
const rect = this._getContentRect();
|
|
1324
|
+
const { x, y, width, height } = rect;
|
|
1325
|
+
if (width <= 0 || height <= 0) return;
|
|
1326
|
+
const attrs = styleToCellAttrs12(this._style);
|
|
1327
|
+
if (this._raw.length === 0 && !this.isFocused) {
|
|
1328
|
+
screen.writeString(x, y, truncate2(this._placeholder, width), { ...attrs, dim: true });
|
|
1329
|
+
return;
|
|
1330
|
+
}
|
|
1331
|
+
const display = this._raw;
|
|
1332
|
+
const visibleWidth = width - 1;
|
|
1333
|
+
let scrollX = 0;
|
|
1334
|
+
if (this._cursorPos > visibleWidth) {
|
|
1335
|
+
scrollX = this._cursorPos - visibleWidth;
|
|
1336
|
+
}
|
|
1337
|
+
const visibleText = display.slice(scrollX, scrollX + visibleWidth);
|
|
1338
|
+
screen.writeString(x, y, visibleText, attrs);
|
|
1339
|
+
if (this.isFocused) {
|
|
1340
|
+
const cursorScreenPos = x + this._cursorPos - scrollX;
|
|
1341
|
+
if (cursorScreenPos >= x && cursorScreenPos < x + width) {
|
|
1342
|
+
const cursorChar = this._cursorPos < display.length ? display[this._cursorPos] : " ";
|
|
1343
|
+
screen.setCell(cursorScreenPos, y, {
|
|
1344
|
+
char: cursorChar,
|
|
1345
|
+
...attrs,
|
|
1346
|
+
inverse: true
|
|
1347
|
+
});
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
if (this.isFocused && width > 8) {
|
|
1351
|
+
const hint = `\xB1${this._step}`;
|
|
1352
|
+
screen.writeString(x + width - hint.length, y, hint, { ...attrs, dim: true });
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
1355
|
+
};
|
|
1356
|
+
|
|
1357
|
+
// src/PathInput.ts
|
|
1358
|
+
import * as fs from "fs";
|
|
1359
|
+
import * as path from "path";
|
|
1360
|
+
import { Widget as Widget15 } from "@termuijs/widgets";
|
|
1361
|
+
import { styleToCellAttrs as styleToCellAttrs13, truncate as truncate3, caps as caps10 } from "@termuijs/core";
|
|
1362
|
+
var PathInput = class extends Widget15 {
|
|
1363
|
+
_value = "";
|
|
1364
|
+
_cursorPos = 0;
|
|
1365
|
+
_placeholder;
|
|
1366
|
+
_maxLength;
|
|
1367
|
+
_cwd;
|
|
1368
|
+
_maxCompletions;
|
|
1369
|
+
_completions = [];
|
|
1370
|
+
_completionIndex = -1;
|
|
1371
|
+
// -1 = original value, 0..n = cycling
|
|
1372
|
+
_preCompletionValue = "";
|
|
1373
|
+
// value before Tab was pressed
|
|
1374
|
+
_showCompletions = false;
|
|
1375
|
+
_onChange;
|
|
1376
|
+
_onSubmit;
|
|
1377
|
+
focusable = true;
|
|
1378
|
+
constructor(style = {}, options = {}) {
|
|
1379
|
+
super({ border: "single", height: 3, ...style });
|
|
1380
|
+
this._placeholder = options.placeholder ?? "";
|
|
1381
|
+
this._maxLength = options.maxLength ?? 4096;
|
|
1382
|
+
this._cwd = options.cwd ?? process.cwd();
|
|
1383
|
+
this._maxCompletions = options.maxCompletions ?? 5;
|
|
1384
|
+
this._onChange = options.onChange;
|
|
1385
|
+
this._onSubmit = options.onSubmit;
|
|
1386
|
+
}
|
|
1387
|
+
get value() {
|
|
1388
|
+
return this._value;
|
|
1389
|
+
}
|
|
1390
|
+
set value(v) {
|
|
1391
|
+
this._value = v.slice(0, this._maxLength);
|
|
1392
|
+
this._cursorPos = Math.min(this._cursorPos, this._value.length);
|
|
1393
|
+
this._dismissCompletions();
|
|
1394
|
+
}
|
|
1395
|
+
get completions() {
|
|
1396
|
+
return this._completions;
|
|
1397
|
+
}
|
|
1398
|
+
get isShowingCompletions() {
|
|
1399
|
+
return this._showCompletions && this._completions.length > 0;
|
|
1400
|
+
}
|
|
1401
|
+
_dismissCompletions() {
|
|
1402
|
+
this._completions = [];
|
|
1403
|
+
this._completionIndex = -1;
|
|
1404
|
+
this._showCompletions = false;
|
|
1405
|
+
}
|
|
1406
|
+
/** Compute completions for the current value. Does NOT throw. */
|
|
1407
|
+
_computeCompletions() {
|
|
1408
|
+
try {
|
|
1409
|
+
const raw = this._value;
|
|
1410
|
+
const isAbs = path.isAbsolute(raw);
|
|
1411
|
+
const base = isAbs ? raw : path.join(this._cwd, raw);
|
|
1412
|
+
let dir;
|
|
1413
|
+
let prefix;
|
|
1414
|
+
if (raw === "" || raw.endsWith("/") || raw.endsWith(path.sep)) {
|
|
1415
|
+
dir = isAbs ? raw || "/" : path.join(this._cwd, raw || ".");
|
|
1416
|
+
prefix = "";
|
|
1417
|
+
} else {
|
|
1418
|
+
dir = path.dirname(base);
|
|
1419
|
+
prefix = path.basename(base).toLowerCase();
|
|
1420
|
+
}
|
|
1421
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
1422
|
+
const matches = entries.filter((e) => e.name.toLowerCase().startsWith(prefix)).slice(0, this._maxCompletions).map((e) => {
|
|
1423
|
+
const full = path.join(dir, e.name);
|
|
1424
|
+
const candidate = isAbs ? full + (e.isDirectory() ? path.sep : "") : path.relative(this._cwd, full) + (e.isDirectory() ? path.sep : "");
|
|
1425
|
+
return candidate;
|
|
1426
|
+
});
|
|
1427
|
+
return matches;
|
|
1428
|
+
} catch {
|
|
1429
|
+
return [];
|
|
1430
|
+
}
|
|
1431
|
+
}
|
|
1432
|
+
/** Trigger tab-completion. */
|
|
1433
|
+
triggerCompletion() {
|
|
1434
|
+
if (!this._showCompletions || this._completions.length === 0) {
|
|
1435
|
+
this._preCompletionValue = this._value;
|
|
1436
|
+
this._completions = this._computeCompletions();
|
|
1437
|
+
if (this._completions.length === 0) {
|
|
1438
|
+
this._showCompletions = false;
|
|
1439
|
+
return;
|
|
1440
|
+
}
|
|
1441
|
+
this._showCompletions = true;
|
|
1442
|
+
this._completionIndex = 0;
|
|
1443
|
+
} else {
|
|
1444
|
+
this._completionIndex = (this._completionIndex + 1) % this._completions.length;
|
|
1445
|
+
}
|
|
1446
|
+
this._value = this._completions[this._completionIndex];
|
|
1447
|
+
this._cursorPos = this._value.length;
|
|
1448
|
+
this._onChange?.(this._value);
|
|
1449
|
+
this.markDirty();
|
|
1450
|
+
}
|
|
1451
|
+
insertChar(char) {
|
|
1452
|
+
if (this._value.length >= this._maxLength) return;
|
|
1453
|
+
this._value = this._value.slice(0, this._cursorPos) + char + this._value.slice(this._cursorPos);
|
|
1454
|
+
this._cursorPos++;
|
|
1455
|
+
this._dismissCompletions();
|
|
1456
|
+
this._onChange?.(this._value);
|
|
1457
|
+
this.markDirty();
|
|
1458
|
+
}
|
|
1459
|
+
deleteBack() {
|
|
1460
|
+
if (this._cursorPos > 0) {
|
|
1461
|
+
this._value = this._value.slice(0, this._cursorPos - 1) + this._value.slice(this._cursorPos);
|
|
1462
|
+
this._cursorPos--;
|
|
1463
|
+
this._dismissCompletions();
|
|
1464
|
+
this._onChange?.(this._value);
|
|
1465
|
+
this.markDirty();
|
|
1466
|
+
}
|
|
1467
|
+
}
|
|
1468
|
+
deleteForward() {
|
|
1469
|
+
if (this._cursorPos < this._value.length) {
|
|
1470
|
+
this._value = this._value.slice(0, this._cursorPos) + this._value.slice(this._cursorPos + 1);
|
|
1471
|
+
this._dismissCompletions();
|
|
1472
|
+
this._onChange?.(this._value);
|
|
1473
|
+
this.markDirty();
|
|
1474
|
+
}
|
|
1475
|
+
}
|
|
1476
|
+
moveCursorLeft() {
|
|
1477
|
+
this._cursorPos = Math.max(0, this._cursorPos - 1);
|
|
1478
|
+
this.markDirty();
|
|
1479
|
+
}
|
|
1480
|
+
moveCursorRight() {
|
|
1481
|
+
this._cursorPos = Math.min(this._value.length, this._cursorPos + 1);
|
|
1482
|
+
this.markDirty();
|
|
1483
|
+
}
|
|
1484
|
+
moveCursorHome() {
|
|
1485
|
+
this._cursorPos = 0;
|
|
1486
|
+
this.markDirty();
|
|
1487
|
+
}
|
|
1488
|
+
moveCursorEnd() {
|
|
1489
|
+
this._cursorPos = this._value.length;
|
|
1490
|
+
this.markDirty();
|
|
1491
|
+
}
|
|
1492
|
+
submit() {
|
|
1493
|
+
this._dismissCompletions();
|
|
1494
|
+
this._onSubmit?.(this._value);
|
|
1495
|
+
}
|
|
1496
|
+
clear() {
|
|
1497
|
+
this._value = "";
|
|
1498
|
+
this._cursorPos = 0;
|
|
1499
|
+
this._dismissCompletions();
|
|
1500
|
+
this._onChange?.("");
|
|
1501
|
+
this.markDirty();
|
|
1502
|
+
}
|
|
1503
|
+
/**
|
|
1504
|
+
* Handle key events. Call this from your input loop.
|
|
1505
|
+
* Tab — trigger/cycle completions
|
|
1506
|
+
* Esc — dismiss completions
|
|
1507
|
+
* Enter — submit current value
|
|
1508
|
+
*/
|
|
1509
|
+
handleKey(event) {
|
|
1510
|
+
switch (event.key) {
|
|
1511
|
+
case "tab":
|
|
1512
|
+
this.triggerCompletion();
|
|
1513
|
+
break;
|
|
1514
|
+
case "escape":
|
|
1515
|
+
this._dismissCompletions();
|
|
1516
|
+
this.markDirty();
|
|
1517
|
+
break;
|
|
1518
|
+
case "backspace":
|
|
1519
|
+
this.deleteBack();
|
|
1520
|
+
break;
|
|
1521
|
+
case "delete":
|
|
1522
|
+
this.deleteForward();
|
|
1523
|
+
break;
|
|
1524
|
+
case "left":
|
|
1525
|
+
this.moveCursorLeft();
|
|
1526
|
+
break;
|
|
1527
|
+
case "right":
|
|
1528
|
+
this.moveCursorRight();
|
|
1529
|
+
break;
|
|
1530
|
+
case "home":
|
|
1531
|
+
this.moveCursorHome();
|
|
1532
|
+
break;
|
|
1533
|
+
case "end":
|
|
1534
|
+
this.moveCursorEnd();
|
|
1535
|
+
break;
|
|
1536
|
+
case "return":
|
|
1537
|
+
case "enter":
|
|
1538
|
+
this.submit();
|
|
1539
|
+
break;
|
|
1540
|
+
default:
|
|
1541
|
+
if (event.key && event.key.length === 1 && !event.ctrl && !event.alt) {
|
|
1542
|
+
this.insertChar(event.key);
|
|
1543
|
+
}
|
|
1544
|
+
}
|
|
1545
|
+
}
|
|
1546
|
+
_renderSelf(screen) {
|
|
1547
|
+
const rect = this._getContentRect();
|
|
1548
|
+
const { x, y, width, height } = rect;
|
|
1549
|
+
if (width <= 0 || height <= 0) return;
|
|
1550
|
+
const attrs = styleToCellAttrs13(this._style);
|
|
1551
|
+
if (height <= 1 && this._showCompletions) {
|
|
1552
|
+
this._showCompletions = false;
|
|
1553
|
+
}
|
|
1554
|
+
if (this._value.length === 0 && !this.isFocused) {
|
|
1555
|
+
screen.writeString(x, y, truncate3(this._placeholder, width), { ...attrs, dim: true });
|
|
1556
|
+
} else {
|
|
1557
|
+
const visibleWidth = width - 1;
|
|
1558
|
+
let scrollX = 0;
|
|
1559
|
+
if (this._cursorPos > visibleWidth) {
|
|
1560
|
+
scrollX = this._cursorPos - visibleWidth;
|
|
1561
|
+
}
|
|
1562
|
+
const visibleText = this._value.slice(scrollX, scrollX + visibleWidth);
|
|
1563
|
+
screen.writeString(x, y, visibleText, attrs);
|
|
1564
|
+
if (this.isFocused) {
|
|
1565
|
+
const cursorScreenPos = x + this._cursorPos - scrollX;
|
|
1566
|
+
if (cursorScreenPos >= x && cursorScreenPos < x + width) {
|
|
1567
|
+
const cursorChar = this._cursorPos < this._value.length ? this._value[this._cursorPos] : " ";
|
|
1568
|
+
screen.setCell(cursorScreenPos, y, {
|
|
1569
|
+
char: cursorChar,
|
|
1570
|
+
...attrs,
|
|
1571
|
+
inverse: true
|
|
1572
|
+
});
|
|
1573
|
+
}
|
|
1574
|
+
}
|
|
1575
|
+
}
|
|
1576
|
+
if (this._showCompletions && this._completions.length > 0 && height > 1) {
|
|
1577
|
+
const maxRows = Math.min(this._completions.length, height - 1);
|
|
1578
|
+
for (let i = 0; i < maxRows; i++) {
|
|
1579
|
+
const row = y + 1 + i;
|
|
1580
|
+
const isSelected = i === this._completionIndex;
|
|
1581
|
+
const prefix = isSelected ? caps10.unicode ? "\u25B6 " : "> " : " ";
|
|
1582
|
+
const entry = this._completions[i];
|
|
1583
|
+
screen.writeString(x, row, truncate3(prefix + entry, width), {
|
|
1584
|
+
...attrs,
|
|
1585
|
+
bold: isSelected,
|
|
1586
|
+
inverse: isSelected
|
|
1587
|
+
});
|
|
1588
|
+
}
|
|
1589
|
+
}
|
|
1590
|
+
}
|
|
1591
|
+
};
|
|
1592
|
+
|
|
1593
|
+
// src/KeyboardShortcuts.ts
|
|
1594
|
+
import { Widget as Widget16 } from "@termuijs/widgets";
|
|
1595
|
+
import { styleToCellAttrs as styleToCellAttrs14, caps as caps11 } from "@termuijs/core";
|
|
1596
|
+
var KeyboardShortcuts = class extends Widget16 {
|
|
1597
|
+
_bindings;
|
|
1598
|
+
_keyColor;
|
|
1599
|
+
_categoryColor;
|
|
1600
|
+
_columns;
|
|
1601
|
+
_showCategories;
|
|
1602
|
+
constructor(bindings, options = {}) {
|
|
1603
|
+
super({});
|
|
1604
|
+
this._bindings = bindings;
|
|
1605
|
+
this._keyColor = options.keyColor ?? { type: "named", name: "cyan" };
|
|
1606
|
+
this._categoryColor = options.categoryColor ?? { type: "named", name: "yellow" };
|
|
1607
|
+
this._columns = Math.max(1, options.columns ?? 2);
|
|
1608
|
+
this._showCategories = options.showCategories ?? true;
|
|
1609
|
+
}
|
|
1610
|
+
/** Replace all bindings and trigger a re-render. */
|
|
1611
|
+
setBindings(bindings) {
|
|
1612
|
+
this._bindings = bindings;
|
|
1613
|
+
this.markDirty();
|
|
1614
|
+
}
|
|
1615
|
+
/** Group bindings by category, preserving insertion order. */
|
|
1616
|
+
_groupBindings() {
|
|
1617
|
+
const groups = [];
|
|
1618
|
+
const indexMap = /* @__PURE__ */ new Map();
|
|
1619
|
+
for (const b of this._bindings) {
|
|
1620
|
+
const cat = b.category ?? "";
|
|
1621
|
+
const key = cat;
|
|
1622
|
+
if (!indexMap.has(key)) {
|
|
1623
|
+
indexMap.set(key, groups.length);
|
|
1624
|
+
groups.push({ category: b.category, bindings: [] });
|
|
1625
|
+
}
|
|
1626
|
+
groups[indexMap.get(key)].bindings.push(b);
|
|
1627
|
+
}
|
|
1628
|
+
return groups;
|
|
1629
|
+
}
|
|
1630
|
+
/**
|
|
1631
|
+
* Render a key label in a bordered box style:
|
|
1632
|
+
* ┌─────┐
|
|
1633
|
+
* │ key │
|
|
1634
|
+
* └─────┘
|
|
1635
|
+
*
|
|
1636
|
+
* We fit it on one line: [ key ] — box drawing characters or ASCII fallback.
|
|
1637
|
+
*/
|
|
1638
|
+
_renderKeyLabel(screen, kx, ky, key, attrs) {
|
|
1639
|
+
if (caps11.unicode) {
|
|
1640
|
+
const label = `[${key}]`;
|
|
1641
|
+
screen.writeString(kx, ky, label, { ...attrs, fg: this._keyColor, bold: true });
|
|
1642
|
+
return label.length;
|
|
1643
|
+
} else {
|
|
1644
|
+
const label = `[${key}]`;
|
|
1645
|
+
screen.writeString(kx, ky, label, { ...attrs, fg: this._keyColor, bold: true });
|
|
1646
|
+
return label.length;
|
|
1647
|
+
}
|
|
1648
|
+
}
|
|
1649
|
+
_renderSelf(screen) {
|
|
1650
|
+
const { x, y, width, height } = this._rect;
|
|
1651
|
+
if (width <= 0 || height <= 0) return;
|
|
1652
|
+
const attrs = styleToCellAttrs14(this._style);
|
|
1653
|
+
const groups = this._groupBindings();
|
|
1654
|
+
let row = y;
|
|
1655
|
+
const colWidth = Math.floor(width / this._columns);
|
|
1656
|
+
for (const group of groups) {
|
|
1657
|
+
if (row >= y + height) break;
|
|
1658
|
+
if (this._showCategories && group.category) {
|
|
1659
|
+
const heading = group.category.toUpperCase();
|
|
1660
|
+
const divider = caps11.unicode ? "\u2500" : "-";
|
|
1661
|
+
const line = heading + " " + divider.repeat(Math.max(0, width - heading.length - 1));
|
|
1662
|
+
screen.writeString(x, row, line.slice(0, width), {
|
|
1663
|
+
...attrs,
|
|
1664
|
+
fg: this._categoryColor,
|
|
1665
|
+
bold: true
|
|
1666
|
+
});
|
|
1667
|
+
row++;
|
|
1668
|
+
if (row >= y + height) break;
|
|
1669
|
+
}
|
|
1670
|
+
for (let i = 0; i < group.bindings.length; i += this._columns) {
|
|
1671
|
+
if (row >= y + height) break;
|
|
1672
|
+
for (let col = 0; col < this._columns; col++) {
|
|
1673
|
+
const binding = group.bindings[i + col];
|
|
1674
|
+
if (!binding) continue;
|
|
1675
|
+
const cx = x + col * colWidth;
|
|
1676
|
+
const availWidth = colWidth - 1;
|
|
1677
|
+
if (availWidth <= 0) continue;
|
|
1678
|
+
const labelLen = this._renderKeyLabel(screen, cx, row, binding.key, attrs);
|
|
1679
|
+
const descX = cx + labelLen + 1;
|
|
1680
|
+
const descWidth = availWidth - labelLen - 1;
|
|
1681
|
+
if (descWidth > 0) {
|
|
1682
|
+
const desc = binding.description.slice(0, descWidth);
|
|
1683
|
+
screen.writeString(descX, row, desc, attrs);
|
|
1684
|
+
}
|
|
1685
|
+
}
|
|
1686
|
+
row++;
|
|
1687
|
+
}
|
|
1688
|
+
if (this._showCategories && group.category) {
|
|
1689
|
+
row++;
|
|
1690
|
+
}
|
|
1691
|
+
}
|
|
1692
|
+
}
|
|
1693
|
+
};
|
|
778
1694
|
export {
|
|
779
1695
|
Box,
|
|
780
1696
|
CommandPalette,
|
|
@@ -782,10 +1698,17 @@ export {
|
|
|
782
1698
|
Divider,
|
|
783
1699
|
Form,
|
|
784
1700
|
Gauge,
|
|
1701
|
+
KeyboardShortcuts,
|
|
785
1702
|
List,
|
|
786
1703
|
LogView,
|
|
787
1704
|
Modal,
|
|
788
1705
|
MultiSelect,
|
|
1706
|
+
NonInteractiveError,
|
|
1707
|
+
NotificationCenter,
|
|
1708
|
+
NotificationStore,
|
|
1709
|
+
NumberInput,
|
|
1710
|
+
PasswordInput,
|
|
1711
|
+
PathInput,
|
|
789
1712
|
ProgressBar,
|
|
790
1713
|
Select,
|
|
791
1714
|
Spacer,
|
|
@@ -798,6 +1721,9 @@ export {
|
|
|
798
1721
|
TextInput,
|
|
799
1722
|
Toast,
|
|
800
1723
|
Tree,
|
|
801
|
-
|
|
1724
|
+
Widget17 as Widget,
|
|
1725
|
+
notifications,
|
|
1726
|
+
prompt,
|
|
1727
|
+
useNotifications
|
|
802
1728
|
};
|
|
803
1729
|
//# sourceMappingURL=index.js.map
|