mepcli 1.0.0-beta.5 → 1.0.0-rc.1

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/LICENSE CHANGED
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2025 CodeTease
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
1
+ MIT License
2
+
3
+ Copyright (c) 2025 CodeTease
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -14,7 +14,7 @@ A **CodeTease** project.
14
14
  ## Features
15
15
 
16
16
  - **Zero Dependency:** Keeps your project clean and fast.
17
- - **Comprehensive:** 60+ prompt types for every need.
17
+ - **Comprehensive:** 70+ prompt types for every need.
18
18
  - **Mouse Support:** Built-in scroll and click interaction.
19
19
  - **Responsive:** Fluid cursor movement and validation.
20
20
  - **Elegant:** Modern ANSI color styling.
@@ -85,9 +85,11 @@ Powerful tools for selecting dates, files, colors, and more.
85
85
  | `checkbox` | Multiple choice selection. |
86
86
  | `multiSelect` | Multiple selection with filtering. |
87
87
  | `multiColumnSelect` | Selection with grid layout. |
88
+ | `fuzzyMultiColumn` | Grid layout + Fuzzy search combination. |
88
89
  | `fuzzySelect` | Selection with fuzzy search. |
89
90
  | `autocomplete` | Async searchable selection. |
90
91
  | `selectRange` | Select a continuous range (start-end). |
92
+ | `multiRange` | Select multiple discontinuous ranges. |
91
93
  | `treeSelect` | Hierarchical multi-selection. |
92
94
  | `grid` | 2D matrix selection (rows x columns). |
93
95
  | `seat` | Seat selection map with gaps. |
@@ -98,6 +100,7 @@ Powerful tools for selecting dates, files, colors, and more.
98
100
  | `time` | Time picker. |
99
101
  | `file` | File system navigator. |
100
102
  | `breadcrumb` | Breadcrumb navigation style. |
103
+ | `breadcrumbSearch` | Breadcrumb navigation with local fuzzy search. |
101
104
  | `miller` | Miller columns navigation. |
102
105
  | `tree` | Hierarchical tree navigation. |
103
106
 
@@ -501,12 +504,23 @@ The Grid prompt (Matrix selection) includes robust shortcuts for bulk actions.
501
504
  </details>
502
505
 
503
506
  <details>
504
- <summary><b>Select Range Prompt</b></summary>
507
+ <summary><b>Select Range Prompt & Multi Range Prompt</b></summary>
505
508
 
506
509
  * **Keyboard:**
507
510
  * `Arrows (Up/Down)`: Navigate items.
508
- * `Space`: Set/Unset anchor point.
509
- * `Enter`: Submit selected range.
511
+ * `Space`: Set/Unset anchor point (drag start) or commit range (drag end).
512
+ * `Enter`: Submit selected range(s).
513
+
514
+ </details>
515
+
516
+ <details>
517
+ <summary><b>Breadcrumb Search Prompt</b></summary>
518
+
519
+ * **Keyboard:**
520
+ * `Arrows`: Navigate.
521
+ * `Typing`: Enter **Search Mode** (filters current folder).
522
+ * `Esc`: Exit Search Mode.
523
+ * `Enter`: Drill down (Folder) or Select (File).
510
524
 
511
525
  </details>
512
526
 
package/dist/core.d.ts CHANGED
@@ -109,4 +109,7 @@ export declare class MepCLI {
109
109
  static connectionString(options: ConnectionStringOptions): Promise<ConnectionStringResult>;
110
110
  static curl(options: CurlOptions): Promise<CurlResult>;
111
111
  static phone(options: PhoneOptions): Promise<string>;
112
+ static fuzzyMultiColumn<V>(options: MultiColumnSelectOptions<V>): Promise<V>;
113
+ static multiRange<V>(options: SelectRangeOptions<V>): Promise<V[]>;
114
+ static breadcrumbSearch(options: BreadcrumbOptions): Promise<string>;
112
115
  }
package/dist/core.js CHANGED
@@ -71,6 +71,9 @@ const license_1 = require("./prompts/license");
71
71
  const regex_1 = require("./prompts/regex");
72
72
  const box_1 = require("./prompts/box");
73
73
  const phone_1 = require("./prompts/phone");
74
+ const fuzzy_multi_column_1 = require("./prompts/fuzzy-multi-column");
75
+ const multi_range_1 = require("./prompts/multi-range");
76
+ const breadcrumb_search_1 = require("./prompts/breadcrumb-search");
74
77
  const connection_string_1 = require("./prompts/connection-string");
75
78
  const curl_1 = require("./prompts/curl");
