inquirerjs-checkbox-search 0.2.1 → 0.4.0
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 +43 -28
- package/dist/commonjs/index.d.ts +38 -1
- package/dist/commonjs/index.js +117 -8
- package/dist/esm/index.d.ts +38 -1
- package/dist/esm/index.js +114 -8
- package/package.json +11 -4
package/README.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
A multi-select prompt with text filtering/search capability for [inquirer.js](https://github.com/SBoudrias/Inquirer.js).
|
|
4
4
|
|
|
5
|
+
<img src="docs/img/header.gif" alt="Demo showing inquirerjs-checkbox-search in action" width="100%">
|
|
6
|
+
|
|
5
7
|
This prompt combines the functionality of `@inquirer/checkbox` and `@inquirer/search`, allowing you to:
|
|
6
8
|
|
|
7
9
|
- ✅ **Multi-select** multiple options with checkboxes
|
|
@@ -38,19 +40,19 @@ console.log('Selected:', selected);
|
|
|
38
40
|
|
|
39
41
|
### Options
|
|
40
42
|
|
|
41
|
-
| Property | Type | Required | Description
|
|
42
|
-
| -------------- | ----------------------------------------------------------------------------------- | -------- |
|
|
43
|
-
| `message` | `string` | Yes | The question to ask
|
|
44
|
-
| `choices` | `Array<Choice \| string \| Separator>` | No\* | Static list of choices
|
|
45
|
-
| `source` | `(term?: string, opt: { signal: AbortSignal }) => Promise<Array<Choice \| string>>` | No\* | Async function for dynamic choices
|
|
46
|
-
| `pageSize` | `number`
|
|
47
|
-
| `loop` | `boolean` | No | Whether to loop around when navigating (default: true)
|
|
48
|
-
| `required` | `boolean` | No | Require at least one selection (default: false)
|
|
49
|
-
| `validate` | `(selection: Array<Choice>) => boolean \| string \| Promise<string \| boolean>` | No | Custom validation function
|
|
50
|
-
| `instructions` | `string \| boolean` | No | Custom instructions text or false to hide
|
|
51
|
-
| `theme` | `Theme` | No | Custom theme configuration
|
|
52
|
-
| `default` | `Array<Value>` | No | Initially selected values
|
|
53
|
-
| `filter` | `(items: Array<Choice>, term: string) => Array<Choice>` | No | Custom filter function
|
|
43
|
+
| Property | Type | Required | Description |
|
|
44
|
+
| -------------- | ----------------------------------------------------------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
45
|
+
| `message` | `string` | Yes | The question to ask |
|
|
46
|
+
| `choices` | `Array<Choice \| string \| Separator>` | No\* | Static list of choices |
|
|
47
|
+
| `source` | `(term?: string, opt: { signal: AbortSignal }) => Promise<Array<Choice \| string>>` | No\* | Async function for dynamic choices |
|
|
48
|
+
| `pageSize` | `number \| PageSizeConfig` | No | Page size configuration. Can be a number (fixed size) or PageSizeConfig object for advanced control. If not specified, auto-sizes based on terminal height (fallback: 7) |
|
|
49
|
+
| `loop` | `boolean` | No | Whether to loop around when navigating (default: true) |
|
|
50
|
+
| `required` | `boolean` | No | Require at least one selection (default: false) |
|
|
51
|
+
| `validate` | `(selection: Array<Choice>) => boolean \| string \| Promise<string \| boolean>` | No | Custom validation function |
|
|
52
|
+
| `instructions` | `string \| boolean` | No | Custom instructions text or false to hide |
|
|
53
|
+
| `theme` | `Theme` | No | Custom theme configuration |
|
|
54
|
+
| `default` | `Array<Value>` | No | Initially selected values |
|
|
55
|
+
| `filter` | `(items: Array<Choice>, term: string) => Array<Choice>` | No | Custom filter function |
|
|
54
56
|
|
|
55
57
|
\*Either `choices` or `source` must be provided.
|
|
56
58
|
|
|
@@ -67,6 +69,33 @@ type Choice<Value = any> = {
|
|
|
67
69
|
};
|
|
68
70
|
```
|
|
69
71
|
|
|
72
|
+
### PageSize Configuration
|
|
73
|
+
|
|
74
|
+
The `pageSize` property accepts either a number (for simple fixed sizing) or a `PageSizeConfig` object for advanced control:
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
type PageSizeConfig = {
|
|
78
|
+
base?: number; // Starting page size (if not specified, auto-calculated from terminal)
|
|
79
|
+
max?: number; // Maximum page size (absolute constraint)
|
|
80
|
+
min?: number; // Minimum page size (absolute constraint, defaults to 1)
|
|
81
|
+
buffer?: number; // Fixed buffer lines to subtract from page size
|
|
82
|
+
autoBufferDescriptions?: boolean; // Auto-reserve space for descriptions
|
|
83
|
+
autoBufferCountsLineWidth?: boolean; // Consider terminal width when counting description lines
|
|
84
|
+
minBuffer?: number; // Minimum buffer lines (applied after auto/manual buffer)
|
|
85
|
+
};
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
**Buffer Calculation Process:**
|
|
89
|
+
|
|
90
|
+
1. Start with base page size (from `base` or auto-calculated)
|
|
91
|
+
2. Calculate buffer:
|
|
92
|
+
- If `autoBufferDescriptions` is true: Add lines needed for largest description
|
|
93
|
+
- Otherwise: Add `buffer` value (if specified)
|
|
94
|
+
- Ensure buffer is at least `minBuffer` (if specified)
|
|
95
|
+
3. Subtract buffer from base page size
|
|
96
|
+
4. Apply `min`/`max` constraints
|
|
97
|
+
5. Ensure final result is at least 1
|
|
98
|
+
|
|
70
99
|
### Theme Options
|
|
71
100
|
|
|
72
101
|
```typescript
|
|
@@ -102,21 +131,7 @@ type CheckboxSearchTheme = {
|
|
|
102
131
|
|
|
103
132
|
## Advanced Features
|
|
104
133
|
|
|
105
|
-
For detailed examples of advanced features, see the [`examples/`](./examples/) directory
|
|
106
|
-
|
|
107
|
-
- **[Basic Multi-Select](./examples/basic.js)** - Simple multi-select functionality
|
|
108
|
-
- **[Search Filtering](./examples/search-filtering.js)** - Real-time search with larger lists
|
|
109
|
-
- **[Async Source](./examples/async-source.js)** - Dynamic loading with mock API
|
|
110
|
-
- **[Custom Theme](./examples/custom-theme.js)** - Custom icons and styling
|
|
111
|
-
- **[Validation](./examples/validation.js)** - Input validation and pre-selection
|
|
112
|
-
- **[Custom Filter](./examples/custom-filter.js)** - Fuzzy matching filter
|
|
113
|
-
|
|
114
|
-
**To run examples:**
|
|
115
|
-
|
|
116
|
-
1. Build the package: `npm run build`
|
|
117
|
-
2. Run any example: `node examples/basic.js`
|
|
118
|
-
|
|
119
|
-
See the [examples README](./examples/README.md) for detailed instructions.
|
|
134
|
+
For detailed examples of advanced features, see the [`examples/`](./examples/) directory.
|
|
120
135
|
|
|
121
136
|
Each example includes detailed comments and demonstrates real-world usage patterns.
|
|
122
137
|
|
package/dist/commonjs/index.d.ts
CHANGED
|
@@ -44,6 +44,43 @@ export type NormalizedChoice<Value> = {
|
|
|
44
44
|
disabled: boolean | string;
|
|
45
45
|
checked: boolean;
|
|
46
46
|
};
|
|
47
|
+
/**
|
|
48
|
+
* Configuration options for the checkbox-search prompt
|
|
49
|
+
*/
|
|
50
|
+
export type PageSizeConfig = {
|
|
51
|
+
base?: number;
|
|
52
|
+
max?: number;
|
|
53
|
+
min?: number;
|
|
54
|
+
autoBufferDescriptions?: boolean;
|
|
55
|
+
buffer?: number;
|
|
56
|
+
minBuffer?: number;
|
|
57
|
+
autoBufferCountsLineWidth?: boolean;
|
|
58
|
+
};
|
|
59
|
+
export type PageSize = number | PageSizeConfig;
|
|
60
|
+
/**
|
|
61
|
+
* Internal item type (choice or separator)
|
|
62
|
+
*/
|
|
63
|
+
type Item<Value> = NormalizedChoice<Value> | Separator;
|
|
64
|
+
/**
|
|
65
|
+
* Validate PageSizeConfig object for correctness
|
|
66
|
+
* @param config - PageSizeConfig to validate
|
|
67
|
+
* @throws Error if validation fails
|
|
68
|
+
*/
|
|
69
|
+
export declare function validatePageSizeConfig(config: PageSizeConfig): void;
|
|
70
|
+
/**
|
|
71
|
+
* Calculate the maximum number of lines needed for descriptions across all items
|
|
72
|
+
* @param items - Array of items to analyze
|
|
73
|
+
* @param countLineWidth - Whether to consider terminal width for line wrapping
|
|
74
|
+
* @returns Maximum lines needed by any description
|
|
75
|
+
*/
|
|
76
|
+
export declare function calculateDescriptionLines<Value>(items: readonly Item<Value>[], countLineWidth: boolean): number;
|
|
77
|
+
/**
|
|
78
|
+
* Resolve PageSize configuration to a final numeric page size
|
|
79
|
+
* @param pageSize - PageSize configuration (number or PageSizeConfig)
|
|
80
|
+
* @param items - Current items to consider for auto-buffering
|
|
81
|
+
* @returns Final resolved page size
|
|
82
|
+
*/
|
|
83
|
+
export declare function resolvePageSize<Value>(pageSize: PageSize, items: readonly Item<Value>[]): number;
|
|
47
84
|
/**
|
|
48
85
|
* Calculate dynamic page size based on terminal height
|
|
49
86
|
* @param fallbackPageSize - Default page size to use if terminal height is not available
|
|
@@ -69,7 +106,7 @@ export declare function calculateDynamicPageSize(fallbackPageSize: number): numb
|
|
|
69
106
|
declare const _default: <Value>(config: {
|
|
70
107
|
message: string;
|
|
71
108
|
prefix?: string | undefined;
|
|
72
|
-
pageSize?:
|
|
109
|
+
pageSize?: PageSize | undefined;
|
|
73
110
|
instructions?: string | boolean | undefined;
|
|
74
111
|
choices?: readonly (string | Separator)[] | readonly (Separator | Choice<Value>)[] | undefined;
|
|
75
112
|
source?: ((term: string | undefined, opt: {
|
package/dist/commonjs/index.js
CHANGED
|
@@ -4,6 +4,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.Separator = void 0;
|
|
7
|
+
exports.validatePageSizeConfig = validatePageSizeConfig;
|
|
8
|
+
exports.calculateDescriptionLines = calculateDescriptionLines;
|
|
9
|
+
exports.resolvePageSize = resolvePageSize;
|
|
7
10
|
exports.calculateDynamicPageSize = calculateDynamicPageSize;
|
|
8
11
|
const core_1 = require("@inquirer/core");
|
|
9
12
|
const yoctocolors_cjs_1 = __importDefault(require("yoctocolors-cjs"));
|
|
@@ -105,6 +108,108 @@ function defaultFilter(items, term) {
|
|
|
105
108
|
value.includes(searchTerm));
|
|
106
109
|
});
|
|
107
110
|
}
|
|
111
|
+
/**
|
|
112
|
+
* Validate PageSizeConfig object for correctness
|
|
113
|
+
* @param config - PageSizeConfig to validate
|
|
114
|
+
* @throws Error if validation fails
|
|
115
|
+
*/
|
|
116
|
+
function validatePageSizeConfig(config) {
|
|
117
|
+
if (config.min !== undefined && config.min < 1) {
|
|
118
|
+
throw new Error('PageSize min cannot be less than 1');
|
|
119
|
+
}
|
|
120
|
+
if (config.base !== undefined && config.base < 1) {
|
|
121
|
+
throw new Error('PageSize base cannot be less than 1');
|
|
122
|
+
}
|
|
123
|
+
if (config.buffer !== undefined && config.buffer < 0) {
|
|
124
|
+
throw new Error('PageSize buffer cannot be negative');
|
|
125
|
+
}
|
|
126
|
+
if (config.minBuffer !== undefined && config.minBuffer < 0) {
|
|
127
|
+
throw new Error('PageSize minBuffer cannot be negative');
|
|
128
|
+
}
|
|
129
|
+
if (config.min !== undefined &&
|
|
130
|
+
config.max !== undefined &&
|
|
131
|
+
config.min > config.max) {
|
|
132
|
+
throw new Error(`PageSize min (${config.min}) cannot be greater than max (${config.max})`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Calculate the maximum number of lines needed for descriptions across all items
|
|
137
|
+
* @param items - Array of items to analyze
|
|
138
|
+
* @param countLineWidth - Whether to consider terminal width for line wrapping
|
|
139
|
+
* @returns Maximum lines needed by any description
|
|
140
|
+
*/
|
|
141
|
+
function calculateDescriptionLines(items, countLineWidth) {
|
|
142
|
+
let maxLines = 0;
|
|
143
|
+
for (const item of items) {
|
|
144
|
+
if (core_1.Separator.isSeparator(item) || !item.description) {
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
let lines;
|
|
148
|
+
if (countLineWidth) {
|
|
149
|
+
// Consider terminal width for wrapping
|
|
150
|
+
const terminalWidth = process.stdout.columns || 80;
|
|
151
|
+
const descriptionLines = item.description.split('\n');
|
|
152
|
+
lines = descriptionLines.reduce((total, line) => {
|
|
153
|
+
return total + (Math.ceil(line.length / terminalWidth) || 1);
|
|
154
|
+
}, 0);
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
// Simple newline counting
|
|
158
|
+
lines = item.description.split('\n').length;
|
|
159
|
+
}
|
|
160
|
+
maxLines = Math.max(maxLines, lines);
|
|
161
|
+
}
|
|
162
|
+
return maxLines;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Resolve PageSize configuration to a final numeric page size
|
|
166
|
+
* @param pageSize - PageSize configuration (number or PageSizeConfig)
|
|
167
|
+
* @param items - Current items to consider for auto-buffering
|
|
168
|
+
* @returns Final resolved page size
|
|
169
|
+
*/
|
|
170
|
+
function resolvePageSize(pageSize, items) {
|
|
171
|
+
// Handle simple number case (backward compatibility)
|
|
172
|
+
if (typeof pageSize === 'number') {
|
|
173
|
+
return pageSize;
|
|
174
|
+
}
|
|
175
|
+
// Validate the configuration
|
|
176
|
+
validatePageSizeConfig(pageSize);
|
|
177
|
+
// Step 1: Determine base page size
|
|
178
|
+
let basePageSize;
|
|
179
|
+
if (pageSize.base !== undefined) {
|
|
180
|
+
basePageSize = pageSize.base;
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
// Auto-calculate using existing logic
|
|
184
|
+
basePageSize = calculateDynamicPageSize(7);
|
|
185
|
+
}
|
|
186
|
+
// Step 2: Calculate buffer reduction
|
|
187
|
+
let buffer = 0;
|
|
188
|
+
// 2a: Start at 0 ✓
|
|
189
|
+
// 2b: If autoBufferDescriptions, add max description lines
|
|
190
|
+
if (pageSize.autoBufferDescriptions) {
|
|
191
|
+
buffer += calculateDescriptionLines(items, pageSize.autoBufferCountsLineWidth || false);
|
|
192
|
+
}
|
|
193
|
+
else {
|
|
194
|
+
// 2c: Add buffer value (only if not auto-buffering)
|
|
195
|
+
buffer += pageSize.buffer || 0;
|
|
196
|
+
}
|
|
197
|
+
// 2d: Ensure at least minBuffer
|
|
198
|
+
if (pageSize.minBuffer !== undefined) {
|
|
199
|
+
buffer = Math.max(buffer, pageSize.minBuffer);
|
|
200
|
+
}
|
|
201
|
+
// Step 3: Subtract buffer from base
|
|
202
|
+
let finalPageSize = basePageSize - buffer;
|
|
203
|
+
// Step 4: Apply min/max constraints
|
|
204
|
+
if (pageSize.min !== undefined) {
|
|
205
|
+
finalPageSize = Math.max(finalPageSize, pageSize.min);
|
|
206
|
+
}
|
|
207
|
+
if (pageSize.max !== undefined) {
|
|
208
|
+
finalPageSize = Math.min(finalPageSize, pageSize.max);
|
|
209
|
+
}
|
|
210
|
+
// Ensure minimum of 1
|
|
211
|
+
return Math.max(1, finalPageSize);
|
|
212
|
+
}
|
|
108
213
|
/**
|
|
109
214
|
* Calculate dynamic page size based on terminal height
|
|
110
215
|
* @param fallbackPageSize - Default page size to use if terminal height is not available
|
|
@@ -160,14 +265,6 @@ exports.default = (0, core_1.createPrompt)((config, done) => {
|
|
|
160
265
|
const emptyArray = (0, core_1.useMemo)(() => [], []);
|
|
161
266
|
// Configuration with defaults
|
|
162
267
|
const { pageSize: configPageSize, loop = true, required, validate = () => true, default: defaultValues = emptyArray, } = config;
|
|
163
|
-
// Calculate effective page size (memoized with terminal size tracking)
|
|
164
|
-
// If pageSize is specified, use it as fixed size
|
|
165
|
-
// If not specified, use auto-sizing that recalculates when terminal resizes
|
|
166
|
-
const terminalHeight = process.stdout.rows; // Track terminal size for memoization
|
|
167
|
-
const pageSize = (0, core_1.useMemo)(() => configPageSize !== undefined
|
|
168
|
-
? configPageSize // Fixed page size
|
|
169
|
-
: calculateDynamicPageSize(7), // Auto page size with fallback 7
|
|
170
|
-
[configPageSize, terminalHeight]);
|
|
171
268
|
const theme = (0, core_1.makeTheme)(checkboxSearchTheme, config.theme);
|
|
172
269
|
// State management hooks
|
|
173
270
|
const [status, setStatus] = (0, core_1.useState)('idle');
|
|
@@ -190,6 +287,18 @@ exports.default = (0, core_1.createPrompt)((config, done) => {
|
|
|
190
287
|
}
|
|
191
288
|
return [];
|
|
192
289
|
});
|
|
290
|
+
// Calculate effective page size (memoized with terminal size tracking)
|
|
291
|
+
// Use new resolvePageSize function to handle both number and PageSizeConfig
|
|
292
|
+
const terminalHeight = process.stdout.rows; // Track terminal size for memoization
|
|
293
|
+
const pageSize = (0, core_1.useMemo)(() => {
|
|
294
|
+
if (configPageSize !== undefined) {
|
|
295
|
+
return resolvePageSize(configPageSize, allItems);
|
|
296
|
+
}
|
|
297
|
+
else {
|
|
298
|
+
// Default behavior - auto-calculate
|
|
299
|
+
return calculateDynamicPageSize(7);
|
|
300
|
+
}
|
|
301
|
+
}, [configPageSize, terminalHeight, allItems]);
|
|
193
302
|
// Store the active item value instead of active index
|
|
194
303
|
const [activeItemValue, setActiveItemValue] = (0, core_1.useState)(null);
|
|
195
304
|
// Compute filtered items based on search term and filtering logic
|
package/dist/esm/index.d.ts
CHANGED
|
@@ -44,6 +44,43 @@ export type NormalizedChoice<Value> = {
|
|
|
44
44
|
disabled: boolean | string;
|
|
45
45
|
checked: boolean;
|
|
46
46
|
};
|
|
47
|
+
/**
|
|
48
|
+
* Configuration options for the checkbox-search prompt
|
|
49
|
+
*/
|
|
50
|
+
export type PageSizeConfig = {
|
|
51
|
+
base?: number;
|
|
52
|
+
max?: number;
|
|
53
|
+
min?: number;
|
|
54
|
+
autoBufferDescriptions?: boolean;
|
|
55
|
+
buffer?: number;
|
|
56
|
+
minBuffer?: number;
|
|
57
|
+
autoBufferCountsLineWidth?: boolean;
|
|
58
|
+
};
|
|
59
|
+
export type PageSize = number | PageSizeConfig;
|
|
60
|
+
/**
|
|
61
|
+
* Internal item type (choice or separator)
|
|
62
|
+
*/
|
|
63
|
+
type Item<Value> = NormalizedChoice<Value> | Separator;
|
|
64
|
+
/**
|
|
65
|
+
* Validate PageSizeConfig object for correctness
|
|
66
|
+
* @param config - PageSizeConfig to validate
|
|
67
|
+
* @throws Error if validation fails
|
|
68
|
+
*/
|
|
69
|
+
export declare function validatePageSizeConfig(config: PageSizeConfig): void;
|
|
70
|
+
/**
|
|
71
|
+
* Calculate the maximum number of lines needed for descriptions across all items
|
|
72
|
+
* @param items - Array of items to analyze
|
|
73
|
+
* @param countLineWidth - Whether to consider terminal width for line wrapping
|
|
74
|
+
* @returns Maximum lines needed by any description
|
|
75
|
+
*/
|
|
76
|
+
export declare function calculateDescriptionLines<Value>(items: readonly Item<Value>[], countLineWidth: boolean): number;
|
|
77
|
+
/**
|
|
78
|
+
* Resolve PageSize configuration to a final numeric page size
|
|
79
|
+
* @param pageSize - PageSize configuration (number or PageSizeConfig)
|
|
80
|
+
* @param items - Current items to consider for auto-buffering
|
|
81
|
+
* @returns Final resolved page size
|
|
82
|
+
*/
|
|
83
|
+
export declare function resolvePageSize<Value>(pageSize: PageSize, items: readonly Item<Value>[]): number;
|
|
47
84
|
/**
|
|
48
85
|
* Calculate dynamic page size based on terminal height
|
|
49
86
|
* @param fallbackPageSize - Default page size to use if terminal height is not available
|
|
@@ -69,7 +106,7 @@ export declare function calculateDynamicPageSize(fallbackPageSize: number): numb
|
|
|
69
106
|
declare const _default: <Value>(config: {
|
|
70
107
|
message: string;
|
|
71
108
|
prefix?: string | undefined;
|
|
72
|
-
pageSize?:
|
|
109
|
+
pageSize?: PageSize | undefined;
|
|
73
110
|
instructions?: string | boolean | undefined;
|
|
74
111
|
choices?: readonly (string | Separator)[] | readonly (Separator | Choice<Value>)[] | undefined;
|
|
75
112
|
source?: ((term: string | undefined, opt: {
|
package/dist/esm/index.js
CHANGED
|
@@ -98,6 +98,108 @@ function defaultFilter(items, term) {
|
|
|
98
98
|
value.includes(searchTerm));
|
|
99
99
|
});
|
|
100
100
|
}
|
|
101
|
+
/**
|
|
102
|
+
* Validate PageSizeConfig object for correctness
|
|
103
|
+
* @param config - PageSizeConfig to validate
|
|
104
|
+
* @throws Error if validation fails
|
|
105
|
+
*/
|
|
106
|
+
export function validatePageSizeConfig(config) {
|
|
107
|
+
if (config.min !== undefined && config.min < 1) {
|
|
108
|
+
throw new Error('PageSize min cannot be less than 1');
|
|
109
|
+
}
|
|
110
|
+
if (config.base !== undefined && config.base < 1) {
|
|
111
|
+
throw new Error('PageSize base cannot be less than 1');
|
|
112
|
+
}
|
|
113
|
+
if (config.buffer !== undefined && config.buffer < 0) {
|
|
114
|
+
throw new Error('PageSize buffer cannot be negative');
|
|
115
|
+
}
|
|
116
|
+
if (config.minBuffer !== undefined && config.minBuffer < 0) {
|
|
117
|
+
throw new Error('PageSize minBuffer cannot be negative');
|
|
118
|
+
}
|
|
119
|
+
if (config.min !== undefined &&
|
|
120
|
+
config.max !== undefined &&
|
|
121
|
+
config.min > config.max) {
|
|
122
|
+
throw new Error(`PageSize min (${config.min}) cannot be greater than max (${config.max})`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Calculate the maximum number of lines needed for descriptions across all items
|
|
127
|
+
* @param items - Array of items to analyze
|
|
128
|
+
* @param countLineWidth - Whether to consider terminal width for line wrapping
|
|
129
|
+
* @returns Maximum lines needed by any description
|
|
130
|
+
*/
|
|
131
|
+
export function calculateDescriptionLines(items, countLineWidth) {
|
|
132
|
+
let maxLines = 0;
|
|
133
|
+
for (const item of items) {
|
|
134
|
+
if (Separator.isSeparator(item) || !item.description) {
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
let lines;
|
|
138
|
+
if (countLineWidth) {
|
|
139
|
+
// Consider terminal width for wrapping
|
|
140
|
+
const terminalWidth = process.stdout.columns || 80;
|
|
141
|
+
const descriptionLines = item.description.split('\n');
|
|
142
|
+
lines = descriptionLines.reduce((total, line) => {
|
|
143
|
+
return total + (Math.ceil(line.length / terminalWidth) || 1);
|
|
144
|
+
}, 0);
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
// Simple newline counting
|
|
148
|
+
lines = item.description.split('\n').length;
|
|
149
|
+
}
|
|
150
|
+
maxLines = Math.max(maxLines, lines);
|
|
151
|
+
}
|
|
152
|
+
return maxLines;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Resolve PageSize configuration to a final numeric page size
|
|
156
|
+
* @param pageSize - PageSize configuration (number or PageSizeConfig)
|
|
157
|
+
* @param items - Current items to consider for auto-buffering
|
|
158
|
+
* @returns Final resolved page size
|
|
159
|
+
*/
|
|
160
|
+
export function resolvePageSize(pageSize, items) {
|
|
161
|
+
// Handle simple number case (backward compatibility)
|
|
162
|
+
if (typeof pageSize === 'number') {
|
|
163
|
+
return pageSize;
|
|
164
|
+
}
|
|
165
|
+
// Validate the configuration
|
|
166
|
+
validatePageSizeConfig(pageSize);
|
|
167
|
+
// Step 1: Determine base page size
|
|
168
|
+
let basePageSize;
|
|
169
|
+
if (pageSize.base !== undefined) {
|
|
170
|
+
basePageSize = pageSize.base;
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
// Auto-calculate using existing logic
|
|
174
|
+
basePageSize = calculateDynamicPageSize(7);
|
|
175
|
+
}
|
|
176
|
+
// Step 2: Calculate buffer reduction
|
|
177
|
+
let buffer = 0;
|
|
178
|
+
// 2a: Start at 0 ✓
|
|
179
|
+
// 2b: If autoBufferDescriptions, add max description lines
|
|
180
|
+
if (pageSize.autoBufferDescriptions) {
|
|
181
|
+
buffer += calculateDescriptionLines(items, pageSize.autoBufferCountsLineWidth || false);
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
// 2c: Add buffer value (only if not auto-buffering)
|
|
185
|
+
buffer += pageSize.buffer || 0;
|
|
186
|
+
}
|
|
187
|
+
// 2d: Ensure at least minBuffer
|
|
188
|
+
if (pageSize.minBuffer !== undefined) {
|
|
189
|
+
buffer = Math.max(buffer, pageSize.minBuffer);
|
|
190
|
+
}
|
|
191
|
+
// Step 3: Subtract buffer from base
|
|
192
|
+
let finalPageSize = basePageSize - buffer;
|
|
193
|
+
// Step 4: Apply min/max constraints
|
|
194
|
+
if (pageSize.min !== undefined) {
|
|
195
|
+
finalPageSize = Math.max(finalPageSize, pageSize.min);
|
|
196
|
+
}
|
|
197
|
+
if (pageSize.max !== undefined) {
|
|
198
|
+
finalPageSize = Math.min(finalPageSize, pageSize.max);
|
|
199
|
+
}
|
|
200
|
+
// Ensure minimum of 1
|
|
201
|
+
return Math.max(1, finalPageSize);
|
|
202
|
+
}
|
|
101
203
|
/**
|
|
102
204
|
* Calculate dynamic page size based on terminal height
|
|
103
205
|
* @param fallbackPageSize - Default page size to use if terminal height is not available
|
|
@@ -153,14 +255,6 @@ export default createPrompt((config, done) => {
|
|
|
153
255
|
const emptyArray = useMemo(() => [], []);
|
|
154
256
|
// Configuration with defaults
|
|
155
257
|
const { pageSize: configPageSize, loop = true, required, validate = () => true, default: defaultValues = emptyArray, } = config;
|
|
156
|
-
// Calculate effective page size (memoized with terminal size tracking)
|
|
157
|
-
// If pageSize is specified, use it as fixed size
|
|
158
|
-
// If not specified, use auto-sizing that recalculates when terminal resizes
|
|
159
|
-
const terminalHeight = process.stdout.rows; // Track terminal size for memoization
|
|
160
|
-
const pageSize = useMemo(() => configPageSize !== undefined
|
|
161
|
-
? configPageSize // Fixed page size
|
|
162
|
-
: calculateDynamicPageSize(7), // Auto page size with fallback 7
|
|
163
|
-
[configPageSize, terminalHeight]);
|
|
164
258
|
const theme = makeTheme(checkboxSearchTheme, config.theme);
|
|
165
259
|
// State management hooks
|
|
166
260
|
const [status, setStatus] = useState('idle');
|
|
@@ -183,6 +277,18 @@ export default createPrompt((config, done) => {
|
|
|
183
277
|
}
|
|
184
278
|
return [];
|
|
185
279
|
});
|
|
280
|
+
// Calculate effective page size (memoized with terminal size tracking)
|
|
281
|
+
// Use new resolvePageSize function to handle both number and PageSizeConfig
|
|
282
|
+
const terminalHeight = process.stdout.rows; // Track terminal size for memoization
|
|
283
|
+
const pageSize = useMemo(() => {
|
|
284
|
+
if (configPageSize !== undefined) {
|
|
285
|
+
return resolvePageSize(configPageSize, allItems);
|
|
286
|
+
}
|
|
287
|
+
else {
|
|
288
|
+
// Default behavior - auto-calculate
|
|
289
|
+
return calculateDynamicPageSize(7);
|
|
290
|
+
}
|
|
291
|
+
}, [configPageSize, terminalHeight, allItems]);
|
|
186
292
|
// Store the active item value instead of active index
|
|
187
293
|
const [activeItemValue, setActiveItemValue] = useState(null);
|
|
188
294
|
// Compute filtered items based on search term and filtering logic
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "inquirerjs-checkbox-search",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "A multi-select prompt with text filtering for inquirer.js",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"answer",
|
|
@@ -70,15 +70,22 @@
|
|
|
70
70
|
"lint:check": "eslint . --ext .ts",
|
|
71
71
|
"format": "prettier --write .",
|
|
72
72
|
"format:check": "prettier --check .",
|
|
73
|
-
"
|
|
74
|
-
"
|
|
73
|
+
"quality": "npm run format && npm run lint && npm run typecheck",
|
|
74
|
+
"quality:check": "npm run format:check && npm run lint:check && npm run typecheck",
|
|
75
|
+
"test": "npm run quality && npm run test:unit",
|
|
76
|
+
"test:ci": "npm run quality:check && npm run test:coverage",
|
|
75
77
|
"test:unit": "vitest run",
|
|
76
78
|
"test:coverage": "vitest run --coverage",
|
|
77
79
|
"test:ui": "vitest --ui",
|
|
78
80
|
"typecheck": "tsc --noEmit",
|
|
79
81
|
"attw": "attw --pack",
|
|
80
82
|
"pkg:inspect": "rimraf package-inspect *.tgz && npm run build && npm pack && mkdir package-inspect && tar -xzf *.tgz -C package-inspect && echo '\\n📦 Package extracted to package-inspect/ for inspection'",
|
|
81
|
-
"prepublishOnly": "npm run clean && npm run build && npm run test:ci && npm run attw"
|
|
83
|
+
"prepublishOnly": "npm run clean && npm run build && npm run test:ci && npm run attw",
|
|
84
|
+
"demo:docker:build": "docker build -f demos/Dockerfile -t vhs-node-demo .",
|
|
85
|
+
"demo:docker:run": "docker run --rm -v $PWD/docs/img:/workspace/docs/img vhs-node-demo",
|
|
86
|
+
"demo:generate:basic": "npm run demo:docker:build && npm run demo:docker:run demos/basic.tape",
|
|
87
|
+
"demo:generate:all": "npm run demo:generate:basic",
|
|
88
|
+
"demo:generate": "npm run demo:generate:basic"
|
|
82
89
|
},
|
|
83
90
|
"dependencies": {
|
|
84
91
|
"@inquirer/core": "^10.1.13",
|