Skip to content

Commit df4eea1

Browse files
authored
feat: rework path, remove suggestion prompt (#335)
1 parent 53ebd43 commit df4eea1

File tree

19 files changed

+502
-1288
lines changed

19 files changed

+502
-1288
lines changed

.changeset/short-taxis-cross.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@clack/prompts": minor
3+
"@clack/core": minor
4+
---
5+
6+
Remove `suggestion` prompt and change `path` prompt to be an autocomplete prompt.

packages/core/src/index.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
export type { ClackState as State, ValueWithCursorPart } from './types.js';
1+
export type { ClackState as State } from './types.js';
22
export type { ClackSettings } from './utils/settings.js';
33

44
export { default as ConfirmPrompt } from './prompts/confirm.js';
@@ -10,6 +10,5 @@ export { default as SelectPrompt } from './prompts/select.js';
1010
export { default as SelectKeyPrompt } from './prompts/select-key.js';
1111
export { default as TextPrompt } from './prompts/text.js';
1212
export { default as AutocompletePrompt } from './prompts/autocomplete.js';
13-
export { default as SuggestionPrompt } from './prompts/suggestion.js';
1413
export { block, isCancel, getColumns } from './utils/index.js';
1514
export { updateSettings, settings } from './utils/settings.js';

packages/core/src/prompts/autocomplete.ts

Lines changed: 31 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -46,15 +46,14 @@ function normalisedValue<T>(multiple: boolean, values: T[] | undefined): T | T[]
4646

4747
interface AutocompleteOptions<T extends OptionLike>
4848
extends PromptOptions<T['value'] | T['value'][], AutocompletePrompt<T>> {
49-
options: T[];
49+
options: T[] | ((this: AutocompletePrompt<T>) => T[]);
5050
filter?: FilterFunction<T>;
5151
multiple?: boolean;
5252
}
5353

5454
export default class AutocompletePrompt<T extends OptionLike> extends Prompt<
5555
T['value'] | T['value'][]
5656
> {
57-
options: T[];
5857
filteredOptions: T[];
5958
multiple: boolean;
6059
isNavigating = false;
@@ -64,6 +63,7 @@ export default class AutocompletePrompt<T extends OptionLike> extends Prompt<
6463
#cursor = 0;
6564
#lastUserInput = '';
6665
#filterFn: FilterFunction<T>;
66+
#options: T[] | (() => T[]);
6767

6868
get cursor(): number {
6969
return this.#cursor;
@@ -81,11 +81,19 @@ export default class AutocompletePrompt<T extends OptionLike> extends Prompt<
8181
return `${s1}${color.inverse(s2)}${s3.join('')}`;
8282
}
8383

84+
get options(): T[] {
85+
if (typeof this.#options === 'function') {
86+
return this.#options();
87+
}
88+
return this.#options;
89+
}
90+
8491
constructor(opts: AutocompleteOptions<T>) {
8592
super(opts);
8693

87-
this.options = opts.options;
88-
this.filteredOptions = [...this.options];
94+
this.#options = opts.options;
95+
const options = this.options;
96+
this.filteredOptions = [...options];
8997
this.multiple = opts.multiple === true;
9098
this.#filterFn = opts.filter ?? defaultFilter;
9199
let initialValues: unknown[] | undefined;
@@ -103,7 +111,7 @@ export default class AutocompletePrompt<T extends OptionLike> extends Prompt<
103111

104112
if (initialValues) {
105113
for (const selectedValue of initialValues) {
106-
const selectedIndex = this.options.findIndex((opt) => opt.value === selectedValue);
114+
const selectedIndex = options.findIndex((opt) => opt.value === selectedValue);
107115
if (selectedIndex !== -1) {
108116
this.toggleSelected(selectedValue);
109117
this.#cursor = selectedIndex;
@@ -113,16 +121,6 @@ export default class AutocompletePrompt<T extends OptionLike> extends Prompt<
113121

114122
this.focusedValue = this.options[this.#cursor]?.value;
115123

116-
this.on('finalize', () => {
117-
if (!this.value) {
118-
this.value = normalisedValue(this.multiple, initialValues);
119-
}
120-
121-
if (this.state === 'submit') {
122-
this.value = normalisedValue(this.multiple, this.selectedValues);
123-
}
124-
});
125-
126124
this.on('key', (char, key) => this.#onKey(char, key));
127125
this.on('userInput', (value) => this.#onUserInputChanged(value));
128126
}
@@ -141,6 +139,7 @@ export default class AutocompletePrompt<T extends OptionLike> extends Prompt<
141139
#onKey(_char: string | undefined, key: Key): void {
142140
const isUpKey = key.name === 'up';
143141
const isDownKey = key.name === 'down';
142+
const isReturnKey = key.name === 'return';
144143

145144
// Start navigation mode with up/down arrows
146145
if (isUpKey || isDownKey) {
@@ -153,6 +152,8 @@ export default class AutocompletePrompt<T extends OptionLike> extends Prompt<
153152
this.selectedValues = [this.focusedValue];
154153
}
155154
this.isNavigating = true;
155+
} else if (isReturnKey) {
156+
this.value = normalisedValue(this.multiple, this.selectedValues);
156157
} else {
157158
if (this.multiple) {
158159
if (
@@ -171,6 +172,10 @@ export default class AutocompletePrompt<T extends OptionLike> extends Prompt<
171172
}
172173
}
173174

175+
deselectAll() {
176+
this.selectedValues = [];
177+
}
178+
174179
toggleSelected(value: T['value']) {
175180
if (this.filteredOptions.length === 0) {
176181
return;
@@ -191,13 +196,22 @@ export default class AutocompletePrompt<T extends OptionLike> extends Prompt<
191196
if (value !== this.#lastUserInput) {
192197
this.#lastUserInput = value;
193198

199+
const options = this.options;
200+
194201
if (value) {
195-
this.filteredOptions = this.options.filter((opt) => this.#filterFn(value, opt));
202+
this.filteredOptions = options.filter((opt) => this.#filterFn(value, opt));
196203
} else {
197-
this.filteredOptions = [...this.options];
204+
this.filteredOptions = [...options];
198205
}
199206
this.#cursor = getCursorForValue(this.focusedValue, this.filteredOptions);
200207
this.focusedValue = this.filteredOptions[this.#cursor]?.value;
208+
if (!this.multiple) {
209+
if (this.focusedValue !== undefined) {
210+
this.toggleSelected(this.focusedValue);
211+
} else {
212+
this.deselectAll();
213+
}
214+
}
201215
}
202216
}
203217
}

packages/core/src/prompts/prompt.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import type { Action } from '../utils/index.js';
1313
export interface PromptOptions<TValue, Self extends Prompt<TValue>> {
1414
render(this: Omit<Self, 'prompt'>): string | undefined;
1515
initialValue?: any;
16+
initialUserInput?: string;
1617
validate?: ((value: TValue | undefined) => string | Error | undefined) | undefined;
1718
input?: Readable;
1819
output?: Writable;
@@ -25,7 +26,7 @@ export default class Prompt<TValue> {
2526
protected output: Writable;
2627
private _abortSignal?: AbortSignal;
2728

28-
protected rl: ReadLine | undefined;
29+
private rl: ReadLine | undefined;
2930
private opts: Omit<PromptOptions<TValue, Prompt<TValue>>, 'render' | 'input' | 'output'>;
3031
private _render: (context: Omit<Prompt<TValue>, 'prompt'>) => string | undefined;
3132
private _track = false;
@@ -145,7 +146,9 @@ export default class Prompt<TValue> {
145146
});
146147
this.rl.prompt();
147148

148-
this.emit('beforePrompt');
149+
if (this.opts.initialUserInput !== undefined) {
150+
this._setUserInput(this.opts.initialUserInput, true);
151+
}
149152

150153
this.input.on('keypress', this.onKeypress);
151154
setRawMode(this.input, true);

packages/core/src/prompts/suggestion.ts

Lines changed: 0 additions & 126 deletions
This file was deleted.

packages/core/src/prompts/text.ts

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,11 @@ export default class TextPrompt extends Prompt<string> {
2323
return this._cursor;
2424
}
2525
constructor(opts: TextOptions) {
26-
super(opts);
27-
28-
this.on('beforePrompt', () => {
29-
if (opts.initialValue !== undefined) {
30-
this._setUserInput(opts.initialValue, true);
31-
}
26+
super({
27+
...opts,
28+
initialUserInput: opts.initialUserInput ?? opts.initialValue,
3229
});
30+
3331
this.on('userInput', (input) => {
3432
this._setValue(input);
3533
});

packages/core/src/types.ts

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,3 @@ export interface ClackEvents<TValue> {
2323
finalize: () => void;
2424
beforePrompt: () => void;
2525
}
26-
27-
/**
28-
* Display a value
29-
*/
30-
export interface ValueWithCursorPart {
31-
text: string;
32-
type: 'value' | 'cursor_on_value' | 'suggestion' | 'cursor_on_suggestion';
33-
}

0 commit comments

Comments
 (0)