76
79
  const pipeline_1 = require("./pipeline");
@@ -322,6 +325,15 @@ class MepCLI {
322
325
  static phone(options) {
323
326
  return new phone_1.PhonePrompt(options).run();
324
327
  }
328
+ static fuzzyMultiColumn(options) {
329
+ return new fuzzy_multi_column_1.FuzzyMultiColumnPrompt(options).run();
330
+ }
331
+ static multiRange(options) {
332
+ return new multi_range_1.MultiRangePrompt(options).run();
333
+ }
334
+ static breadcrumbSearch(options) {
335
+ return new breadcrumb_search_1.BreadcrumbSearchPrompt(options).run();
336
+ }
325
337
  }
326
338
  exports.MepCLI = MepCLI;
327
339
  MepCLI.theme = theme_1.theme;
package/dist/index.d.ts CHANGED
@@ -41,5 +41,8 @@ export * from './prompts/box';
41
41
  export * from './prompts/connection-string';
42
42
  export * from './prompts/curl';
43
43
  export * from './prompts/phone';
44
+ export * from './prompts/fuzzy-multi-column';
45
+ export * from './prompts/multi-range';
46
+ export * from './prompts/breadcrumb-search';
44
47
  export * from './pipeline';
45
48
  export * from './tasks';
package/dist/index.js CHANGED
@@ -57,5 +57,8 @@ __exportStar(require("./prompts/box"), exports);
57
57
  __exportStar(require("./prompts/connection-string"), exports);
58
58
  __exportStar(require("./prompts/curl"), exports);
59
59
  __exportStar(require("./prompts/phone"), exports);
60
+ __exportStar(require("./prompts/fuzzy-multi-column"), exports);
61
+ __exportStar(require("./prompts/multi-range"), exports);
62
+ __exportStar(require("./prompts/breadcrumb-search"), exports);
60
63
  __exportStar(require("./pipeline"), exports);
61
64
  __exportStar(require("./tasks"), exports);
