Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 11 additions & 4 deletions packages/prompts/src/autocomplete.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { AutocompletePrompt } from '@clack/core';
import color from 'picocolors';
import { cursor } from 'sisteransi';
import {
type CommonOptions,
type CommonPromptOptions,
clearPrompt,
S_BAR,
S_BAR_END,
S_CHECKBOX_INACTIVE,
Expand Down Expand Up @@ -41,7 +43,7 @@ function getSelectedOptions<T>(values: T[], options: Option<T>[]): Option<T>[] {
return results;
}

interface AutocompleteSharedOptions<Value> extends CommonOptions {
interface AutocompleteSharedOptions<Value> extends CommonPromptOptions {
/**
* The message to display to the user.
*/
Expand Down Expand Up @@ -103,7 +105,10 @@ export const autocomplete = <Value>(opts: AutocompleteOptions<Value>) => {
const selected = getSelectedOptions(this.selectedValues, options);
const label =
selected.length > 0 ? ` ${color.dim(selected.map(getLabel).join(', '))}` : '';
return `${headings.join('\n')}\n${color.gray(S_BAR)}${label}`;

return clearPrompt(opts)
? cursor.up()
: `${headings.join('\n')}\n${color.gray(S_BAR)}${label}`;
}

case 'cancel': {
Expand Down Expand Up @@ -276,7 +281,9 @@ export const autocompleteMultiselect = <Value>(opts: AutocompleteMultiSelectOpti
// Render prompt state
switch (this.state) {
case 'submit': {
return `${title}${color.gray(S_BAR)} ${color.dim(`${this.selectedValues.length} items selected`)}`;
return clearPrompt(opts)
? cursor.up()
: `${title}${color.gray(S_BAR)} ${color.dim(`${this.selectedValues.length} items selected`)}`;
}
case 'cancel': {
return `${title}${color.gray(S_BAR)} ${color.strikethrough(color.dim(userInput))}`;
Expand Down
8 changes: 8 additions & 0 deletions packages/prompts/src/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,11 @@ export interface CommonOptions {
output?: Writable;
signal?: AbortSignal;
}

export interface CommonPromptOptions extends CommonOptions {
clearPromptOnDone?: boolean;
}

export const clearPrompt = (opts: CommonPromptOptions) => {
return Boolean(opts.clearPromptOnDone && typeof opts.clearPromptOnDone === 'boolean');
};
10 changes: 7 additions & 3 deletions packages/prompts/src/confirm.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
import { ConfirmPrompt } from '@clack/core';
import color from 'picocolors';
import { cursor } from 'sisteransi';
import {
type CommonOptions,
type CommonPromptOptions,
clearPrompt,
S_BAR,
S_BAR_END,
S_RADIO_ACTIVE,
S_RADIO_INACTIVE,
symbol,
} from './common.js';

export interface ConfirmOptions extends CommonOptions {
export interface ConfirmOptions extends CommonPromptOptions {
message: string;
active?: string;
inactive?: string;
Expand All @@ -31,7 +33,9 @@ export const confirm = (opts: ConfirmOptions) => {

switch (this.state) {
case 'submit':
return `${title}${color.gray(S_BAR)} ${color.dim(value)}`;
return clearPrompt(opts)
? cursor.up()
: `${title}${color.gray(S_BAR)} ${color.dim(value)}`;
case 'cancel':
return `${title}${color.gray(S_BAR)} ${color.strikethrough(
color.dim(value)
Expand Down
8 changes: 5 additions & 3 deletions packages/prompts/src/group-multi-select.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { GroupMultiSelectPrompt } from '@clack/core';
import color from 'picocolors';
import { cursor } from 'sisteransi';
import {
type CommonOptions,
type CommonPromptOptions,
clearPrompt,
S_BAR,
S_BAR_END,
S_CHECKBOX_ACTIVE,
Expand All @@ -11,7 +13,7 @@ import {
} from './common.js';
import type { Option } from './select.js';

export interface GroupMultiSelectOptions<Value> extends CommonOptions {
export interface GroupMultiSelectOptions<Value> extends CommonPromptOptions {
message: string;
options: Record<string, Option<Value>[]>;
initialValues?: Value[];
Expand Down Expand Up @@ -109,7 +111,7 @@ export const groupMultiselect = <Value>(opts: GroupMultiSelectOptions<Value>) =>
.map((option) => opt(option, 'submitted'));
const optionsText =
selectedOptions.length === 0 ? '' : ` ${selectedOptions.join(color.dim(', '))}`;
return `${title}${color.gray(S_BAR)}${optionsText}`;
return clearPrompt(opts) ? cursor.up() : `${title}${color.gray(S_BAR)}${optionsText}`;
}
case 'cancel': {
const label = this.options
Expand Down
20 changes: 12 additions & 8 deletions packages/prompts/src/multi-select.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { MultiSelectPrompt } from '@clack/core';
import color from 'picocolors';
import { cursor } from 'sisteransi';
import {
type CommonOptions,
type CommonPromptOptions,
clearPrompt,
S_BAR,
S_BAR_END,
S_CHECKBOX_ACTIVE,
Expand All @@ -12,7 +14,7 @@ import {
import { limitOptions } from './limit-options.js';
import type { Option } from './select.js';

export interface MultiSelectOptions<Value> extends CommonOptions {
export interface MultiSelectOptions<Value> extends CommonPromptOptions {
message: string;
options: Option<Value>[];
initialValues?: Value[];
Expand Down Expand Up @@ -86,12 +88,14 @@ export const multiselect = <Value>(opts: MultiSelectOptions<Value>) => {

switch (this.state) {
case 'submit': {
return `${title}${color.gray(S_BAR)} ${
this.options
.filter(({ value: optionValue }) => value.includes(optionValue))
.map((option) => opt(option, 'submitted'))
.join(color.dim(', ')) || color.dim('none')
}`;
return clearPrompt(opts)
? cursor.up()
: `${title}${color.gray(S_BAR)} ${
this.options
.filter(({ value: optionValue }) => value.includes(optionValue))
.map((option) => opt(option, 'submitted'))
.join(color.dim(', ')) || color.dim('none')
}`;
}
case 'cancel': {
const label = this.options
Expand Down
14 changes: 11 additions & 3 deletions packages/prompts/src/password.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
import { PasswordPrompt } from '@clack/core';
import color from 'picocolors';
import { type CommonOptions, S_BAR, S_BAR_END, S_PASSWORD_MASK, symbol } from './common.js';
import { cursor } from 'sisteransi';
import {
type CommonPromptOptions,
clearPrompt,
S_BAR,
S_BAR_END,
S_PASSWORD_MASK,
symbol,
} from './common.js';

export interface PasswordOptions extends CommonOptions {
export interface PasswordOptions extends CommonPromptOptions {
message: string;
mask?: string;
validate?: (value: string | undefined) => string | Error | undefined;
Expand Down Expand Up @@ -32,7 +40,7 @@ export const password = (opts: PasswordOptions) => {
}
case 'submit': {
const maskedText = masked ? ` ${color.dim(masked)}` : '';
return `${title}${color.gray(S_BAR)}${maskedText}`;
return clearPrompt(opts) ? cursor.up() : `${title}${color.gray(S_BAR)}${maskedText}`;
}
case 'cancel': {
const maskedText = masked ? ` ${color.strikethrough(color.dim(masked))}` : '';
Expand Down
4 changes: 2 additions & 2 deletions packages/prompts/src/path.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { existsSync, lstatSync, readdirSync } from 'node:fs';
import { dirname, join } from 'node:path';
import { autocomplete } from './autocomplete.js';
import type { CommonOptions } from './common.js';
import type { CommonPromptOptions } from './common.js';

export interface PathOptions extends CommonOptions {
export interface PathOptions extends CommonPromptOptions {
root?: string;
directory?: boolean;
initialValue?: string;
Expand Down
13 changes: 8 additions & 5 deletions packages/prompts/src/select-key.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { SelectKeyPrompt } from '@clack/core';
import color from 'picocolors';
import { S_BAR, S_BAR_END, symbol } from './common.js';
import { cursor } from 'sisteransi';
import { clearPrompt, S_BAR, S_BAR_END, symbol } from './common.js';
import type { Option, SelectOptions } from './select.js';

export const selectKey = <Value extends string>(opts: SelectOptions<Value>) => {
Expand Down Expand Up @@ -36,10 +37,12 @@ export const selectKey = <Value extends string>(opts: SelectOptions<Value>) => {

switch (this.state) {
case 'submit':
return `${title}${color.gray(S_BAR)} ${opt(
this.options.find((opt) => opt.value === this.value) ?? opts.options[0],
'selected'
)}`;
return clearPrompt(opts)
? cursor.up()
: `${title}${color.gray(S_BAR)} ${opt(
this.options.find((opt) => opt.value === this.value) ?? opts.options[0],
'selected'
)}`;
case 'cancel':
return `${title}${color.gray(S_BAR)} ${opt(this.options[0], 'cancelled')}\n${color.gray(
S_BAR
Expand Down
10 changes: 7 additions & 3 deletions packages/prompts/src/select.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { SelectPrompt } from '@clack/core';
import color from 'picocolors';
import { cursor } from 'sisteransi';
import {
type CommonOptions,
type CommonPromptOptions,
clearPrompt,
S_BAR,
S_BAR_END,
S_RADIO_ACTIVE,
Expand Down Expand Up @@ -50,7 +52,7 @@ export type Option<Value> = Value extends Primitive
hint?: string;
};

export interface SelectOptions<Value> extends CommonOptions {
export interface SelectOptions<Value> extends CommonPromptOptions {
message: string;
options: Option<Value>[];
initialValue?: Value;
Expand Down Expand Up @@ -85,7 +87,9 @@ export const select = <Value>(opts: SelectOptions<Value>) => {

switch (this.state) {
case 'submit':
return `${title}${color.gray(S_BAR)} ${opt(this.options[this.cursor], 'selected')}`;
return clearPrompt(opts)
? cursor.up()
: `${title}${color.gray(S_BAR)} ${opt(this.options[this.cursor], 'selected')}`;
case 'cancel':
return `${title}${color.gray(S_BAR)} ${opt(
this.options[this.cursor],
Expand Down
7 changes: 4 additions & 3 deletions packages/prompts/src/text.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { TextPrompt } from '@clack/core';
import color from 'picocolors';
import { type CommonOptions, S_BAR, S_BAR_END, symbol } from './common.js';
import { cursor } from 'sisteransi';
import { type CommonPromptOptions, clearPrompt, S_BAR, S_BAR_END, symbol } from './common.js';

export interface TextOptions extends CommonOptions {
export interface TextOptions extends CommonPromptOptions {
message: string;
placeholder?: string;
defaultValue?: string;
Expand Down Expand Up @@ -36,7 +37,7 @@ export const text = (opts: TextOptions) => {
}
case 'submit': {
const valueText = value ? ` ${color.dim(value)}` : '';
return `${title}${color.gray(S_BAR)}${valueText}`;
return clearPrompt(opts) ? cursor.up() : `${title}${color.gray(S_BAR)}${valueText}`;
}
case 'cancel': {
const valueText = value ? ` ${color.strikethrough(color.dim(value))}` : '';
Expand Down
89 changes: 89 additions & 0 deletions packages/prompts/test/__snapshots__/autocomplete.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,41 @@ exports[`autocomplete > can be aborted by a signal 1`] = `
]
`;

exports[`autocomplete > clear prompt after done 1`] = `
[
"<cursor.hide>",
"│
◆ foo
│
│ Search: _
│ ● Apple
│ ○ Banana
│ ○ Cherry
│ ○ Grape
│ ○ Orange
│ ↑/↓ to select • Enter: confirm • Type: to search
└",
"<cursor.backward count=999><cursor.up count=10>",
"<cursor.down count=3>",
"<erase.down>",
"│ Search:
│ ○ Apple
│ ● Banana
│ ○ Cherry
│ ○ Grape
│ ○ Orange
│ ↑/↓ to select • Enter: confirm • Type: to search
└",
"<cursor.backward count=999><cursor.up count=10>",
"",
"<erase.down>",
"<cursor.up count=1>",
"
",
"<cursor.show>",
]
`;

exports[`autocomplete > limits displayed options when maxItems is set 1`] = `
[
"<cursor.hide>",
Expand Down Expand Up @@ -411,6 +446,60 @@ exports[`autocompleteMultiselect > can use navigation keys to select options 1`]
]
`;

exports[`autocompleteMultiselect > clear prompt after done 1`] = `
[
"<cursor.hide>",
"│
◆ Select fruits

│ Search: _
│ ◻ Apple
│ ◻ Banana
│ ◻ Cherry
│ ◻ Grape
│ ◻ Orange
│ ↑/↓ to navigate • Tab: select • Enter: confirm • Type: to search
└",
"<cursor.backward count=999><cursor.up count=10>",
"<cursor.down count=3>",
"<erase.down>",
"│ Search: 
│ ◻ Apple
│ ◻ Banana
│ ◻ Cherry
│ ◻ Grape
│ ◻ Orange
│ ↑/↓ to navigate • Space/Tab: select • Enter: confirm • Type: to search
└",
"<cursor.backward count=999><cursor.up count=10>",
"<cursor.down count=5>",
"<erase.line><cursor.left count=1>",
"│ ◼ Banana",
"<cursor.down count=5>",
"<cursor.backward count=999><cursor.up count=10>",
"<cursor.down count=5>",
"<erase.down>",
"│ ◼ Banana
│ ◻ Cherry
│ ◻ Grape
│ ◻ Orange
│ ↑/↓ to navigate • Space/Tab: select • Enter: confirm • Type: to search
└",
"<cursor.backward count=999><cursor.up count=10>",
"<cursor.down count=6>",
"<erase.line><cursor.left count=1>",
"│ ◼ Cherry",
"<cursor.down count=4>",
"<cursor.backward count=999><cursor.up count=10>",
"",
"<erase.down>",
"<cursor.up count=1>",
"
",
"<cursor.show>",
]
`;

exports[`autocompleteMultiselect > renders error when empty selection & required is true 1`] = `
[
"<cursor.hide>",
Expand Down
Loading
Loading