Skip to content

Commit bfe0dd3

Browse files
committed
fix: prevent placeholder from being used as input value in text prompts
1 parent 2837845 commit bfe0dd3

File tree

11 files changed

+290
-76
lines changed

11 files changed

+290
-76
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@clack/prompts": patch
3+
"@clack/core": patch
4+
---
5+
6+
Prevents placeholder from being used as input value in text prompts

examples/basic/default-value.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import * as p from '@clack/prompts';
2+
import color from 'picocolors';
3+
4+
async function main() {
5+
const defaultPath = 'my-project';
6+
7+
const result = await p.text(
8+
{
9+
message: 'Enter the directory to bootstrap the project',
10+
placeholder: ` (hit Enter to use '${defaultPath}')`,
11+
defaultValue: defaultPath,
12+
validate: (value) => {
13+
if (!value) {
14+
return 'Directory is required';
15+
}
16+
if (value.includes(' ')) {
17+
return 'Directory cannot contain spaces';
18+
}
19+
return undefined;
20+
},
21+
}
22+
);
23+
24+
if (p.isCancel(result)) {
25+
p.cancel('Operation cancelled.');
26+
process.exit(0);
27+
}
28+
29+
p.outro(`Let's bootstrap the project in ${color.cyan(result)}`);
30+
}
31+
32+
main().catch(console.error);

packages/core/src/prompts/prompt.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export interface PromptOptions<Self extends Prompt> {
1414
render(this: Omit<Self, 'prompt'>): string | undefined;
1515
placeholder?: string;
1616
initialValue?: any;
17+
defaultValue?: any;
1718
validate?: ((value: any) => string | Error | undefined) | undefined;
1819
input?: Readable;
1920
output?: Writable;
@@ -222,9 +223,12 @@ export default class Prompt {
222223
this.emit('key', char?.toLowerCase(), key);
223224

224225
if (key?.name === 'return') {
225-
if (!this.value && this.opts.placeholder) {
226-
this.rl?.write(this.opts.placeholder);
227-
this._setValue(this.opts.placeholder);
226+
if (!this.value) {
227+
if (this.opts.defaultValue) {
228+
this._setValue(this.opts.defaultValue);
229+
} else {
230+
this._setValue('');
231+
}
228232
}
229233

230234
if (this.opts.validate) {

packages/core/src/prompts/text.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ export default class TextPrompt extends Prompt {
2929
if (!this.value) {
3030
this.value = opts.defaultValue;
3131
}
32+
if (this.value === undefined) {
33+
this.value = '';
34+
}
3235
});
3336
}
3437
}

packages/core/test/prompts/prompt.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ describe('Prompt', () => {
3838
const resultPromise = instance.prompt();
3939
input.emit('keypress', '', { name: 'return' });
4040
const result = await resultPromise;
41-
expect(result).to.equal(undefined);
41+
expect(result).to.equal('');
4242
expect(isCancel(result)).to.equal(false);
4343
expect(instance.state).to.equal('submit');
4444
expect(output.buffer).to.deep.equal([cursor.hide, 'foo', '\n', cursor.show]);

packages/core/test/prompts/text.test.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,5 +110,32 @@ describe('TextPrompt', () => {
110110
input.emit('keypress', 'right', { name: 'right' });
111111
expect(instance.valueWithCursor).to.equal('foo█');
112112
});
113+
114+
test('does not use placeholder as value when pressing enter', async () => {
115+
const instance = new TextPrompt({
116+
input,
117+
output,
118+
render: () => 'foo',
119+
placeholder: ' (hit Enter to use default)',
120+
defaultValue: 'default-value'
121+
});
122+
const resultPromise = instance.prompt();
123+
input.emit('keypress', '', { name: 'return' });
124+
const result = await resultPromise;
125+
expect(result).to.equal('default-value');
126+
});
127+
128+
test('returns empty string when no value and no default', async () => {
129+
const instance = new TextPrompt({
130+
input,
131+
output,
132+
render: () => 'foo',
133+
placeholder: ' (hit Enter to use default)'
134+
});
135+
const resultPromise = instance.prompt();
136+
input.emit('keypress', '', { name: 'return' });
137+
const result = await resultPromise;
138+
expect(result).to.equal('');
139+
});
113140
});
114141
});