@@ -0,0 +1,13 @@
1
+ import { BreadcrumbPrompt } from './breadcrumb';
2
+ import { BreadcrumbOptions } from '../types';
3
+ export declare class BreadcrumbSearchPrompt extends BreadcrumbPrompt {
4
+ private isSearchMode;
5
+ private searchBuffer;
6
+ private filteredEntries;
7
+ private searchCursor;
8
+ constructor(options: BreadcrumbOptions);
9
+ protected handleInput(char: string, key: Buffer): void;
10
+ private updateSearchResults;
11
+ protected render(firstRender: boolean): void;
12
+ private highlight;
13
+ }
@@ -0,0 +1,226 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.BreadcrumbSearchPrompt = void 0;
37
+ const breadcrumb_1 = require("./breadcrumb");
38
+ const theme_1 = require("../theme");
39
+ const ansi_1 = require("../ansi");
40
+ const symbols_1 = require("../symbols");
41
+ const utils_1 = require("../utils");
42
+ const path = __importStar(require("path"));
43
+ class BreadcrumbSearchPrompt extends breadcrumb_1.BreadcrumbPrompt {
44
+ constructor(options) {
45
+ super(options);
46
+ this.isSearchMode = false;
47
+ this.searchBuffer = '';
48
+ this.filteredEntries = [];
49
+ this.searchCursor = 0;
50
+ }
51
+ handleInput(char, key) {
52
+ if (this.isLoading)
53
+ return;
54
+ // Toggle Search Mode or Type
55
+ if (!this.isSearchMode) {
56
+ // If typing a regular char, enter search mode
57
+ if (char.length === 1 && !/^[\x00-\x1F]/.test(char) && !char.startsWith('\x1b')) {
58
+ this.isSearchMode = true;
59
+ this.searchBuffer = char;
60
+ this.updateSearchResults();
61
+ this.render(false);
62
+ return;
63
+ }
64
+ // Otherwise use default navigation
65
+ super.handleInput(char, key);
66
+ return;
67
+ }
68
+ // --- In Search Mode ---
69
+ // Esc: Exit search mode
70
+ if (char === '\x1b') {
71
+ this.isSearchMode = false;
72
+ this.searchBuffer = '';
73
+ this.render(false);
74
+ return;
75
+ }
76
+ // Backspace
77
+ if (char === '\u0008' || char === '\x7f') {
78
+ if (this.searchBuffer.length > 0) {
79
+ this.searchBuffer = this.searchBuffer.slice(0, -1);
80
+ if (this.searchBuffer.length === 0) {
81
+ this.isSearchMode = false;
82
+ }
83
+ else {
84
+ this.updateSearchResults();
85
+ }
86
+ this.render(false);
87
+ }
88
+ else {
89
+ this.isSearchMode = false;
90
+ this.render(false);
91
+ }
92
+ return;
93
+ }
94
+ // Enter
95
+ if (char === '\r' || char === '\n') {
96
+ if (this.filteredEntries.length === 0)
97
+ return;
98
+ const entry = this.filteredEntries[this.searchCursor];
99
+ if (entry.isDirectory) {
100
+ // Drill down
101
+ // Hack: Find index in currentEntries
102
+ const realIndex = this.currentEntries.findIndex(e => e.name === entry.name);
103
+ if (realIndex !== -1) {
104
+ this.cursor = realIndex;
105
+ this.drillDown().then(() => {
106
+ this.isSearchMode = false;
107
+ this.searchBuffer = '';
108
+ });
109
+ }
110
+ }
111
+ else {
112
+ const fullPath = path.join(this.currentPath, entry.name);
113
+ this.submit(fullPath);
114
+ }
115
+ return;
116
+ }
117
+ // Navigation (Up/Down)
118
+ if (this.isUp(char)) {
119
+ if (this.searchCursor > 0) {
120
+ this.searchCursor--;
121
+ }
122
+ else {
123
+ this.searchCursor = Math.max(0, this.filteredEntries.length - 1);
124
+ }
125
+ this.render(false);
126
+ return;
127
+ }
128
+ if (this.isDown(char)) {
129
+ if (this.searchCursor < this.filteredEntries.length - 1) {
130
+ this.searchCursor++;
131
+ }
132
+ else {
133
+ this.searchCursor = 0;
134
+ }
135
+ this.render(false);
136
+ return;
137
+ }
138
+ // Typing
139
+ if (char.length === 1 && !/^[\x00-\x1F]/.test(char) && !char.startsWith('\x1b')) {
140
+ this.searchBuffer += char;
141
+ this.updateSearchResults();
142
+ this.render(false);
143
+ }
144
+ }
145
+ updateSearchResults() {
146
+ if (!this.searchBuffer) {
147
+ this.filteredEntries = [];
148
+ return;
149
+ }
150
+ const results = this.currentEntries.map(entry => {
151
+ const match = (0, utils_1.fuzzyMatch)(this.searchBuffer, entry.name);
152
+ return { entry, match };
153
+ }).filter(item => item.match !== null)
154
+ .sort((a, b) => b.match.score - a.match.score);
155
+ this.filteredEntries = results.map(r => ({
156
+ ...r.entry,
157
+ _match: r.match
158
+ }));
159
+ this.searchCursor = 0;
160
+ }
161
+ render(firstRender) {
162
+ if (!this.isSearchMode) {
163
+ super.render(firstRender);
164
+ return;
165
+ }
166
+ // Render Search Overlay
167
+ let output = '';
168
+ // Breadcrumb + Search Bar
169
+ const relative = path.relative(this.root, this.currentPath);
170
+ // ... (Truncation logic omitted for brevity, simplified version)
171
+ const pathStr = relative || path.basename(this.root);
172
+ const prefix = `${theme_1.theme.success}?${ansi_1.ANSI.RESET} ${theme_1.theme.title}${this.options.message}${ansi_1.ANSI.RESET} `;
173
+ output += `${prefix}${theme_1.theme.muted}${pathStr}/${ansi_1.ANSI.RESET} ${ansi_1.ANSI.FG_YELLOW}(Search: ${this.searchBuffer})${ansi_1.ANSI.RESET}\n`;
174
+ if (this.filteredEntries.length === 0) {
175
+ output += ` ${theme_1.theme.muted}No results found${ansi_1.ANSI.RESET}`;
176
+ }
177
+ else {
178
+ const pageSize = this.pageSize;
179
+ let start = 0;
180
+ const half = Math.floor(pageSize / 2);
181
+ start = Math.max(0, this.searchCursor - half);
182
+ if (start + pageSize > this.filteredEntries.length) {
183
+ start = Math.max(0, this.filteredEntries.length - pageSize);
184
+ }
185
+ const visible = this.filteredEntries.slice(start, start + pageSize);
186
+ visible.forEach((entry, index) => {
187
+ const actualIndex = start + index;
188
+ const isSelected = actualIndex === this.searchCursor;
189
+ const icon = entry.isDirectory ? '📂' : '📄';
190
+ // Highlight match
191
+ const title = this.highlight(entry.name, entry._match.indices, isSelected);
192
+ let line = '';
193
+ if (isSelected) {
194
+ line += `${theme_1.theme.main}${symbols_1.symbols.pointer} ${icon} ${title}${ansi_1.ANSI.RESET}`;
195
+ }
196
+ else {
197
+ line += ` ${icon} ${title}`;
198
+ }
199
+ if (index > 0 || index === 0)
200
+ output += line;
201
+ if (index < visible.length - 1)
202
+ output += '\n';
203
+ });
204
+ }
205
+ this.renderFrame(output);
206
+ }
207
+ highlight(text, indices, isSelected) {
208
+ let output = '';
209
+ const indexSet = new Set(indices);
210
+ for (let i = 0; i < text.length; i++) {
211
+ if (indexSet.has(i)) {
212
+ if (isSelected) {
213
+ output += `${ansi_1.ANSI.BOLD}${ansi_1.ANSI.FG_WHITE}${text[i]}${theme_1.theme.main}`;
214
+ }
215
+ else {
216
+ output += `${ansi_1.ANSI.BOLD}${ansi_1.ANSI.FG_CYAN}${text[i]}${ansi_1.ANSI.RESET}`;
217
+ }
218
+ }
219
+ else {
220
+ output += text[i];
221
+ }
222
+ }
223
+ return output;
224
+ }
225
+ }
226
+ exports.BreadcrumbSearchPrompt = BreadcrumbSearchPrompt;
@@ -1,22 +1,32 @@
1
1
  import { Prompt } from '../base';
