mepcli 0.2.5 → 0.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/base.d.ts +13 -0
- package/dist/base.js +256 -2
- package/dist/input.d.ts +13 -0
- package/dist/input.js +89 -0
- package/dist/prompts/checkbox.d.ts +2 -0
- package/dist/prompts/checkbox.js +40 -18
- package/dist/prompts/confirm.js +9 -7
- package/dist/prompts/date.js +4 -25
- package/dist/prompts/file.js +50 -27
- package/dist/prompts/list.js +22 -17
- package/dist/prompts/multi-select.js +6 -32
- package/dist/prompts/number.d.ts +1 -1
- package/dist/prompts/number.js +30 -34
- package/dist/prompts/select.d.ts +0 -2
- package/dist/prompts/select.js +15 -29
- package/dist/prompts/slider.js +6 -5
- package/dist/prompts/text.d.ts +2 -1
- package/dist/prompts/text.js +198 -122
- package/dist/prompts/toggle.js +2 -6
- package/dist/utils.d.ts +23 -0
- package/dist/utils.js +135 -0
- package/package.json +4 -4
package/dist/prompts/file.js
CHANGED
|
@@ -61,18 +61,27 @@ class FilePrompt extends base_1.Prompt {
|
|
|
61
61
|
.filter(f => f.startsWith(partial))
|
|
62
62
|
.filter(f => {
|
|
63
63
|
const fullPath = path.join(dir, f);
|
|
64
|
-
|
|
65
|
-
|
|
64
|
+
// Handle errors if file doesn't exist or permission denied
|
|
65
|
+
try {
|
|
66
|
+
const isDir = fs.statSync(fullPath).isDirectory();
|
|
67
|
+
if (this.options.onlyDirectories && !isDir)
|
|
68
|
+
return false;
|
|
69
|
+
if (this.options.extensions && !isDir) {
|
|
70
|
+
return this.options.extensions.some(ext => f.endsWith(ext));
|
|
71
|
+
}
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
catch (e) {
|
|
66
75
|
return false;
|
|
67
|
-
if (this.options.extensions && !isDir) {
|
|
68
|
-
return this.options.extensions.some(ext => f.endsWith(ext));
|
|
69
76
|
}
|
|
70
|
-
return true;
|
|
71
77
|
})
|
|
72
78
|
.map(f => {
|
|
73
79
|
const fullPath = path.join(dir, f);
|
|
74
|
-
|
|
75
|
-
|
|
80
|
+
try {
|
|
81
|
+
if (fs.statSync(fullPath).isDirectory())
|
|
82
|
+
return f + '/';
|
|
83
|
+
}
|
|
84
|
+
catch (e) { /* ignore */ }
|
|
76
85
|
return f;
|
|
77
86
|
});
|
|
78
87
|
}
|
|
@@ -86,37 +95,51 @@ class FilePrompt extends base_1.Prompt {
|
|
|
86
95
|
this.selectedSuggestion = -1;
|
|
87
96
|
}
|
|
88
97
|
render(firstRender) {
|
|
89
|
-
|
|
90
|
-
if (!firstRender) {
|
|
91
|
-
// Clear input line + suggestions
|
|
92
|
-
this.print(ansi_1.ANSI.ERASE_LINE + ansi_1.ANSI.CURSOR_LEFT); // Input line
|
|
93
|
-
// We need to track how many lines suggestions took
|
|
94
|
-
// For now assume simple clear, or use ANSI.ERASE_DOWN if at bottom?
|
|
95
|
-
// Safer to move up and clear
|
|
96
|
-
this.print(ansi_1.ANSI.ERASE_DOWN);
|
|
97
|
-
}
|
|
98
|
+
// Construct string
|
|
98
99
|
const icon = this.errorMsg ? `${theme_1.theme.error}✖` : `${theme_1.theme.success}?`;
|
|
99
|
-
|
|
100
|
+
let output = `${icon} ${ansi_1.ANSI.BOLD}${theme_1.theme.title}${this.options.message}${ansi_1.ANSI.RESET} ${this.input}`;
|
|
101
|
+
// Suggestions
|
|
100
102
|
if (this.suggestions.length > 0) {
|
|
101
|
-
|
|
103
|
+
output += '\n'; // Separate input from suggestions
|
|
102
104
|
const maxShow = 5;
|
|
103
|
-
this.suggestions.slice(0, maxShow)
|
|
105
|
+
const displayed = this.suggestions.slice(0, maxShow);
|
|
106
|
+
displayed.forEach((s, i) => {
|
|
107
|
+
if (i > 0)
|
|
108
|
+
output += '\n';
|
|
104
109
|
if (i === this.selectedSuggestion) {
|
|
105
|
-
|
|
110
|
+
output += `${theme_1.theme.main}❯ ${s}${ansi_1.ANSI.RESET}`;
|
|
106
111
|
}
|
|
107
112
|
else {
|
|
108
|
-
|
|
113
|
+
output += ` ${s}`;
|
|
109
114
|
}
|
|
110
115
|
});
|
|
111
116
|
if (this.suggestions.length > maxShow) {
|
|
112
|
-
|
|
117
|
+
output += `\n ...and ${this.suggestions.length - maxShow} more`;
|
|
113
118
|
}
|
|
114
|
-
// Move cursor back to input line
|
|
115
|
-
const lines = Math.min(this.suggestions.length, maxShow) + (this.suggestions.length > maxShow ? 2 : 1);
|
|
116
|
-
this.print(`\x1b[${lines}A`);
|
|
117
|
-
const inputLen = this.options.message.length + 3 + this.input.length;
|
|
118
|
-
this.print(`\x1b[${inputLen}C`);
|
|
119
119
|
}
|
|
120
|
+
this.renderFrame(output);
|
|
121
|
+
this.print(ansi_1.ANSI.SHOW_CURSOR);
|
|
122
|
+
// Restore Cursor Logic
|
|
123
|
+
// We need to move up to the input line if we printed suggestions.
|
|
124
|
+
// The input line is always the first line (index 0).
|
|
125
|
+
// So we move up by (totalLines - 1).
|
|
126
|
+
const totalLines = this.lastRenderHeight; // renderFrame sets this
|
|
127
|
+
if (totalLines > 1) {
|
|
128
|
+
this.print(`\x1b[${totalLines - 1}A`);
|
|
129
|
+
}
|
|
130
|
+
// Move right
|
|
131
|
+
const prefix = `${icon} ${theme_1.theme.title}${this.options.message} `;
|
|
132
|
+
const prefixLen = this.stripAnsi(prefix).length;
|
|
133
|
+
// Cursor is usually at the end of input unless we add backspace support etc.
|
|
134
|
+
// The cursor property tracks it, but my handleInput simplified it.
|
|
135
|
+
// Let's rely on this.input.length for now since handleInput appends.
|
|
136
|
+
// Ah, handleInput logic below supports cursor pos theoretically but I only see appending?
|
|
137
|
+
// Actually handleInput doesn't support left/right in the original code, it supports down/up for suggestions.
|
|
138
|
+
// So cursor is always at end.
|
|
139
|
+
const targetCol = prefixLen + this.input.length;
|
|
140
|
+
this.print(ansi_1.ANSI.CURSOR_LEFT);
|
|
141
|
+
if (targetCol > 0)
|
|
142
|
+
this.print(`\x1b[${targetCol}C`);
|
|
120
143
|
}
|
|
121
144
|
handleInput(char) {
|
|
122
145
|
if (char === '\t') { // Tab
|
package/dist/prompts/list.js
CHANGED
|
@@ -13,32 +13,37 @@ class ListPrompt extends base_1.Prompt {
|
|
|
13
13
|
this.value = options.initial || [];
|
|
14
14
|
}
|
|
15
15
|
render(firstRender) {
|
|
16
|
-
|
|
17
|
-
if (!firstRender) {
|
|
18
|
-
this.print(ansi_1.ANSI.ERASE_LINE + ansi_1.ANSI.CURSOR_LEFT);
|
|
19
|
-
if (this.errorMsg) {
|
|
20
|
-
this.print(ansi_1.ANSI.UP + ansi_1.ANSI.ERASE_LINE + ansi_1.ANSI.CURSOR_LEFT);
|
|
21
|
-
}
|
|
22
|
-
}
|
|
16
|
+
// Prepare content
|
|
23
17
|
const icon = this.errorMsg ? `${theme_1.theme.error}✖` : `${theme_1.theme.success}?`;
|
|
24
|
-
|
|
18
|
+
let mainLine = `${icon} ${ansi_1.ANSI.BOLD}${theme_1.theme.title}${this.options.message}${ansi_1.ANSI.RESET} `;
|
|
25
19
|
// Render Tags
|
|
26
20
|
if (this.value.length > 0) {
|
|
27
21
|
this.value.forEach((tag) => {
|
|
28
|
-
|
|
22
|
+
mainLine += `${theme_1.theme.main}[${tag}]${ansi_1.ANSI.RESET} `;
|
|
29
23
|
});
|
|
30
24
|
}
|
|
31
25
|
// Render Current Input
|
|
32
|
-
|
|
26
|
+
mainLine += `${this.currentInput}`;
|
|
27
|
+
let output = mainLine;
|
|
33
28
|
if (this.errorMsg) {
|
|
34
|
-
|
|
29
|
+
output += `\n${theme_1.theme.error}>> ${this.errorMsg}${ansi_1.ANSI.RESET}`;
|
|
30
|
+
}
|
|
31
|
+
// Use Double Buffering
|
|
32
|
+
this.renderFrame(output);
|
|
33
|
+
this.print(ansi_1.ANSI.SHOW_CURSOR);
|
|
34
|
+
// If we printed an error, the cursor is at the end of the error line.
|
|
35
|
+
// We need to move it back to the end of the input line.
|
|
36
|
+
if (this.errorMsg) {
|
|
37
|
+
// Move up one line (since error is always on the next line in this simple implementation)
|
|
35
38
|
this.print(ansi_1.ANSI.UP);
|
|
36
|
-
//
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
this.
|
|
40
|
-
|
|
41
|
-
|
|
39
|
+
// Move to the correct column.
|
|
40
|
+
// We need to calculate visual length of mainLine to place cursor correctly.
|
|
41
|
+
// stripAnsi is available in base class now.
|
|
42
|
+
const visualLength = this.stripAnsi(mainLine).length;
|
|
43
|
+
this.print(ansi_1.ANSI.CURSOR_LEFT); // Go to start
|
|
44
|
+
if (visualLength > 0) {
|
|
45
|
+
this.print(`\x1b[${visualLength}C`);
|
|
46
|
+
}
|
|
42
47
|
}
|
|
43
48
|
}
|
|
44
49
|
handleInput(char) {
|
|
@@ -24,31 +24,6 @@ class MultiSelectPrompt extends base_1.Prompt {
|
|
|
24
24
|
.filter(c => c.title.toLowerCase().includes(this.searchBuffer.toLowerCase()));
|
|
25
25
|
}
|
|
26
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
27
|
let output = '';
|
|
53
28
|
const choices = this.getFilteredChoices();
|
|
54
29
|
// Adjust Scroll
|
|
@@ -65,27 +40,26 @@ class MultiSelectPrompt extends base_1.Prompt {
|
|
|
65
40
|
const searchStr = this.searchBuffer ? ` ${theme_1.theme.muted}(Filter: ${this.searchBuffer})${ansi_1.ANSI.RESET}` : '';
|
|
66
41
|
output += `${icon} ${ansi_1.ANSI.BOLD}${theme_1.theme.title}${this.options.message}${ansi_1.ANSI.RESET}${searchStr}\n`;
|
|
67
42
|
if (choices.length === 0) {
|
|
68
|
-
output += ` ${theme_1.theme.muted}No results found${ansi_1.ANSI.RESET}
|
|
43
|
+
output += ` ${theme_1.theme.muted}No results found${ansi_1.ANSI.RESET}`; // No newline at end
|
|
69
44
|
}
|
|
70
45
|
else {
|
|
71
46
|
const visible = choices.slice(this.scrollTop, this.scrollTop + this.pageSize);
|
|
72
47
|
visible.forEach((choice, index) => {
|
|
48
|
+
if (index > 0)
|
|
49
|
+
output += '\n';
|
|
73
50
|
const actualIndex = this.scrollTop + index;
|
|
74
51
|
const cursor = actualIndex === this.selectedIndex ? `${theme_1.theme.main}❯${ansi_1.ANSI.RESET}` : ' ';
|
|
75
52
|
const isChecked = this.checkedState[choice.originalIndex];
|
|
76
53
|
const checkbox = isChecked
|
|
77
54
|
? `${theme_1.theme.success}◉${ansi_1.ANSI.RESET}`
|
|
78
55
|
: `${theme_1.theme.muted}◯${ansi_1.ANSI.RESET}`;
|
|
79
|
-
output += `${cursor} ${checkbox} ${choice.title}
|
|
56
|
+
output += `${cursor} ${checkbox} ${choice.title}`;
|
|
80
57
|
});
|
|
81
58
|
}
|
|
82
59
|
if (this.errorMsg) {
|
|
83
|
-
output +=
|
|
60
|
+
output += `\n${theme_1.theme.error}>> ${this.errorMsg}${ansi_1.ANSI.RESET}`;
|
|
84
61
|
}
|
|
85
|
-
this.
|
|
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;
|
|
62
|
+
this.renderFrame(output);
|
|
89
63
|
}
|
|
90
64
|
handleInput(char) {
|
|
91
65
|
const choices = this.getFilteredChoices();
|
package/dist/prompts/number.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { Prompt } from '../base';
|
|
2
2
|
import { NumberOptions } from '../types';
|
|
3
3
|
export declare class NumberPrompt extends Prompt<number, NumberOptions> {
|
|
4
|
-
private cursor;
|
|
5
4
|
private stringValue;
|
|
5
|
+
private cursor;
|
|
6
6
|
private errorMsg;
|
|
7
7
|
constructor(options: NumberOptions);
|
|
8
8
|
protected render(firstRender: boolean): void;
|
package/dist/prompts/number.js
CHANGED
|
@@ -8,45 +8,47 @@ const theme_1 = require("../theme");
|
|
|
8
8
|
class NumberPrompt extends base_1.Prompt {
|
|
9
9
|
constructor(options) {
|
|
10
10
|
super(options);
|
|
11
|
+
this.stringValue = '';
|
|
11
12
|
this.cursor = 0;
|
|
12
13
|
this.errorMsg = '';
|
|
13
|
-
|
|
14
|
-
|
|
14
|
+
// We work with string for editing, but value property stores the parsed number ultimately
|
|
15
|
+
// Initialize stringValue from initial
|
|
16
|
+
this.stringValue = options.initial !== undefined ? options.initial.toString() : '';
|
|
15
17
|
this.cursor = this.stringValue.length;
|
|
16
18
|
}
|
|
17
19
|
render(firstRender) {
|
|
18
|
-
|
|
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);
|
|
20
|
+
// Prepare content
|
|
27
21
|
const icon = this.errorMsg ? `${theme_1.theme.error}✖` : `${theme_1.theme.success}?`;
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
22
|
+
// Prefix
|
|
23
|
+
let output = `${icon} ${ansi_1.ANSI.BOLD}${theme_1.theme.title}${this.options.message}${ansi_1.ANSI.RESET} `;
|
|
24
|
+
// Value
|
|
25
|
+
output += `${theme_1.theme.main}${this.stringValue}${ansi_1.ANSI.RESET}`;
|
|
26
|
+
if (this.errorMsg) {
|
|
27
|
+
output += `\n${theme_1.theme.error}>> ${this.errorMsg}${ansi_1.ANSI.RESET}`;
|
|
28
|
+
}
|
|
29
|
+
this.renderFrame(output);
|
|
30
|
+
this.print(ansi_1.ANSI.SHOW_CURSOR);
|
|
31
|
+
// Restore cursor position
|
|
32
|
+
// If errorMsg, we are on the line below the input.
|
|
32
33
|
if (this.errorMsg) {
|
|
33
|
-
this.print(`\n${ansi_1.ANSI.ERASE_LINE}${theme_1.theme.error}>> ${this.errorMsg}${ansi_1.ANSI.RESET}`);
|
|
34
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
35
|
}
|
|
39
|
-
//
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
36
|
+
// Calculate visual offset
|
|
37
|
+
const prefix = `${icon} ${theme_1.theme.title}${this.options.message} `;
|
|
38
|
+
const prefixLen = this.stripAnsi(prefix).length;
|
|
39
|
+
const targetCol = prefixLen + this.cursor;
|
|
40
|
+
this.print(ansi_1.ANSI.CURSOR_LEFT);
|
|
41
|
+
if (targetCol > 0) {
|
|
42
|
+
this.print(`\x1b[${targetCol}C`);
|
|
43
43
|
}
|
|
44
44
|
}
|
|
45
45
|
handleInput(char) {
|
|
46
46
|
// Enter
|
|
47
47
|
if (char === '\r' || char === '\n') {
|
|
48
48
|
const num = parseFloat(this.stringValue);
|
|
49
|
-
if (isNaN(num)) {
|
|
49
|
+
if (this.stringValue.trim() === '' || isNaN(num)) {
|
|
50
|
+
// Check if empty is allowed?
|
|
51
|
+
// If not required? Assuming required for number prompt usually
|
|
50
52
|
this.errorMsg = 'Please enter a valid number.';
|
|
51
53
|
this.render(false);
|
|
52
54
|
return;
|
|
@@ -61,9 +63,6 @@ class NumberPrompt extends base_1.Prompt {
|
|
|
61
63
|
this.render(false);
|
|
62
64
|
return;
|
|
63
65
|
}
|
|
64
|
-
if (this.errorMsg) {
|
|
65
|
-
this.print(`\n${ansi_1.ANSI.ERASE_LINE}${ansi_1.ANSI.UP}`);
|
|
66
|
-
}
|
|
67
66
|
this.submit(num);
|
|
68
67
|
return;
|
|
69
68
|
}
|
|
@@ -73,6 +72,8 @@ class NumberPrompt extends base_1.Prompt {
|
|
|
73
72
|
num += (this.options.step ?? 1);
|
|
74
73
|
if (this.options.max !== undefined && num > this.options.max)
|
|
75
74
|
num = this.options.max;
|
|
75
|
+
// Round to avoid float errors
|
|
76
|
+
num = Math.round(num * 10000) / 10000;
|
|
76
77
|
this.stringValue = num.toString();
|
|
77
78
|
this.cursor = this.stringValue.length;
|
|
78
79
|
this.errorMsg = '';
|
|
@@ -85,6 +86,8 @@ class NumberPrompt extends base_1.Prompt {
|
|
|
85
86
|
num -= (this.options.step ?? 1);
|
|
86
87
|
if (this.options.min !== undefined && num < this.options.min)
|
|
87
88
|
num = this.options.min;
|
|
89
|
+
// Round to avoid float errors
|
|
90
|
+
num = Math.round(num * 10000) / 10000;
|
|
88
91
|
this.stringValue = num.toString();
|
|
89
92
|
this.cursor = this.stringValue.length;
|
|
90
93
|
this.errorMsg = '';
|
|
@@ -118,14 +121,7 @@ class NumberPrompt extends base_1.Prompt {
|
|
|
118
121
|
return;
|
|
119
122
|
}
|
|
120
123
|
// Numeric Input (and . and -)
|
|
121
|
-
// Simple paste support for numbers is also good
|
|
122
124
|
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
125
|
// Allow if it looks like a number part
|
|
130
126
|
this.stringValue = this.stringValue.slice(0, this.cursor) + char + this.stringValue.slice(this.cursor);
|
|
131
127
|
this.cursor += char.length;
|
package/dist/prompts/select.d.ts
CHANGED
|
@@ -9,8 +9,6 @@ export declare class SelectPrompt extends Prompt<any, SelectOptions> {
|
|
|
9
9
|
private isSeparator;
|
|
10
10
|
private findNextSelectableIndex;
|
|
11
11
|
private getFilteredChoices;
|
|
12
|
-
private lastRenderHeight;
|
|
13
|
-
protected renderWrapper(firstRender: boolean): void;
|
|
14
12
|
protected render(firstRender: boolean): void;
|
|
15
13
|
protected handleInput(char: string): void;
|
|
16
14
|
}
|
package/dist/prompts/select.js
CHANGED
|
@@ -12,8 +12,6 @@ class SelectPrompt extends base_1.Prompt {
|
|
|
12
12
|
this.searchBuffer = '';
|
|
13
13
|
this.scrollTop = 0;
|
|
14
14
|
this.pageSize = 7;
|
|
15
|
-
// Custom render to handle variable height clearing
|
|
16
|
-
this.lastRenderHeight = 0;
|
|
17
15
|
// Find first non-separator index
|
|
18
16
|
this.selectedIndex = this.findNextSelectableIndex(-1, 1);
|
|
19
17
|
}
|
|
@@ -51,10 +49,7 @@ class SelectPrompt extends base_1.Prompt {
|
|
|
51
49
|
return c.title.toLowerCase().includes(this.searchBuffer.toLowerCase());
|
|
52
50
|
});
|
|
53
51
|
}
|
|
54
|
-
|
|
55
|
-
if (!firstRender && this.lastRenderHeight > 0) {
|
|
56
|
-
this.print(`\x1b[${this.lastRenderHeight}A`);
|
|
57
|
-
}
|
|
52
|
+
render(firstRender) {
|
|
58
53
|
let output = '';
|
|
59
54
|
const choices = this.getFilteredChoices();
|
|
60
55
|
// Adjust Scroll Top
|
|
@@ -70,44 +65,35 @@ class SelectPrompt extends base_1.Prompt {
|
|
|
70
65
|
}
|
|
71
66
|
// Header
|
|
72
67
|
const searchStr = this.searchBuffer ? ` ${theme_1.theme.muted}(Filter: ${this.searchBuffer})${ansi_1.ANSI.RESET}` : '';
|
|
73
|
-
|
|
68
|
+
// Note: We avoid ERASE_LINE here because renderFrame handles full redraw
|
|
69
|
+
output += `${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
70
|
if (choices.length === 0) {
|
|
75
|
-
output +=
|
|
71
|
+
output += ` ${theme_1.theme.muted}No results found${ansi_1.ANSI.RESET}`;
|
|
72
|
+
// We can omit newline at the very end if we want, but usually it's better to be consistent.
|
|
73
|
+
// renderFrame adds newline via truncate logic? No, it joins.
|
|
74
|
+
// So if I want a line break, I must add it.
|
|
76
75
|
}
|
|
77
76
|
else {
|
|
78
77
|
const visibleChoices = choices.slice(this.scrollTop, this.scrollTop + this.pageSize);
|
|
79
78
|
visibleChoices.forEach((choice, index) => {
|
|
80
79
|
const actualIndex = this.scrollTop + index;
|
|
81
|
-
|
|
80
|
+
if (index > 0)
|
|
81
|
+
output += '\n'; // Separator between items
|
|
82
82
|
if (this.isSeparator(choice)) {
|
|
83
|
-
output += ` ${ansi_1.ANSI.DIM}${choice.text || '────────'}${ansi_1.ANSI.RESET}
|
|
83
|
+
output += ` ${ansi_1.ANSI.DIM}${choice.text || '────────'}${ansi_1.ANSI.RESET}`;
|
|
84
84
|
}
|
|
85
85
|
else {
|
|
86
86
|
if (actualIndex === this.selectedIndex) {
|
|
87
|
-
output += `${theme_1.theme.main}❯ ${choice.title}${ansi_1.ANSI.RESET}
|
|
87
|
+
output += `${theme_1.theme.main}❯ ${choice.title}${ansi_1.ANSI.RESET}`;
|
|
88
88
|
}
|
|
89
89
|
else {
|
|
90
|
-
output += ` ${choice.title}
|
|
90
|
+
output += ` ${choice.title}`;
|
|
91
91
|
}
|
|
92
92
|
}
|
|
93
93
|
});
|
|
94
94
|
}
|
|
95
|
-
|
|
96
|
-
|
|
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);
|
|
95
|
+
// No manual printing. Pass to renderFrame.
|
|
96
|
+
this.renderFrame(output);
|
|
111
97
|
}
|
|
112
98
|
handleInput(char) {
|
|
113
99
|
const choices = this.getFilteredChoices();
|
|
@@ -121,7 +107,7 @@ class SelectPrompt extends base_1.Prompt {
|
|
|
121
107
|
if (this.isSeparator(choices[this.selectedIndex]))
|
|
122
108
|
return;
|
|
123
109
|
this.cleanup();
|
|
124
|
-
|
|
110
|
+
// Cursor is shown by cleanup
|
|
125
111
|
if (this._resolve)
|
|
126
112
|
this._resolve(choices[this.selectedIndex].value);
|
|
127
113
|
return;
|
package/dist/prompts/slider.js
CHANGED
|
@@ -11,10 +11,6 @@ class SliderPrompt extends base_1.Prompt {
|
|
|
11
11
|
this.value = options.initial ?? options.min;
|
|
12
12
|
}
|
|
13
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
14
|
const width = 20;
|
|
19
15
|
const range = this.options.max - this.options.min;
|
|
20
16
|
const ratio = (this.value - this.options.min) / range;
|
|
@@ -27,7 +23,8 @@ class SliderPrompt extends base_1.Prompt {
|
|
|
27
23
|
bar += '─';
|
|
28
24
|
}
|
|
29
25
|
const unit = this.options.unit || '';
|
|
30
|
-
|
|
26
|
+
const output = `${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}`;
|
|
27
|
+
this.renderFrame(output);
|
|
31
28
|
}
|
|
32
29
|
handleInput(char) {
|
|
33
30
|
if (char === '\r' || char === '\n') {
|
|
@@ -37,10 +34,14 @@ class SliderPrompt extends base_1.Prompt {
|
|
|
37
34
|
const step = this.options.step || 1;
|
|
38
35
|
if (this.isLeft(char)) { // Left
|
|
39
36
|
this.value = Math.max(this.options.min, this.value - step);
|
|
37
|
+
// Round to avoid float errors
|
|
38
|
+
this.value = Math.round(this.value * 10000) / 10000;
|
|
40
39
|
this.render(false);
|
|
41
40
|
}
|
|
42
41
|
if (this.isRight(char)) { // Right
|
|
43
42
|
this.value = Math.min(this.options.max, this.value + step);
|
|
43
|
+
// Round to avoid float errors
|
|
44
|
+
this.value = Math.round(this.value * 10000) / 10000;
|
|
44
45
|
this.render(false);
|
|
45
46
|
}
|
|
46
47
|
}
|
package/dist/prompts/text.d.ts
CHANGED
|
@@ -4,9 +4,10 @@ export declare class TextPrompt extends Prompt<string, TextOptions> {
|
|
|
4
4
|
private errorMsg;
|
|
5
5
|
private cursor;
|
|
6
6
|
private hasTyped;
|
|
7
|
-
private
|
|
7
|
+
private segments;
|
|
8
8
|
constructor(options: TextOptions);
|
|
9
9
|
protected render(firstRender: boolean): void;
|
|
10
|
+
private getSegmentWidth;
|
|
10
11
|
protected handleInput(char: string): void;
|
|
11
12
|
private validateAndSubmit;
|
|
12
13
|
}
|