mepcli 0.2.1 → 0.2.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 +61 -17
- package/dist/ansi.d.ts +2 -0
- package/dist/ansi.js +2 -0
- package/dist/base.d.ts +44 -0
- package/dist/base.js +87 -0
- package/dist/core.d.ts +6 -1
- package/dist/core.js +35 -775
- package/dist/prompts/checkbox.d.ts +10 -0
- package/dist/prompts/checkbox.js +92 -0
- package/dist/prompts/confirm.d.ts +7 -0
- package/dist/prompts/confirm.js +40 -0
- package/dist/prompts/date.d.ts +10 -0
- package/dist/prompts/date.js +157 -0
- package/dist/prompts/file.d.ts +13 -0
- package/dist/prompts/file.js +194 -0
- package/dist/prompts/list.d.ts +9 -0
- package/dist/prompts/list.js +83 -0
- package/dist/prompts/multi-select.d.ts +14 -0
- package/dist/prompts/multi-select.js +149 -0
- package/dist/prompts/number.d.ts +10 -0
- package/dist/prompts/number.js +137 -0
- package/dist/prompts/select.d.ts +16 -0
- package/dist/prompts/select.js +162 -0
- package/dist/prompts/slider.d.ts +7 -0
- package/dist/prompts/slider.js +48 -0
- package/dist/prompts/text.d.ts +12 -0
- package/dist/prompts/text.js +245 -0
- package/dist/prompts/toggle.d.ts +7 -0
- package/dist/prompts/toggle.js +45 -0
- package/dist/theme.d.ts +2 -0
- package/dist/theme.js +11 -0
- package/dist/types.d.ts +24 -0
- package/example.ts +57 -5
- package/package.json +1 -1
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MultiSelectPrompt = void 0;
|
|
4
|
+
const ansi_1 = require("../ansi");
|
|
5
|
+
const base_1 = require("../base");
|
|
6
|
+
const theme_1 = require("../theme");
|
|
7
|
+
// --- Implementation: MultiSelect Prompt ---
|
|
8
|
+
class MultiSelectPrompt extends base_1.Prompt {
|
|
9
|
+
constructor(options) {
|
|
10
|
+
super(options);
|
|
11
|
+
this.selectedIndex = 0;
|
|
12
|
+
this.searchBuffer = '';
|
|
13
|
+
this.scrollTop = 0;
|
|
14
|
+
this.pageSize = 7;
|
|
15
|
+
this.errorMsg = '';
|
|
16
|
+
this.checkedState = options.choices.map(c => !!c.selected);
|
|
17
|
+
this.selectedIndex = 0;
|
|
18
|
+
}
|
|
19
|
+
getFilteredChoices() {
|
|
20
|
+
if (!this.searchBuffer)
|
|
21
|
+
return this.options.choices.map((c, i) => ({ ...c, originalIndex: i }));
|
|
22
|
+
return this.options.choices
|
|
23
|
+
.map((c, i) => ({ ...c, originalIndex: i }))
|
|
24
|
+
.filter(c => c.title.toLowerCase().includes(this.searchBuffer.toLowerCase()));
|
|
25
|
+
}
|
|
26
|
+
render(firstRender) {
|
|
27
|
+
this.print(ansi_1.ANSI.HIDE_CURSOR);
|
|
28
|
+
// This is tricky because height changes with filter.
|
|
29
|
+
// Simplified clearing:
|
|
30
|
+
if (!firstRender) {
|
|
31
|
+
this.print(ansi_1.ANSI.ERASE_DOWN); // Clear everything below cursor
|
|
32
|
+
// But we need to move cursor up to start of prompt
|
|
33
|
+
// We can store last height?
|
|
34
|
+
}
|
|
35
|
+
// Wait, standard render loop usually assumes fixed position or we manually handle it.
|
|
36
|
+
// Let's use ERASE_LINE + UP loop like SelectPrompt but simpler since we have full screen control in a way
|
|
37
|
+
// Actually, let's just clear screen? No, that's bad.
|
|
38
|
+
// Let's stick to SelectPrompt's strategy.
|
|
39
|
+
if (!firstRender) {
|
|
40
|
+
// Hack: Just clear last 10 lines to be safe? No.
|
|
41
|
+
// We will implement proper tracking later if needed, for now standard clear
|
|
42
|
+
// Let's re-use SelectPrompt logic structure if possible, but distinct implementation here.
|
|
43
|
+
// Simplest: Always move to top of prompt and erase down.
|
|
44
|
+
// Assuming we track how many lines we printed.
|
|
45
|
+
}
|
|
46
|
+
// ... Implementation detail: use a simpler clear strategy:
|
|
47
|
+
// Move to start of prompt line (we need to track lines printed in previous frame)
|
|
48
|
+
if (this.lastRenderLines) {
|
|
49
|
+
this.print(`\x1b[${this.lastRenderLines}A`);
|
|
50
|
+
this.print(ansi_1.ANSI.ERASE_DOWN);
|
|
51
|
+
}
|
|
52
|
+
let output = '';
|
|
53
|
+
const choices = this.getFilteredChoices();
|
|
54
|
+
// Adjust Scroll
|
|
55
|
+
if (this.selectedIndex < this.scrollTop) {
|
|
56
|
+
this.scrollTop = this.selectedIndex;
|
|
57
|
+
}
|
|
58
|
+
else if (this.selectedIndex >= this.scrollTop + this.pageSize) {
|
|
59
|
+
this.scrollTop = this.selectedIndex - this.pageSize + 1;
|
|
60
|
+
}
|
|
61
|
+
if (this.scrollTop > choices.length - 1) {
|
|
62
|
+
this.scrollTop = Math.max(0, choices.length - this.pageSize);
|
|
63
|
+
}
|
|
64
|
+
const icon = this.errorMsg ? `${theme_1.theme.error}✖` : `${theme_1.theme.success}?`;
|
|
65
|
+
const searchStr = this.searchBuffer ? ` ${theme_1.theme.muted}(Filter: ${this.searchBuffer})${ansi_1.ANSI.RESET}` : '';
|
|
66
|
+
output += `${icon} ${ansi_1.ANSI.BOLD}${theme_1.theme.title}${this.options.message}${ansi_1.ANSI.RESET}${searchStr}\n`;
|
|
67
|
+
if (choices.length === 0) {
|
|
68
|
+
output += ` ${theme_1.theme.muted}No results found${ansi_1.ANSI.RESET}\n`;
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
const visible = choices.slice(this.scrollTop, this.scrollTop + this.pageSize);
|
|
72
|
+
visible.forEach((choice, index) => {
|
|
73
|
+
const actualIndex = this.scrollTop + index;
|
|
74
|
+
const cursor = actualIndex === this.selectedIndex ? `${theme_1.theme.main}❯${ansi_1.ANSI.RESET}` : ' ';
|
|
75
|
+
const isChecked = this.checkedState[choice.originalIndex];
|
|
76
|
+
const checkbox = isChecked
|
|
77
|
+
? `${theme_1.theme.success}◉${ansi_1.ANSI.RESET}`
|
|
78
|
+
: `${theme_1.theme.muted}◯${ansi_1.ANSI.RESET}`;
|
|
79
|
+
output += `${cursor} ${checkbox} ${choice.title}\n`;
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
if (this.errorMsg) {
|
|
83
|
+
output += `${theme_1.theme.error}>> ${this.errorMsg}${ansi_1.ANSI.RESET}\n`;
|
|
84
|
+
}
|
|
85
|
+
this.print(output);
|
|
86
|
+
// Count lines
|
|
87
|
+
const lines = 1 + (choices.length === 0 ? 1 : Math.min(choices.length, this.pageSize)) + (this.errorMsg ? 1 : 0);
|
|
88
|
+
this.lastRenderLines = lines;
|
|
89
|
+
}
|
|
90
|
+
handleInput(char) {
|
|
91
|
+
const choices = this.getFilteredChoices();
|
|
92
|
+
if (char === '\r' || char === '\n') {
|
|
93
|
+
const selectedCount = this.checkedState.filter(Boolean).length;
|
|
94
|
+
const { min = 0, max } = this.options;
|
|
95
|
+
if (selectedCount < min) {
|
|
96
|
+
this.errorMsg = `Select at least ${min}.`;
|
|
97
|
+
this.render(false);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
if (max && selectedCount > max) {
|
|
101
|
+
this.errorMsg = `Max ${max} allowed.`;
|
|
102
|
+
this.render(false);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
const results = this.options.choices
|
|
106
|
+
.filter((_, i) => this.checkedState[i])
|
|
107
|
+
.map(c => c.value);
|
|
108
|
+
this.submit(results);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
if (char === ' ') {
|
|
112
|
+
if (choices.length > 0) {
|
|
113
|
+
const choice = choices[this.selectedIndex];
|
|
114
|
+
const originalIndex = choice.originalIndex;
|
|
115
|
+
this.checkedState[originalIndex] = !this.checkedState[originalIndex];
|
|
116
|
+
this.render(false);
|
|
117
|
+
}
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
if (this.isUp(char)) { // Up
|
|
121
|
+
if (choices.length > 0) {
|
|
122
|
+
this.selectedIndex = (this.selectedIndex - 1 + choices.length) % choices.length;
|
|
123
|
+
this.render(false);
|
|
124
|
+
}
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
if (this.isDown(char)) { // Down
|
|
128
|
+
if (choices.length > 0) {
|
|
129
|
+
this.selectedIndex = (this.selectedIndex + 1) % choices.length;
|
|
130
|
+
this.render(false);
|
|
131
|
+
}
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
if (char === '\u0008' || char === '\x7f') { // Backspace
|
|
135
|
+
if (this.searchBuffer.length > 0) {
|
|
136
|
+
this.searchBuffer = this.searchBuffer.slice(0, -1);
|
|
137
|
+
this.selectedIndex = 0;
|
|
138
|
+
this.render(false);
|
|
139
|
+
}
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
if (!/^[\x00-\x1F]/.test(char) && !char.startsWith('\x1b')) {
|
|
143
|
+
this.searchBuffer += char;
|
|
144
|
+
this.selectedIndex = 0;
|
|
145
|
+
this.render(false);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
exports.MultiSelectPrompt = MultiSelectPrompt;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Prompt } from '../base';
|
|
2
|
+
import { NumberOptions } from '../types';
|
|
3
|
+
export declare class NumberPrompt extends Prompt<number, NumberOptions> {
|
|
4
|
+
private cursor;
|
|
5
|
+
private stringValue;
|
|
6
|
+
private errorMsg;
|
|
7
|
+
constructor(options: NumberOptions);
|
|
8
|
+
protected render(firstRender: boolean): void;
|
|
9
|
+
protected handleInput(char: string): void;
|
|
10
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.NumberPrompt = void 0;
|
|
4
|
+
const ansi_1 = require("../ansi");
|
|
5
|
+
const base_1 = require("../base");
|
|
6
|
+
const theme_1 = require("../theme");
|
|
7
|
+
// --- Implementation: Number Prompt ---
|
|
8
|
+
class NumberPrompt extends base_1.Prompt {
|
|
9
|
+
constructor(options) {
|
|
10
|
+
super(options);
|
|
11
|
+
this.cursor = 0;
|
|
12
|
+
this.errorMsg = '';
|
|
13
|
+
this.value = options.initial ?? 0;
|
|
14
|
+
this.stringValue = this.value.toString();
|
|
15
|
+
this.cursor = this.stringValue.length;
|
|
16
|
+
}
|
|
17
|
+
render(firstRender) {
|
|
18
|
+
this.print(ansi_1.ANSI.SHOW_CURSOR);
|
|
19
|
+
if (!firstRender) {
|
|
20
|
+
this.print(ansi_1.ANSI.ERASE_LINE + ansi_1.ANSI.CURSOR_LEFT);
|
|
21
|
+
if (this.errorMsg) {
|
|
22
|
+
this.print(ansi_1.ANSI.UP + ansi_1.ANSI.ERASE_LINE + ansi_1.ANSI.CURSOR_LEFT);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
// 1. Render the Prompt Message
|
|
26
|
+
this.print(ansi_1.ANSI.ERASE_LINE + ansi_1.ANSI.CURSOR_LEFT);
|
|
27
|
+
const icon = this.errorMsg ? `${theme_1.theme.error}✖` : `${theme_1.theme.success}?`;
|
|
28
|
+
this.print(`${icon} ${ansi_1.ANSI.BOLD}${theme_1.theme.title}${this.options.message}${ansi_1.ANSI.RESET} `);
|
|
29
|
+
// 2. Render the Value
|
|
30
|
+
this.print(`${theme_1.theme.main}${this.stringValue}${ansi_1.ANSI.RESET}`);
|
|
31
|
+
// 3. Handle Error Message
|
|
32
|
+
if (this.errorMsg) {
|
|
33
|
+
this.print(`\n${ansi_1.ANSI.ERASE_LINE}${theme_1.theme.error}>> ${this.errorMsg}${ansi_1.ANSI.RESET}`);
|
|
34
|
+
this.print(ansi_1.ANSI.UP);
|
|
35
|
+
const promptLen = this.options.message.length + 3;
|
|
36
|
+
const valLen = this.stringValue.length;
|
|
37
|
+
this.print(`\x1b[1000D\x1b[${promptLen + valLen}C`);
|
|
38
|
+
}
|
|
39
|
+
// 4. Position Cursor
|
|
40
|
+
const diff = this.stringValue.length - this.cursor;
|
|
41
|
+
if (diff > 0) {
|
|
42
|
+
this.print(`\x1b[${diff}D`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
handleInput(char) {
|
|
46
|
+
// Enter
|
|
47
|
+
if (char === '\r' || char === '\n') {
|
|
48
|
+
const num = parseFloat(this.stringValue);
|
|
49
|
+
if (isNaN(num)) {
|
|
50
|
+
this.errorMsg = 'Please enter a valid number.';
|
|
51
|
+
this.render(false);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
if (this.options.min !== undefined && num < this.options.min) {
|
|
55
|
+
this.errorMsg = `Minimum value is ${this.options.min}`;
|
|
56
|
+
this.render(false);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
if (this.options.max !== undefined && num > this.options.max) {
|
|
60
|
+
this.errorMsg = `Maximum value is ${this.options.max}`;
|
|
61
|
+
this.render(false);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
if (this.errorMsg) {
|
|
65
|
+
this.print(`\n${ansi_1.ANSI.ERASE_LINE}${ansi_1.ANSI.UP}`);
|
|
66
|
+
}
|
|
67
|
+
this.submit(num);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
// Up Arrow (Increment)
|
|
71
|
+
if (this.isUp(char)) {
|
|
72
|
+
let num = parseFloat(this.stringValue) || 0;
|
|
73
|
+
num += (this.options.step ?? 1);
|
|
74
|
+
if (this.options.max !== undefined && num > this.options.max)
|
|
75
|
+
num = this.options.max;
|
|
76
|
+
this.stringValue = num.toString();
|
|
77
|
+
this.cursor = this.stringValue.length;
|
|
78
|
+
this.errorMsg = '';
|
|
79
|
+
this.render(false);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
// Down Arrow (Decrement)
|
|
83
|
+
if (this.isDown(char)) {
|
|
84
|
+
let num = parseFloat(this.stringValue) || 0;
|
|
85
|
+
num -= (this.options.step ?? 1);
|
|
86
|
+
if (this.options.min !== undefined && num < this.options.min)
|
|
87
|
+
num = this.options.min;
|
|
88
|
+
this.stringValue = num.toString();
|
|
89
|
+
this.cursor = this.stringValue.length;
|
|
90
|
+
this.errorMsg = '';
|
|
91
|
+
this.render(false);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
// Backspace
|
|
95
|
+
if (char === '\u0008' || char === '\x7f') {
|
|
96
|
+
if (this.cursor > 0) {
|
|
97
|
+
this.stringValue = this.stringValue.slice(0, this.cursor - 1) + this.stringValue.slice(this.cursor);
|
|
98
|
+
this.cursor--;
|
|
99
|
+
this.errorMsg = '';
|
|
100
|
+
this.render(false);
|
|
101
|
+
}
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
// Arrow Left
|
|
105
|
+
if (this.isLeft(char)) {
|
|
106
|
+
if (this.cursor > 0) {
|
|
107
|
+
this.cursor--;
|
|
108
|
+
this.render(false);
|
|
109
|
+
}
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
// Arrow Right
|
|
113
|
+
if (this.isRight(char)) {
|
|
114
|
+
if (this.cursor < this.stringValue.length) {
|
|
115
|
+
this.cursor++;
|
|
116
|
+
this.render(false);
|
|
117
|
+
}
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
// Numeric Input (and . and -)
|
|
121
|
+
// Simple paste support for numbers is also good
|
|
122
|
+
if (/^[0-9.\-]+$/.test(char)) {
|
|
123
|
+
// Basic validation for pasted content
|
|
124
|
+
if (char.includes('-') && (this.cursor !== 0 || this.stringValue.includes('-') || char.lastIndexOf('-') > 0)) {
|
|
125
|
+
// If complex paste fails simple checks, ignore or let user correct
|
|
126
|
+
// For now, strict check on single char logic is preserved if we want,
|
|
127
|
+
// but let's allow pasting valid number strings
|
|
128
|
+
}
|
|
129
|
+
// Allow if it looks like a number part
|
|
130
|
+
this.stringValue = this.stringValue.slice(0, this.cursor) + char + this.stringValue.slice(this.cursor);
|
|
131
|
+
this.cursor += char.length;
|
|
132
|
+
this.errorMsg = '';
|
|
133
|
+
this.render(false);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
exports.NumberPrompt = NumberPrompt;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Prompt } from '../base';
|
|
2
|
+
import { SelectOptions } from '../types';
|
|
3
|
+
export declare class SelectPrompt extends Prompt<any, SelectOptions> {
|
|
4
|
+
private selectedIndex;
|
|
5
|
+
private searchBuffer;
|
|
6
|
+
private scrollTop;
|
|
7
|
+
private readonly pageSize;
|
|
8
|
+
constructor(options: SelectOptions);
|
|
9
|
+
private isSeparator;
|
|
10
|
+
private findNextSelectableIndex;
|
|
11
|
+
private getFilteredChoices;
|
|
12
|
+
private lastRenderHeight;
|
|
13
|
+
protected renderWrapper(firstRender: boolean): void;
|
|
14
|
+
protected render(firstRender: boolean): void;
|
|
15
|
+
protected handleInput(char: string): void;
|
|
16
|
+
}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SelectPrompt = void 0;
|
|
4
|
+
const ansi_1 = require("../ansi");
|
|
5
|
+
const base_1 = require("../base");
|
|
6
|
+
const theme_1 = require("../theme");
|
|
7
|
+
// --- Implementation: Select Prompt ---
|
|
8
|
+
class SelectPrompt extends base_1.Prompt {
|
|
9
|
+
constructor(options) {
|
|
10
|
+
super(options);
|
|
11
|
+
this.selectedIndex = 0;
|
|
12
|
+
this.searchBuffer = '';
|
|
13
|
+
this.scrollTop = 0;
|
|
14
|
+
this.pageSize = 7;
|
|
15
|
+
// Custom render to handle variable height clearing
|
|
16
|
+
this.lastRenderHeight = 0;
|
|
17
|
+
// Find first non-separator index
|
|
18
|
+
this.selectedIndex = this.findNextSelectableIndex(-1, 1);
|
|
19
|
+
}
|
|
20
|
+
isSeparator(item) {
|
|
21
|
+
return item && item.separator === true;
|
|
22
|
+
}
|
|
23
|
+
findNextSelectableIndex(currentIndex, direction) {
|
|
24
|
+
let nextIndex = currentIndex + direction;
|
|
25
|
+
const choices = this.getFilteredChoices();
|
|
26
|
+
// Loop around logic
|
|
27
|
+
if (nextIndex < 0)
|
|
28
|
+
nextIndex = choices.length - 1;
|
|
29
|
+
if (nextIndex >= choices.length)
|
|
30
|
+
nextIndex = 0;
|
|
31
|
+
if (choices.length === 0)
|
|
32
|
+
return 0;
|
|
33
|
+
// Safety check to prevent infinite loop if all are separators (shouldn't happen in practice)
|
|
34
|
+
let count = 0;
|
|
35
|
+
while (this.isSeparator(choices[nextIndex]) && count < choices.length) {
|
|
36
|
+
nextIndex += direction;
|
|
37
|
+
if (nextIndex < 0)
|
|
38
|
+
nextIndex = choices.length - 1;
|
|
39
|
+
if (nextIndex >= choices.length)
|
|
40
|
+
nextIndex = 0;
|
|
41
|
+
count++;
|
|
42
|
+
}
|
|
43
|
+
return nextIndex;
|
|
44
|
+
}
|
|
45
|
+
getFilteredChoices() {
|
|
46
|
+
if (!this.searchBuffer)
|
|
47
|
+
return this.options.choices;
|
|
48
|
+
return this.options.choices.filter(c => {
|
|
49
|
+
if (this.isSeparator(c))
|
|
50
|
+
return false; // Hide separators when searching
|
|
51
|
+
return c.title.toLowerCase().includes(this.searchBuffer.toLowerCase());
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
renderWrapper(firstRender) {
|
|
55
|
+
if (!firstRender && this.lastRenderHeight > 0) {
|
|
56
|
+
this.print(`\x1b[${this.lastRenderHeight}A`);
|
|
57
|
+
}
|
|
58
|
+
let output = '';
|
|
59
|
+
const choices = this.getFilteredChoices();
|
|
60
|
+
// Adjust Scroll Top
|
|
61
|
+
if (this.selectedIndex < this.scrollTop) {
|
|
62
|
+
this.scrollTop = this.selectedIndex;
|
|
63
|
+
}
|
|
64
|
+
else if (this.selectedIndex >= this.scrollTop + this.pageSize) {
|
|
65
|
+
this.scrollTop = this.selectedIndex - this.pageSize + 1;
|
|
66
|
+
}
|
|
67
|
+
// Handle Filtering Edge Case: if list shrinks, scrollTop might be too high
|
|
68
|
+
if (this.scrollTop > choices.length - 1) {
|
|
69
|
+
this.scrollTop = Math.max(0, choices.length - this.pageSize);
|
|
70
|
+
}
|
|
71
|
+
// Header
|
|
72
|
+
const searchStr = this.searchBuffer ? ` ${theme_1.theme.muted}(Filter: ${this.searchBuffer})${ansi_1.ANSI.RESET}` : '';
|
|
73
|
+
output += `${ansi_1.ANSI.ERASE_LINE}${ansi_1.ANSI.CURSOR_LEFT}${theme_1.theme.success}?${ansi_1.ANSI.RESET} ${ansi_1.ANSI.BOLD}${theme_1.theme.title}${this.options.message}${ansi_1.ANSI.RESET}${searchStr}\n`;
|
|
74
|
+
if (choices.length === 0) {
|
|
75
|
+
output += `${ansi_1.ANSI.ERASE_LINE}${ansi_1.ANSI.CURSOR_LEFT} ${theme_1.theme.muted}No results found${ansi_1.ANSI.RESET}\n`;
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
const visibleChoices = choices.slice(this.scrollTop, this.scrollTop + this.pageSize);
|
|
79
|
+
visibleChoices.forEach((choice, index) => {
|
|
80
|
+
const actualIndex = this.scrollTop + index;
|
|
81
|
+
output += `${ansi_1.ANSI.ERASE_LINE}${ansi_1.ANSI.CURSOR_LEFT}`;
|
|
82
|
+
if (this.isSeparator(choice)) {
|
|
83
|
+
output += ` ${ansi_1.ANSI.DIM}${choice.text || '────────'}${ansi_1.ANSI.RESET}\n`;
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
if (actualIndex === this.selectedIndex) {
|
|
87
|
+
output += `${theme_1.theme.main}❯ ${choice.title}${ansi_1.ANSI.RESET}\n`;
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
output += ` ${choice.title}\n`;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
this.print(output);
|
|
96
|
+
// Clear remaining lines if list shrunk
|
|
97
|
+
const visibleCount = Math.min(choices.length, this.pageSize);
|
|
98
|
+
const currentHeight = visibleCount + 1 + (choices.length === 0 ? 1 : 0);
|
|
99
|
+
const linesToClear = this.lastRenderHeight - currentHeight;
|
|
100
|
+
if (linesToClear > 0) {
|
|
101
|
+
for (let i = 0; i < linesToClear; i++) {
|
|
102
|
+
this.print(`${ansi_1.ANSI.ERASE_LINE}\n`);
|
|
103
|
+
}
|
|
104
|
+
this.print(`\x1b[${linesToClear}A`); // Move back up
|
|
105
|
+
}
|
|
106
|
+
this.lastRenderHeight = currentHeight;
|
|
107
|
+
}
|
|
108
|
+
render(firstRender) {
|
|
109
|
+
this.print(ansi_1.ANSI.HIDE_CURSOR);
|
|
110
|
+
this.renderWrapper(firstRender);
|
|
111
|
+
}
|
|
112
|
+
handleInput(char) {
|
|
113
|
+
const choices = this.getFilteredChoices();
|
|
114
|
+
if (char === '\r' || char === '\n') {
|
|
115
|
+
if (choices.length === 0) {
|
|
116
|
+
this.searchBuffer = '';
|
|
117
|
+
this.selectedIndex = this.findNextSelectableIndex(-1, 1);
|
|
118
|
+
this.render(false);
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
if (this.isSeparator(choices[this.selectedIndex]))
|
|
122
|
+
return;
|
|
123
|
+
this.cleanup();
|
|
124
|
+
this.print(ansi_1.ANSI.SHOW_CURSOR);
|
|
125
|
+
if (this._resolve)
|
|
126
|
+
this._resolve(choices[this.selectedIndex].value);
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
if (this.isUp(char)) { // Up
|
|
130
|
+
if (choices.length > 0) {
|
|
131
|
+
this.selectedIndex = this.findNextSelectableIndex(this.selectedIndex, -1);
|
|
132
|
+
this.render(false);
|
|
133
|
+
}
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
if (this.isDown(char)) { // Down
|
|
137
|
+
if (choices.length > 0) {
|
|
138
|
+
this.selectedIndex = this.findNextSelectableIndex(this.selectedIndex, 1);
|
|
139
|
+
this.render(false);
|
|
140
|
+
}
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
// Backspace
|
|
144
|
+
if (char === '\u0008' || char === '\x7f') {
|
|
145
|
+
if (this.searchBuffer.length > 0) {
|
|
146
|
+
this.searchBuffer = this.searchBuffer.slice(0, -1);
|
|
147
|
+
this.selectedIndex = 0; // Reset selection
|
|
148
|
+
this.selectedIndex = this.findNextSelectableIndex(-1, 1);
|
|
149
|
+
this.render(false);
|
|
150
|
+
}
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
// Typing
|
|
154
|
+
if (char.length === 1 && !/^[\x00-\x1F]/.test(char)) {
|
|
155
|
+
this.searchBuffer += char;
|
|
156
|
+
this.selectedIndex = 0; // Reset selection
|
|
157
|
+
this.selectedIndex = this.findNextSelectableIndex(-1, 1);
|
|
158
|
+
this.render(false);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
exports.SelectPrompt = SelectPrompt;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { Prompt } from '../base';
|
|
2
|
+
import { SliderOptions } from '../types';
|
|
3
|
+
export declare class SliderPrompt extends Prompt<number, SliderOptions> {
|
|
4
|
+
constructor(options: SliderOptions);
|
|
5
|
+
protected render(firstRender: boolean): void;
|
|
6
|
+
protected handleInput(char: string): void;
|
|
7
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SliderPrompt = void 0;
|
|
4
|
+
const ansi_1 = require("../ansi");
|
|
5
|
+
const base_1 = require("../base");
|
|
6
|
+
const theme_1 = require("../theme");
|
|
7
|
+
// --- Implementation: Slider Prompt ---
|
|
8
|
+
class SliderPrompt extends base_1.Prompt {
|
|
9
|
+
constructor(options) {
|
|
10
|
+
super(options);
|
|
11
|
+
this.value = options.initial ?? options.min;
|
|
12
|
+
}
|
|
13
|
+
render(firstRender) {
|
|
14
|
+
this.print(ansi_1.ANSI.HIDE_CURSOR);
|
|
15
|
+
if (!firstRender) {
|
|
16
|
+
this.print(ansi_1.ANSI.ERASE_LINE + ansi_1.ANSI.CURSOR_LEFT);
|
|
17
|
+
}
|
|
18
|
+
const width = 20;
|
|
19
|
+
const range = this.options.max - this.options.min;
|
|
20
|
+
const ratio = (this.value - this.options.min) / range;
|
|
21
|
+
const pos = Math.round(ratio * width);
|
|
22
|
+
let bar = '';
|
|
23
|
+
for (let i = 0; i <= width; i++) {
|
|
24
|
+
if (i === pos)
|
|
25
|
+
bar += `${theme_1.theme.main}O${ansi_1.ANSI.RESET}`;
|
|
26
|
+
else
|
|
27
|
+
bar += '─';
|
|
28
|
+
}
|
|
29
|
+
const unit = this.options.unit || '';
|
|
30
|
+
this.print(`${theme_1.theme.success}?${ansi_1.ANSI.RESET} ${ansi_1.ANSI.BOLD}${theme_1.theme.title}${this.options.message}${ansi_1.ANSI.RESET} [${bar}] ${this.value}${unit}`);
|
|
31
|
+
}
|
|
32
|
+
handleInput(char) {
|
|
33
|
+
if (char === '\r' || char === '\n') {
|
|
34
|
+
this.submit(this.value);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
const step = this.options.step || 1;
|
|
38
|
+
if (this.isLeft(char)) { // Left
|
|
39
|
+
this.value = Math.max(this.options.min, this.value - step);
|
|
40
|
+
this.render(false);
|
|
41
|
+
}
|
|
42
|
+
if (this.isRight(char)) { // Right
|
|
43
|
+
this.value = Math.min(this.options.max, this.value + step);
|
|
44
|
+
this.render(false);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
exports.SliderPrompt = SliderPrompt;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Prompt } from '../base';
|
|
2
|
+
import { TextOptions } from '../types';
|
|
3
|
+
export declare class TextPrompt extends Prompt<string, TextOptions> {
|
|
4
|
+
private errorMsg;
|
|
5
|
+
private cursor;
|
|
6
|
+
private hasTyped;
|
|
7
|
+
private renderLines;
|
|
8
|
+
constructor(options: TextOptions);
|
|
9
|
+
protected render(firstRender: boolean): void;
|
|
10
|
+
protected handleInput(char: string): void;
|
|
11
|
+
private validateAndSubmit;
|
|
12
|
+
}
|