2
2
  import { BreadcrumbOptions, MouseEvent } from '../types';
3
+ interface StackItem {
4
+ path: string;
5
+ cursor: number;
6
+ scrollTop: number;
7
+ }
8
+ interface DirEntry {
9
+ name: string;
10
+ isDirectory: boolean;
11
+ }
3
12
  export declare class BreadcrumbPrompt extends Prompt<string, BreadcrumbOptions> {
4
- private stack;
5
- private currentEntries;
6
- private cursor;
7
- private scrollTop;
8
- private root;
9
- private currentPath;
10
- private separator;
11
- private showHidden;
12
- private isLoading;
13
- private error;
14
- private readonly pageSize;
13
+ protected stack: StackItem[];
14
+ protected currentEntries: DirEntry[];
15
+ protected cursor: number;
16
+ protected scrollTop: number;
17
+ protected root: string;
18
+ protected currentPath: string;
19
+ protected separator: string;
20
+ protected showHidden: boolean;
21
+ protected isLoading: boolean;
22
+ protected error: string | null;
23
+ protected readonly pageSize: number;
15
24
  constructor(options: BreadcrumbOptions);
16
- private loadDirectory;
17
- private drillDown;
18
- private goUp;
25
+ protected loadDirectory(dir: string): Promise<void>;
26
+ protected drillDown(): Promise<void>;
27
+ protected goUp(): Promise<void>;
19
28
  protected render(_firstRender: boolean): void;
20
29
  protected handleInput(char: string, _key: Buffer): void;
21
30
  protected handleMouse(_event: MouseEvent): void;
22
31
  }