packages/prompts/src/text.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,10 @@ export const text = (opts: TextOptions) => {
3030
return `${title.trim()}\n${color.yellow(S_BAR)} ${value}\n${color.yellow(
3131
S_BAR_END
3232
)} ${color.yellow(this.error)}\n`;
33-
case 'submit':
34-
return `${title}${color.gray(S_BAR)} ${color.dim(this.value || opts.placeholder)}`;
33+
case 'submit': {
34+
const displayValue = typeof this.value === 'undefined' ? '' : this.value;
35+
return `${title}${color.gray(S_BAR)} ${color.dim(displayValue)}`;
36+
}
3537
case 'cancel':
3638
return `${title}${color.gray(S_BAR)} ${color.strikethrough(
3739
color.dim(this.value ?? '')

packages/prompts/test/__snapshots__/path.test.ts.snap

Lines changed: 48 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -143,17 +143,21 @@ exports[`text (isCI = false) > renders submitted value 1`] = `
143143
144144
exports[`text (isCI = false) > validation errors render and clear (using Error) 1`] = `
145145
[
146-
"<cursor.hide>",
146+
"<cursor.backward count=999>",
147+
"",
148+
"<erase.down>",
147149
"│
148-
[36m◆[39m foo
149-
[36m│[39m /tmp/[7m[90mf[39m[27m[90moo[39m
150-
[36m└[39m
150+
[33m▲[39m foo
151+
[33m│[39m /tmp/[7m[90mf[39m[27m[90moo[39m
152+
[33m└[39m [33mshould be /tmp/bar[39m
151153
",
152154
"<cursor.backward count=999><cursor.up count=4>",
153-
"<cursor.down count=2>",
154-
"<erase.line><cursor.left count=1>",
155-
"│ /tmp/b ",
156-
"<cursor.down count=2>",
155+
"<cursor.down count=1>",
156+
"<erase.down>",
157+
"◆ foo
158+
│ /tmp/b 
159+
└
160+
",
157161
"<cursor.backward count=999><cursor.up count=4>",
158162
"<cursor.down count=1>",
159163
"<erase.down>",
@@ -186,17 +190,21 @@ exports[`text (isCI = false) > validation errors render and clear (using Error)
186190
187191
exports[`text (isCI = false) > validation errors render and clear 1`] = `
188192
[
189-
"<cursor.hide>",
193+
"<cursor.backward count=999>",
194+
"",
195+
"<erase.down>",
190196
"│
191-
[36m◆[39m foo
192-
[36m│[39m /tmp/[7m[90mf[39m[27m[90moo[39m
193-
[36m└[39m
197+
[33m▲[39m foo
198+
[33m│[39m /tmp/[7m[90mf[39m[27m[90moo[39m
199+
[33m└[39m [33mshould be /tmp/bar[39m
194200
",
195201
"<cursor.backward count=999><cursor.up count=4>",
196-
"<cursor.down count=2>",
197-
"<erase.line><cursor.left count=1>",
198-
"│ /tmp/b ",
199-
"<cursor.down count=2>",
202+
"<cursor.down count=1>",
203+
"<erase.down>",
204+
"◆ foo
205+
│ /tmp/b 
206+
└
207+
",
200208
"<cursor.backward count=999><cursor.up count=4>",
201209
"<cursor.down count=1>",
202210
"<erase.down>",
@@ -370,17 +378,21 @@ exports[`text (isCI = true) > renders submitted value 1`] = `
370378
371379
exports[`text (isCI = true) > validation errors render and clear (using Error) 1`] = `
372380
[
373-
"<cursor.hide>",
381+
"<cursor.backward count=999>",
382+
"",
383+
"<erase.down>",
374384
"│
375-
[36m◆[39m foo
376-
[36m│[39m /tmp/[7m[90mf[39m[27m[90moo[39m
377-
[36m└[39m
385+
[33m▲[39m foo
386+
[33m│[39m /tmp/[7m[90mf[39m[27m[90moo[39m
387+
[33m└[39m [33mshould be /tmp/bar[39m
378388
",
379389
"<cursor.backward count=999><cursor.up count=4>",
380-
"<cursor.down count=2>",
381-
"<erase.line><cursor.left count=1>",
382-
"│ /tmp/b ",
383-
"<cursor.down count=2>",
390+
"<cursor.down count=1>",
391+
"<erase.down>",
392+
"◆ foo
393+
│ /tmp/b 
394+
└
395+
",
384396
"<cursor.backward count=999><cursor.up count=4>",
385397
"<cursor.down count=1>",
386398
"<erase.down>",
@@ -413,17 +425,21 @@ exports[`text (isCI = true) > validation errors render and clear (using Error) 1
413425
414426
exports[`text (isCI = true) > validation errors render and clear 1`] = `
415427
[
416-
"<cursor.hide>",
428+
"<cursor.backward count=999>",
429+
"",
430+
"<erase.down>",
417431
"│
418-
[36m◆[39m foo
419-
[36m│[39m /tmp/[7m[90mf[39m[27m[90moo[39m
420-
[36m└[39m
432+
[33m▲[39m foo
433+
[33m│[39m /tmp/[7m[90mf[39m[27m[90moo[39m
434+
[33m└[39m [33mshould be /tmp/bar[39m
421435
",
422436
"<cursor.backward count=999><cursor.up count=4>",
423-
"<cursor.down count=2>",
424-
"<erase.line><cursor.left count=1>",
425-
"│ /tmp/b ",
426-
"<cursor.down count=2>",
437+
"<cursor.down count=1>",
438+
"<erase.down>",
439+
"◆ foo
440+
│ /tmp/b 
441+
└
442+
",
427443
"<cursor.backward count=999><cursor.up count=4>",
428444
"<cursor.down count=1>",
429445
"<erase.down>",

packages/prompts/test/__snapshots__/suggestion.test.ts.snap

Lines changed: 48 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -142,17 +142,21 @@ exports[`text (isCI = false) > renders submitted value 1`] = `
142142
143143
exports[`text (isCI = false) > validation errors render and clear (using Error) 1`] = `
144144
[
145-
"<cursor.hide>",
145+
"<cursor.backward count=999>",
146+
"",
147+
"<erase.down>",
146148
"│
147-
[36m◆[39m foo
148-
[36m│[39m [7m[90mx[39m[27m[90myz[39m
149-
[36m└[39m
149+
[33m▲[39m foo
150+
[33m│[39m [7m[90mx[39m[27m[90myz[39m
151+
[33m└[39m [33mshould be xy[39m
150152
",
151153
"<cursor.backward count=999><cursor.up count=4>",
152-
"<cursor.down count=2>",
153-
"<erase.line><cursor.left count=1>",
154-
"│ xyz",
155-
"<cursor.down count=2>",
154+
"<cursor.down count=1>",
155+
"<erase.down>",
156+
"◆ foo
157+
│ xyz
158+
└
159+
",
156160
"<cursor.backward count=999><cursor.up count=4>",
157161
"<cursor.down count=1>",
158162
"<erase.down>",
@@ -180,17 +184,21 @@ exports[`text (isCI = false) > validation errors render and clear (using Error)
180184
181185
exports[`text (isCI = false) > validation errors render and clear 1`] = `
182186
[
183-
"<cursor.hide>",
187+
"<cursor.backward count=999>",
188+
"",
189+
"<erase.down>",
184190
"│
185-
[36m◆[39m foo
186-
[36m│[39m [7m[90mx[39m[27m[90myz[39m
187-
[36m└[39m
191+
[33m▲[39m foo
192+
[33m│[39m [7m[90mx[39m[27m[90myz[39m
193+
[33m└[39m [33mshould be xy[39m
188194
",
189195
"<cursor.backward count=999><cursor.up count=4>",
190-
"<cursor.down count=2>",
191-
"<erase.line><cursor.left count=1>",
192-
"│ xyz",
193-
"<cursor.down count=2>",
196+
"<cursor.down count=1>",
197+
"<erase.down>",
198+
"◆ foo
199+
│ xyz
200+
└
201+
",
194202
"<cursor.backward count=999><cursor.up count=4>",
195203
"<cursor.down count=1>",
196204
"<erase.down>",
@@ -358,17 +366,21 @@ exports[`text (isCI = true) > renders submitted value 1`] = `
358366
359367
exports[`text (isCI = true) > validation errors render and clear (using Error) 1`] = `
360368
[
361-
"<cursor.hide>",
369+
"<cursor.backward count=999>",
370+
"",
371+
"<erase.down>",
362372
"│
363-
[36m◆[39m foo
364-
[36m│[39m [7m[90mx[39m[27m[90myz[39m
365-
[36m└[39m
373+
[33m▲[39m foo
374+
[33m│[39m [7m[90mx[39m[27m[90myz[39m
375+
[33m└[39m [33mshould be xy[39m
366376
",
367377
"<cursor.backward count=999><cursor.up count=4>",
368-
"<cursor.down count=2>",
369-
"<erase.line><cursor.left count=1>",
370-
"│ xyz",
371-
"<cursor.down count=2>",
378+
"<cursor.down count=1>",
379+
"<erase.down>",
380+
"◆ foo
381+
│ xyz
382+
└
383+
",
372384
"<cursor.backward count=999><cursor.up count=4>",
373385
"<cursor.down count=1>",
374386
"<erase.down>",
@@ -396,17 +408,21 @@ exports[`text (isCI = true) > validation errors render and clear (using Error) 1
396408
397409
exports[`text (isCI = true) > validation errors render and clear 1`] = `
398410
[
399-
"<cursor.hide>",
411+
"<cursor.backward count=999>",
412+
"",
413+
"<erase.down>",
400414
"│
401-
[36m◆[39m foo
402-
[36m│[39m [7m[90mx[39m[27m[90myz[39m
403-
[36m└[39m
415+
[33m▲[39m foo
416+
[33m│[39m [7m[90mx[39m[27m[90myz[39m
417+
[33m└[39m [33mshould be xy[39m
404418
",
405419
"<cursor.backward count=999><cursor.up count=4>",
406-
"<cursor.down count=2>",
407-
"<erase.line><cursor.left count=1>",
408-
"│ xyz",
409-
"<cursor.down count=2>",
420+
"<cursor.down count=1>",
421+
"<erase.down>",
422+
"◆ foo
423+
│ xyz
424+
└
425+
",
410426
"<cursor.backward count=999><cursor.up count=4>",
411427
"<cursor.down count=1>",
412428
"<erase.down>",

0 commit comments

Comments
 (0)