32
+ export {};
@@ -0,0 +1,12 @@
1
+ import { MultiColumnSelectPrompt } from './multi-column-select';
2
+ import { MultiColumnSelectOptions } from '../types';
3
+ export declare class FuzzyMultiColumnPrompt<V> extends MultiColumnSelectPrompt<V> {
4
+ private filteredResults;
5
+ private debounceTimer;
6
+ constructor(options: MultiColumnSelectOptions<V>);
7
+ protected getFilteredChoices(): any[];
8
+ protected handleInput(char: string): void;
9
+ private performSearch;
10
+ private highlight;
11
+ protected render(_firstRender: boolean): void;
12
+ }
@@ -0,0 +1,149 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FuzzyMultiColumnPrompt = void 0;
4
+ const multi_column_select_1 = require("./multi-column-select");
5
+ const theme_1 = require("../theme");
6
+ const ansi_1 = require("../ansi");
7
+ const utils_1 = require("../utils");
8
+ const symbols_1 = require("../symbols");
9
+ class FuzzyMultiColumnPrompt extends multi_column_select_1.MultiColumnSelectPrompt {
10
+ constructor(options) {
11
+ super(options);
12
+ this.filteredResults = [];
13
+ this.filteredResults = this.options.choices;
14
+ }
15
+ getFilteredChoices() {
16
+ return this.filteredResults || this.options.choices;
17
+ }
18
+ handleInput(char) {
19
+ // Backspace
20
+ if (char === '\u0008' || char === '\x7f') {
21
+ if (this.searchBuffer.length > 0) {
22
+ this.searchBuffer = this.searchBuffer.slice(0, -1);
23
+ this.performSearch();
24
+ }
25
+ return;
26
+ }
27
+ // Intercept typing to add debounce
28
+ if (char.length === 1 && !/^[\x00-\x1F]/.test(char) && !char.startsWith('\x1b')) {
29
+ this.searchBuffer += char;
30
+ // Check if debounce is needed
31
+ if (this.options.choices.length > 1000) {
32
+ if (this.debounceTimer)
33
+ clearTimeout(this.debounceTimer);
34
+ this.debounceTimer = setTimeout(() => {
35
+ this.performSearch();
36
+ }, 150); // 150ms debounce
37
+ }
38
+ else {
39
+ this.performSearch();
40
+ }
41
+ return;
42
+ }
43
+ super.handleInput(char);
44
+ }
45
+ performSearch() {
46
+ if (!this.searchBuffer) {
47
+ this.filteredResults = this.options.choices;
48
+ }
49
+ else {
50
+ const results = this.options.choices.map(c => {
51
+ if (this.isSeparator(c))
52
+ return null;
53
+ const match = (0, utils_1.fuzzyMatch)(this.searchBuffer, c.title);
54
+ return { choice: c, match };
55
+ }).filter(item => item && item.match !== null)
56
+ // Sort by score descending
57
+ .sort((a, b) => b.match.score - a.match.score);
58
+ this.filteredResults = results.map(r => {
59
+ r.choice._match = r.match;
60
+ return r.choice;
61
+ });
62
+ }
63
+ this.selectedIndex = 0;
64
+ this.render(false);
65
+ }
66
+ highlight(text, indices, isSelected) {
67
+ let output = '';
68
+ const indexSet = new Set(indices);
69
+ for (let i = 0; i < text.length; i++) {
70
+ if (indexSet.has(i)) {
71
+ if (isSelected) {
72
+ output += `${ansi_1.ANSI.BOLD}${ansi_1.ANSI.FG_WHITE}${text[i]}${theme_1.theme.main}`; // Reset to main theme
73
+ }
74
+ else {
75
+ output += `${ansi_1.ANSI.BOLD}${ansi_1.ANSI.FG_CYAN}${text[i]}${ansi_1.ANSI.RESET}`;
76
+ }
77
+ }
78
+ else {
79
+ output += text[i];
80
+ }
81
+ }
82
+ return output;
83
+ }
84
+ render(_firstRender) {
85
+ let output = '';
86
+ const choices = this.getFilteredChoices();
87
+ // Header
88
+ const searchStr = this.searchBuffer ? ` ${theme_1.theme.muted}(Fuzzy: ${this.searchBuffer})${ansi_1.ANSI.RESET}` : '';
89
+ 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`;
90
+ if (choices.length === 0) {
91
+ output += ` ${theme_1.theme.muted}No results found${ansi_1.ANSI.RESET}`;
92
+ this.renderFrame(output);
93
+ return;
94
+ }
95
+ // Grid Render
96
+ const totalRows = Math.ceil(choices.length / this.cols);
97
+ // Adjust Scroll
98
+ const currentRow = Math.floor(this.selectedIndex / this.cols);
99
+ if (currentRow < this.scrollTop) {
100
+ this.scrollTop = currentRow;
101
+ }
102
+ else if (currentRow >= this.scrollTop + this.pageSize) {
103
+ this.scrollTop = currentRow - this.pageSize + 1;
104
+ }
105
+ // Edge case: if list shrinks
106
+ if (this.scrollTop > totalRows - 1) {
107
+ this.scrollTop = Math.max(0, totalRows - this.pageSize);
108
+ }
109
+ const startRow = this.scrollTop;
110
+ const endRow = Math.min(totalRows, startRow + this.pageSize);
111
+ for (let r = startRow; r < endRow; r++) {
112
+ let rowStr = '';
113
+ for (let c = 0; c < this.cols; c++) {
114
+ const idx = r * this.cols + c;
115
+ if (idx >= choices.length)
116
+ break;
117
+ const choice = choices[idx];
118
+ // Truncate if needed
119
+ let title = choice.title || '';
120
+ if ((0, utils_1.stringWidth)(title) > this.colWidth - 3) {
121
+ title = title.slice(0, this.colWidth - 4) + '…';
122
+ }
123
+ const match = choice._match;
124
+ const isSelected = idx === this.selectedIndex;
125
+ if (match && this.searchBuffer) {
126
+ title = this.highlight(title, match.indices, isSelected);
127
+ }
128
+ let cellContent = '';
129
+ if (isSelected) {
130
+ cellContent = `${theme_1.theme.main}${symbols_1.symbols.pointer} ${title}${ansi_1.ANSI.RESET}`;
131
+ }
132
+ else {
133
+ cellContent = ` ${title}`;
134
+ }
135
+ // Pad cell to colWidth
136
+ const currentWidth = (0, utils_1.stringWidth)(this.stripAnsi(cellContent));
137
+ const padding = Math.max(0, this.colWidth - currentWidth);
138
+ // Add cell to row
139
+ rowStr += cellContent + ' '.repeat(padding);
140
+ }
141
+ output += rowStr + '\n';
142
+ }
143
+ // Remove trailing newline
144
+ if (output.endsWith('\n'))
145
+ output = output.slice(0, -1);
146
+ this.renderFrame(output);
147
+ }
148
+ }
149
+ exports.FuzzyMultiColumnPrompt = FuzzyMultiColumnPrompt;
@@ -1,8 +1,8 @@
1
1
  import { SelectPrompt } from './select';
2
2
  import { MultiColumnSelectOptions } from '../types';
3
3
  export declare class MultiColumnSelectPrompt<V> extends SelectPrompt<V, MultiColumnSelectOptions<V>> {
4
- private cols;
5
- private colWidth;
4
+ protected cols: number;
5
+ protected colWidth: number;
6
6
  constructor(options: MultiColumnSelectOptions<V>);
7
7
  private calculateLayout;
8
8
  protected render(_firstRender: boolean): void;
@@ -0,0 +1,9 @@
1
+ import { SelectRangePrompt } from './select-range';
2
+ import { SelectRangeOptions } from '../types';
3
+ export declare class MultiRangePrompt<V> extends SelectRangePrompt<V> {
4
+ private committedRanges;
5
+ constructor(options: SelectRangeOptions<V>);
6
+ private mergeRanges;
7
+ protected handleInput(char: string, key?: Buffer): void;
8
+ protected render(_firstRender: boolean): void;
9
+ }
@@ -0,0 +1,171 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MultiRangePrompt = void 0;
4
+ const select_range_1 = require("./select-range");
5
+ const theme_1 = require("../theme");
6
+ const ansi_1 = require("../ansi");
7
+ const symbols_1 = require("../symbols");
8
+ class MultiRangePrompt extends select_range_1.SelectRangePrompt {
9
+ constructor(options) {
10
+ super(options);
11
+ this.committedRanges = [];
12
+ }
13
+ mergeRanges() {
14
+ if (this.committedRanges.length === 0)
15
+ return;
16
+ // Sort by start index
17
+ this.committedRanges.sort((a, b) => a.start - b.start);
18
+ const merged = [];
19
+ let current = this.committedRanges[0];
20
+ for (let i = 1; i < this.committedRanges.length; i++) {
21
+ const next = this.committedRanges[i];
22
+ if (current.end >= next.start - 1) { // Overlap or adjacent
23
+ current.end = Math.max(current.end, next.end);
24
+ }
25
+ else {
26
+ merged.push(current);
27
+ current = next;
28
+ }
29
+ }
30
+ merged.push(current);
31
+ this.committedRanges = merged;
32
+ }
33
+ handleInput(char, key) {
34
+ // Handle Enter (Submit)
35
+ if (char === '\r' || char === '\n') {
36
+ const choices = this.getFilteredChoices();
37
+ if (choices.length === 0)
38
+ return;
39
+ // If user was dragging, commit that range first
40
+ if (this.anchorIndex !== null) {
41
+ const start = Math.min(this.anchorIndex, this.selectedIndex);
42
+ const end = Math.max(this.anchorIndex, this.selectedIndex);
43
+ this.committedRanges.push({ start, end });
44
+ this.mergeRanges();
45
+ this.anchorIndex = null;
46
+ }
47
+ // Collect all indices from committed ranges
48
+ const allIndices = new Set();
49
+ for (const range of this.committedRanges) {
50
+ for (let i = range.start; i <= range.end; i++) {
51
+ allIndices.add(i);
52
+ }
53
+ }
54
+ if (allIndices.size === 0) {
55
+ allIndices.add(this.selectedIndex);
56
+ }
57
+ const selectedItems = [];
58
+ const sortedIndices = Array.from(allIndices).sort((a, b) => a - b);
59
+ for (const idx of sortedIndices) {
60
+ if (idx < choices.length) {
61
+ const choice = choices[idx];
62
+ if (!this.isSeparator(choice)) {
63
+ selectedItems.push(choice.value);
64
+ }
65
+ }
66
+ }
67
+ this.submit(selectedItems);
68
+ return;
69
+ }
70
+ // Handle Space (Anchor/Commit)
71
+ if (char === ' ') {
72
+ if (this.anchorIndex === null) {
73
+ // Start dragging
74
+ this.anchorIndex = this.selectedIndex;
75
+ }
76
+ else {
77
+ // Commit range
78
+ const start = Math.min(this.anchorIndex, this.selectedIndex);
79
+ const end = Math.max(this.anchorIndex, this.selectedIndex);
80
+ this.committedRanges.push({ start, end });
81
+ this.mergeRanges();
82
+ this.anchorIndex = null;
83
+ }
84
+ this.render(false);
85
+ return;
86
+ }
87
+ super.handleInput(char, key);
88
+ }
89
+ render(_firstRender) {
90
+ let output = '';
91
+ const choices = this.getFilteredChoices();
92
+ // Scroll Logic (inherited from SelectPrompt basically, but good to ensure)
93
+ if (this.selectedIndex < this.scrollTop) {
94
+ this.scrollTop = this.selectedIndex;
95
+ }
96
+ else if (this.selectedIndex >= this.scrollTop + this.pageSize) {
97
+ this.scrollTop = this.selectedIndex - this.pageSize + 1;
98
+ }
99
+ if (this.scrollTop > choices.length - 1) {
100
+ this.scrollTop = Math.max(0, choices.length - this.pageSize);
101
+ }
102
+ // Header
103
+ const searchStr = this.searchBuffer ? ` ${theme_1.theme.muted}(Filter: ${this.searchBuffer})${ansi_1.ANSI.RESET}` : '';
104
+ 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`;
105
+ if (choices.length === 0) {
106
+ output += ` ${theme_1.theme.muted}No results found${ansi_1.ANSI.RESET}`;
107
+ }
108
+ else {
109
+ const visibleChoices = choices.slice(this.scrollTop, this.scrollTop + this.pageSize);
110
+ visibleChoices.forEach((choice, index) => {
111
+ const actualIndex = this.scrollTop + index;
112
+ if (index > 0)
113
+ output += '\n';
114
+ let isCommitted = false;
115
+ for (const range of this.committedRanges) {
116
+ if (actualIndex >= range.start && actualIndex <= range.end) {
117
+ isCommitted = true;
118
+ break;
119
+ }
120
+ }
121
+ let isDragging = false;
122
+ if (this.anchorIndex !== null) {
123
+ const min = Math.min(this.anchorIndex, this.selectedIndex);
124
+ const max = Math.max(this.anchorIndex, this.selectedIndex);
125
+ if (actualIndex >= min && actualIndex <= max) {
126
+ isDragging = true;
127
+ }
128
+ }
129
+ if (this.isSeparator(choice)) {
130
+ output += ` ${ansi_1.ANSI.DIM}${choice.text || symbols_1.symbols.line.repeat(8)}${ansi_1.ANSI.RESET}`;
131
+ }
132
+ else {
133
+ let prefix = ' ';
134
+ const title = choice.title;
135
+ let content = title;
136
+ // Markers
137
+ if (actualIndex === this.anchorIndex) {
138
+ prefix = `${theme_1.theme.muted}> ${ansi_1.ANSI.RESET}`;
139
+ }
140
+ if (actualIndex === this.selectedIndex) {
141
+ prefix = `${theme_1.theme.main}${symbols_1.symbols.pointer} `;
142
+ }
143
+ if (actualIndex === this.selectedIndex && actualIndex === this.anchorIndex) {
144
+ prefix = `${theme_1.theme.main}${symbols_1.symbols.pointer}>`;
145
+ }
146
+ // Highlighting
147
+ if (isCommitted || isDragging) {
148
+ // Apply highlighting style
149
+ // If committed, maybe green? If dragging, maybe yellow or just highlighted?
150
+ if (actualIndex !== this.selectedIndex && actualIndex !== this.anchorIndex) {
151
+ prefix = `${theme_1.theme.success}* ${ansi_1.ANSI.RESET}`;
152
+ }
153
+ content = `${theme_1.theme.success}${title}${ansi_1.ANSI.RESET}`;
154
+ if (isDragging && !isCommitted) {
155
+ // Distinguish dragging visual? Maybe dim?
156
+ // Using same style as single range prompt for consistency
157
+ }
158
+ }
159
+ // Cursor Underline
160
+ if (actualIndex === this.selectedIndex) {
161
+ content = `${ansi_1.ANSI.UNDERLINE}${content}${ansi_1.ANSI.RESET}`;
162
+ }
163
+ output += `${prefix}${content}`;
164
+ }
165
+ });
166
+ }
167
+ output += `\n${theme_1.theme.muted}(Space to anchor/commit, Enter to submit)${ansi_1.ANSI.RESET}`;
168
+ this.renderFrame(output);
169
+ }
170
+ }
171
+ exports.MultiRangePrompt = MultiRangePrompt;
@@ -49,8 +49,6 @@ class PhonePrompt extends base_1.Prompt {
49
49
  if (mask[i] === '#') {
50
50
  return i;
51
51
  }
52
- else {
53
- }
54
52
  }
55
53
  if (mask[i] === '#') {
56
54
  rawCounter++;
@@ -164,7 +162,7 @@ class PhonePrompt extends base_1.Prompt {
164
162
  }
165
163
  // Input Section
166
164
  const formatted = this.renderFormattedNumber();
167
- let inputRender = formatted;
165
+ const inputRender = formatted;
168
166
  // Layout: [Prefix] [Input]
169
167
  const line = `${prefixRender} ${inputRender}`;
170
168
  let output = title + line;
@@ -255,8 +253,6 @@ class PhonePrompt extends base_1.Prompt {
255
253
  this.activeSection = 'country';
256
254
  }
257
255
  }
258
- else {
259
- }
260
256
  this.render(false);
261
257
  return;
262
258
  }
package/package.json CHANGED
@@ -1,51 +1,51 @@
1
- {
2
- "name": "mepcli",
3
- "version": "1.0.0-beta.5",
4
- "description": "Zero-dependency, interactive CLI prompt for Node.js",
5
- "repository": {
6
- "type": "git",
7
- "url": "git+https://github.com/CodeTease/mep.git"
8
- },
9
- "main": "dist/index.js",
10
- "types": "dist/index.d.ts",
11
- "files": [
12
- "dist",
13
- "README.md",
14
- "LICENSE"
15
- ],
16
- "scripts": {
17
- "build": "tsc",
18
- "prepublishOnly": "npm run build",
19
- "test": "jest",
20
- "demo": "ts-node example.ts",
21
- "lint": "eslint .",
22
- "lint:fix": "eslint . --fix"
23
- },
24
- "keywords": [
25
- "cli",
26
- "prompt",
27
- "inquirer",
28
- "enquirer",
29
- "interactive",
30
- "zero-dependency",
31
- "codetease",
32
- "command-line",
33
- "typescript",
34
- "color",
35
- "ansi"
36
- ],
37
- "author": "CodeTease",
38
- "license": "MIT",
39
- "devDependencies": {
40
- "@eslint/js": "^9",
41
- "@types/jest": "^30",
42
- "@types/node": "^22",
43
- "eslint": "^9",
44
- "globals": "^17",
45
- "jest": "^30",
46
- "ts-jest": "^29",
47
- "ts-node": "^10",
48
- "typescript": "^5",
49
- "typescript-eslint": "^8"
50
- }
51
- }
1
+ {
2
+ "name": "mepcli",
3
+ "version": "1.0.0-rc.1",
4
+ "description": "Zero-dependency, interactive CLI prompt for Node.js",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git+https://github.com/CodeTease/mep.git"
8
+ },
9
+ "main": "dist/index.js",
10
+ "types": "dist/index.d.ts",
11
+ "files": [
12
+ "dist",
13
+ "README.md",
14
+ "LICENSE"
15
+ ],
16
+ "scripts": {
17
+ "build": "tsc",
18
+ "prepublishOnly": "npm run build",
19
+ "test": "jest",
20
+ "demo": "ts-node example.ts",
21
+ "lint": "eslint .",
22
+ "lint:fix": "eslint . --fix"
23
+ },
24
+ "keywords": [
25
+ "cli",
26
+ "prompt",
27
+ "inquirer",
28
+ "enquirer",
29
+ "interactive",
30
+ "zero-dependency",
31
+ "codetease",
32
+ "command-line",
33
+ "typescript",
34
+ "color",
35
+ "ansi"
36
+ ],
37
+ "author": "CodeTease",
38
+ "license": "MIT",
39
+ "devDependencies": {
40
+ "@eslint/js": "^9",
41
+ "@types/jest": "^30",
42
+ "@types/node": "^22",
43
+ "eslint": "^9",
44
+ "globals": "^17",
45
+ "jest": "^30",
46
+ "ts-jest": "^29",
47
+ "ts-node": "^10",
48
+ "typescript": "^5",
49
+ "typescript-eslint": "^8"
50
+ }
51
+ }